From 1be95e8a1a36ec8ac9ac8b112d45b0abc61af496 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Sat, 4 Jan 2025 23:43:40 +0100 Subject: [PATCH 01/54] [Localization] [UI/UX] Updates for pokemon-emerald-pro & pkmnems (#4968) * Add files via upload * Add files via upload --- public/fonts/pkmnems.ttf | Bin 26132 -> 26504 bytes public/fonts/pokemon-emerald-pro.ttf | Bin 93528 -> 93816 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/fonts/pkmnems.ttf b/public/fonts/pkmnems.ttf index b0b50d0f10ff8a64753acb663781be30d9b0213d..2cc7bfeee62569b1190a20ad91dcfada349b0d32 100644 GIT binary patch delta 1094 zcmY*YZ){Ul6hG(ny|!)*ZIL33+P?R0``SlzZflDKQBjwM5dH`(laRskwv`pLjfRzF z`hgN-3?a)TmJlOu*@p$^mkBgXd>_!@6ojc{A7-+U*p!42N%&?&*6}>|ZA5*^x#!>g zoqK-woqJ{mPyUJpKmhQ-32?xcbh>3nf93rgKz9#CgS+;=)Z~4X8V10vm z2g(3W4}fr`+vv|(79pX#i}LR7p7#!ojNX_8sHp<9B(oW#>#vU%3Y426*^s3`?E!Zq z)f35&WP9_4!3U9=7bq|WAimpkq|<15aJWLXrs;rQu zy#c`SPVPv5Ucde^ZSgp409kF716XOX&Gm#_ zHweWQsJrSo4%d}l!w($AG09V$7FQJ~Bv0v%_!br>i6FnEAc*jZt$h^nz;Uq9fnjXK zz1S(D=oWX2`{cFSn{*}AD~zFlA#A`scu)k#lXp`p!CUowWu4s=rik zR==-)TjnT!&cC0Jo55wDJ2&B$?ajc?xR!R;mCifAK<^FsX?YVVTvKdwzmC^SpS#at z;m7Bzp^c$jXejhq=xXR*SPkzC_k>S}uY}9thjNX)UfwSE$)Cy-@{D{>2`c-PL1jXj zQy!{LRaVpLesxeCSEtl@=445>on_exHp;HDN<@exBZnfxkxP*~Q6aiA+7NAx4n?m; zE1FN+thH!IwIS_NlQykYVk=^DtT~p8jmD;8v#~$o?zk4;7SF~{#m#tGU#U0i`}G0+ zj6R{yudgeWYhHXMBM}o*xKF>y0WPQoFRUYKsYF0wsf0hJ1SEybgkSL|{ESv6)SHX5 zriu9{LtMCJnxtLLrmgK2^9$ob{o*VQFt1-cm1ae0ldtJNU^1WXH%;mGQ>S`xsp8@+ z$D3xUHvOegu+l=kblYyx3!ns8>gB|949TFT81LX4q$cc|REgJH7#G`Ubomqt_odZj zJp+Ly?6ZUmFb8oA5FtMi|2=hpZTSJ5nr@Cg2f$M73m zfQzsI)#6;o=_KyJowy6%hOcoq?s3s0&1D;t&Nn)aW(G1&!&a_ieTN$wH#9BlL@D3d XE-<=rnq`3x6$$)4cZ;6x|6kKTDlJBg delta 714 zcmXX^Ur19?82`?8S!**j(V1@Cf4hICq~@}enFUe{1kEx`25MKG4sp5K9@dK!>A^sv zxY9#OL=1!wS>7l}DY3E;so{%06j(|4;DZlprWe*X>A>Nf-}n2@kMsS$Z}=-2T_iCe z0B{5b!3d3B@74Nn%*_J$cQNX?c>aPH)`=bQ3S#yhjO#QYlmde-e3fPv7nC_YKx+0Lv=Cm9>Ch>H6c;%8>hp`*a}Sk15txa|sIO zVn7H4Bhl~8mg9&M0F$(|Qajiw^0UVTBI*Wc;WtNX|i^@l0{Fol;q+&1lvX`E!u!zZNvFOsW< z%;(5QEn$94VoS7zmgow)g&w45=yfKWsbaiL2Q$RPnR#ZD1y*3svJp1HCOP0rIWO17 zMY$*36qn?5K9hIzZTtuy=V$p9e#dEZdYnGzi1V%Uw_p(}1fS3=3=5OOnph}$#DF*| zPKXQQnq-k2l1J*6qQ%mf^jX@GGv#8rR=zF|$j{_i`KK$#Rpsh+jk%^=UtAkSP~0k#{ZAuYYF|%*F~`334+*=!J^%m! diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 84e49ebbc40def70813370e460324d84e021cc69..e4ee49dbff37948936b7770f697ab143b9f434d5 100644 GIT binary patch delta 4252 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 delta 3617 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 Ku>OPyfd2zVR<#!Z From 197b264fb0bf07ece381bd7e9488f3e8048b058b Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:24:08 -0800 Subject: [PATCH 02/54] [Bug] Gimmighoul & Eevee eggs will now properly randomize their forms (#5080) --- src/battle-scene.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 327ab1cc926..ae992f5c00f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1424,6 +1424,8 @@ export default class BattleScene extends SceneBase { return 0; } + const isEggPhase: boolean = [ "EggLapsePhase", "EggHatchPhase" ].includes(this.getCurrentPhase()?.constructor.name ?? ""); + switch (species.speciesId) { case Species.UNOWN: case Species.SHELLOS: @@ -1455,7 +1457,7 @@ export default class BattleScene extends SceneBase { } return Utils.randSeedInt(8); case Species.EEVEE: - if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { + if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30 && !isEggPhase) { return 0; // No Partner Eevee for Wave 12 Preschoolers } return Utils.randSeedInt(2); @@ -1483,7 +1485,7 @@ export default class BattleScene extends SceneBase { return 0; case Species.GIMMIGHOUL: // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs - if (this.gameMode.hasMysteryEncounters) { + if (this.gameMode.hasMysteryEncounters && !isEggPhase) { return 1; // Wandering form } else { return Utils.randSeedInt(species.forms.length); From 07b69c9485841063a4fc0353c48460eaf1e8083e Mon Sep 17 00:00:00 2001 From: Unicornpowerstar Date: Tue, 7 Jan 2025 01:26:00 +0100 Subject: [PATCH 03/54] [Sprite] Fix Issues with the 658-ash.json to display sprite correctly (#5055) * [Sprite][Color] Fixing Issues with the 658-ash.json to display sprite correctly - Fix colors not being shown correctly in the json. - Said fix are adding a new entry to separate the whites that are F8f8f8 by adding f4f4f4 - Changed the wrong color into the correct one on the rare. --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- public/images/pokemon/658-ash.png | Bin 1244 -> 1246 bytes public/images/pokemon/exp/658-ash.png | Bin 2468 -> 2472 bytes public/images/pokemon/shiny/658-ash.png | Bin 1247 -> 1247 bytes public/images/pokemon/variant/658-ash.json | 4 +++- .../images/pokemon/variant/exp/658-ash.json | 2 ++ 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/images/pokemon/658-ash.png b/public/images/pokemon/658-ash.png index a122df859bd0ed45c2bd78ee2d024b343a4cdc88..fa6ce5cb16521b9b690cd05cd6192b31b198b3fc 100644 GIT binary patch delta 1121 zcmV-n1fKic3El~iHUro1^pQC^3iR~!@E90I;80ePd?kMxg= zqE&PDC5!j0yfh(@tdofeQWtq2cpTUkiyW*k;eI6e27)r4V>}!?5R{uNuK)~OKMX0`&&PegUj-qJy~lr`defxu3tnwI`RBUVp5 z8J@GFpQ^jM!12+uH9T;f6xg_2o=*l*cU6%l>AK(%Cy@ot5J(yi%-7jzw&XcKea>m7 z@3((OXV7ak)s1F`Js~AL$8Tp+&?;B1i6|Z)R*r)Eb^d)kgICMFBp}+t(?r^@3!3+; zIrb)<^x;>+gXY;p?pX)Y{2BFdUglu{*=$YIh$n&8fu?z~C&$lXjAvE7WKXE2(vmw-@0P^2%HQMvWJXNFX%{*h`{E-iFdhbXnf7Tiu z$gA3e(23^{{RCc;zL@eZZ9$~uwNzMA6S8dN=%r=};Dh80mpT;k)+~ZO#wO;Szxu;GBGmn#AwK0;k zF4xSHlDcW#_%5n$t8^bJ#8`{N*37t%R(vh@T)cYUkyzLl4Hj3Oku0q&+mG}k?D-B1 zW#(-tkRxu28YO+&mTS7)FF7jwv7LV-OyZ(m(Q?Dchw~=sOAtnC=ZG79=ZZc}_GCrU z4|j5jY;OV#aV-l--46367K$dLw3z!2hd8TUTnuoGvp`&2pk*S6W&MBhUM0r}Za!h$ ziu`oD9^uaLn)|L^3}RvCEbDe1re*suBn(3XGsN7v$=SPyY<_FePu98voQcK1kWmZ-b^{S?i00000NkvXXu0mjf#V0qZ delta 1119 zcmV-l1fcug3ET;gHUj_v0FgO43i$Z=@E91BQ=k8ld?kM-Nklmj;EW9>DCmq`hD~ z^EeLrN$-EM?~^xx*MxiBb4V0g;?iXGqloGagJ|;rIrrTYjf0!`<{028B8um^VaSsG z$|Vgk+lmB|2<%0Uz#TUny_hNDx-R+KXK*0T?zrf|ScZo@CLOG;3l=Y!!4Z^u^M57} zwv7AGn7)n!2+GZtR{#cX+_seM=i|0*QxMWPdJKQ6H_!UM;3WqXJ!ebY=A0AHW=A3L zTxC-TK%jbW1?7B#ZJ#aui$M;j+WL@A zb9VAmbypWSK62ab~h8<)%T$sp>ks?sD~7d+x5vcMSvN#lX#Iy=plJm;s+InDI_ z*64o>dab3p(af+Xq=e`A?aT^VqaH5nvTY!nt?N4RB+xd{bWir=_*soHT}LE)hTp3s-UOQN zhdc}zOY*1DRniQSXXb=ap2}HYD1mIN_&V+5Yg?bb~{=Zv|_PjAq)#!S&ESNZd zs`elZ;`u{AftRE&ro2mg5Gi>*6_!**_TcGMhT6=Vz8?i-FB~iVAUS)m5HC312|a(^ z%HR^`@4+3|n7}f`GfwwaKQb7z4d;}K0VMIK^NXwWxD-pR*ZX+xsguFNDL zRsI6HM#uhxQWWPcE`R}tZ*C)4p!&NUDrz6h5MD>eYbP*Kq&uN7a5$O!@fyaGSoIE?>uK7VIr+_%5aO+t!mO l{Wr`HY^hu|qw1yBj6c9cS26bNh?M{U002ovPDHLkV1k{3Ky?5B diff --git a/public/images/pokemon/exp/658-ash.png b/public/images/pokemon/exp/658-ash.png index 6bb84f3e4fd622689fb60ec1c06599aad87670a7..ced4cbcec716965af329c30e41f496414ccc6716 100644 GIT binary patch delta 2351 zcmYk2dpy&P7sp*zy2z7DAuUBHml2Q1vsRQSx5qDcZHO6JQ(}faecN(-atq-_7IIl4 zWNtAmdq$eMq-5CG!>}^;ESHe{*01M}-}&Rb&*!|(Ij?h0nesE`hpw{A%gdQ=$3dp+ zhto3gI)M1{MQb-)A^+N+S*4aLDVJ>&G$J0ZZ~K8eQIDBB7rgdkJ=`hoKgB{F=zuV1>rivv-nbXW z*F#QMr?+P{J%FJF*aJ__>GN~8u3*Dh2#9R`QQtZDw~OyP$Rc9tQ{jz2iC=vvV;C1; zfZ!JJJtiwi4AD7P*aa7aSWp;dSg)pn&whzIZ z4WqWW^5|#49QFczw9&Ju$hk16`S8}ev=x`G6!3wko#q}SwoU%#o<#F1{mr5B%}CVjDdH;_~X+$(YAU zc_#W}1tEtt5W{K}J@jOQvXw*ai(gViN|zEk!y=fEs%uJmE7Rb}zDu!NuDIQ$HiZkh zkX8++Rq{(pL?k8~lLzdVOh1hCvCru6uM88exJ*-hVk*P-`6fyBkux(g&o57g0bV3a zu*(Z}U;&3kcW|l&OfdW}E8p;-BB~2ndHB@LJ(IIMoeIyw^y3-EhJLoLh0bFpuZ)r( z{CV{f^0P>3*uWXZ(j&Pmb#EIvqARf$zZkP>u)gm}?ySH*{WhA;yxdd!;&BMnA@8qf zShEMINm&O4>5qS@85TV*s&2(~T(}*)+?eUjudyX@EW3enks(~s#XJ?DP#u<&F)gT|-`TLAGM&DTGlCy38nS!qqqthVbvx`>KgNxi!i$RZxDMht;$*D? zV-j8cfCG$w=4Y#LFeGY(Ynt-Z#iGjjEkl&JF?*69fs=!w)6~d+4H55|W~yYRFOSr; z)cHDi1AkQpwlYK;0#@%dvVt#?!3Ws1h1jY_mirfx#_QjI8Z#$qT_GWT<(GcP-6fA| zb$yv}+CSE&(;913i_V*QRkbGWDn#1*(J;(Q}pwFjZ^bz6m8k)US6- z7OJUSvnsx*5C*t$gW)X0=R+@IgK#ANP)0R$t@Yaf zLK!ThJnEo+71>07*xVXH=ltQLXX}A`y)5sd6AI)~AtpyjrSfk#VbuG#o+FH3SKajH z?U*^qpAdUr+%DB~CcTOH&Rm({#{+gfzxP!1eMzep$%qSmBlOt@+F}GdJMX{~9Ti90 zGe{?H1C)_*bpann5bi$nHeAnzpU@MZquqeG)Ajfv>WNAtc08rjE9ORWEuAv0*Nyf| zQhoRlcBTIQ-4{v=i8IJV3uj|(Xi@EEP}~RR#^0a~aebF|$4hpq2pZEq0PLI49o<*3 z1)*MO2CTcXWn<|1YJ!xKhMwQ9;q=j~OwsY)I~nSBnxVS|JJKziBc2(Zqn@NpCiIAd zwym3MGisiNhbz)U=C$Z4W%LBQ+VhU#Aqe%UX7~BaKzv7;DCjuY5W2a#)E92{sUUGu z+DqQVh`OuwQEv)+I5;B&SpLlIMh}ZVAQeceTqUq2v@|@ptU-%~n%L7wjT1S%zJpET zF$Q^EzFITzV)`M;CrW3 zUdFe)Hpg$Bmw~Zdg)}FoU6z1$Zw=+&=13c@!Ov(|nM`e|k3y79MvOFj%P$17#F=<2 z6aOvs{77ZEuK(1AS^d0jtE0cv8mf92DI?o*PIdw*BPYQ)vi<=)2-w-g1pRXB2ZF$D zwD0ohyk09T;m6Eu_XVv@vpTqLNb#l_4S?%N6`4PEtZqv^u(j>Yys5YB==W4<((Ef#R6b1_qA3`DMLJDpg7)58e{(P0*ihW+ zNZUUp&Xfihh1{8+qUB~33kL7yWtTaRiJQlgxjKTFNrWZl!szPOYp%_vEr}bF~bH)(Z3BlTks&qq@T?{Vh)n{f8hR9 z5_Iq3YoE9Jb;MY+_xZGx>@^ti$E9!>5h$1aseW8asF{`uOcN^xf#nWXJ<6cX|Jb_t zvt^y+%`^$TO7I}x9>NaEn-M((Mg?Dc^8Iap>BWJ2A8-2;j5V#_y(SoGS`U}JT!JVY z()ijUi1Myg4pOGOpJ_)Mja%QPMs1sFAP7ciTuDM|$wEW@iFvk2_5Mbc!ob%7 delta 2347 zcmYk2c{~&TAIHBMGNP87qnUL;K=4{rcng`Q!6^zh0lu`}O#|KNa$&@=sj=>+9?3u1CbPE*Ea6 zM`>eu_UBH!VhY%vPwI=I3dbw-bf(YrZEw%d790r#KFyVk_(=>Xo1Yqb%lzRhGhR+t z7!vlK+|xR9MJoMcPLW^aF_PlUl3i#dmw%&;VvC^n9<(A$5V&R|+@j9Rru>N3rF=c! zH0$D|`9;$#0gmx7!Q$!Iaay^Gor6+1pO3v!wK3_@2s59Eh<+R@NV)$oW6LINouBM+ zNbPp>+OK_UmV~T~Eem`@$s<^H+r;x)1cQ5>Svg!?I^0ZL3!ek-H@rOIirK2j3KtkKs>~uuDP3`aZ7_Y|Sz&sIOlo5D8*S;Ch28)X0DIS> zuv(~68rAN7?L9h~|IG%+ooe(jGn!~sT+@GkWLR=`bu**W*U|>Va^Z+i;f#exIBixg zDdW~^Vf(I=PRfM@yWYRx+iRZa?*(mGdp*yvE^K~%^g3)Mw`DU3(>69xfy(+AM9-xm ztk%zYWV*F6UI%%g-`Ly&4w>MeVZGy#$%d(kgKzXxCd%um-xk!&iA_in-4= zJCoEEr||SCe4llSSEK;GI&*}?o*Of)#O_CMEA~Z?xQBgH?D2#dRl?mS2-RM&Z5{Hu z3#hcSLLRJb=t@xkr;zh(=m6mwd28q35OK71WYO2RKd|UWK#e#j2)lT18NC)L2G z*lbR2HglwvgJ5nz2O$#kgrk^a+Wl3Y8t$TylVt7~EK_`KCd-9! zg8Y6Zk?Qu-9{DVOX&d(hJi_C#UNT04lgbFMBa-2M$}QrXXB2=aAr8sOSg#)Ie@#ah z&GvTZ840fc;HkfGQ>KL5$oMyT!~U89yXjmItp& z_`b4HvTVE%;O~-RIHJ|L0Cs-h$8OcEk}i55!y7hOX-+dP)P)$55zP2hnBx;{fAto1 zoZrD`GZc~9|E?+=)<`K-Mw~);Vtl3CWkcM+M0E`y3pe>_mbutdB|RF7@+-1e){l-@ zV&UfIY2Qm20g(i!#7ce~NA-pKkl!{dD$n!a_F{m?kJn#a_%4s>4|lvzCNe*Ls-EY*W21#K6iyFE=r?C!ZVc@p%nDPh0xOiRL z<&UPugUV-bt%bMnUB|gOEc=Y_!KO}AL!cqRfxE%%s~!(9_x#&s(%9Xslwfa7ybDYd z)OmC6aA#6!C(btQRB!u$l?lc9%>aH&D%zgaZ3n%I5pcfX+Wm){z$XE4dl(Zfa*zQ74XI(?zw^ zRD!7z;J^OmUzww@@Y!!;t|>~t8{6dBwO2z-$Wg>$zSBZ5yrkAyAheBhC0dhJM2YV! zX4-eX@;1nk2p}xb42k$a1q~yzRL9`$XHo53VmFrS@D$a}ve)*PgKPL!S`p2b9z@Fc zJ|Fs!pvyk00lvU6t5ym7_A)d(P01)0r9%#ca(tXkzoNe|sb$y)22go~Idg7*v0Ood zq;bwt9Vd5j75&BXv6cs=j^ik2R9`|jURM=YU<`7UBsaZFh1X(FCCvi59NqhmK!#|<-sxVwI5J|90X zK>Cmz2TrXaV@Qs*)Dy_^oxD7-owmKSq~!q(D)S`U01coz%S zJWVeWgJ{MM6~#g{PygBf_;vOhSgB;WJ*W+-Y&n_04XONKKuzs3=(xt`%Ckjl-EnDj*v=M$&bm>on?41p%Tshg_%{w)bD*k^U|4)gKBHLpdgSr{$ZJpuv zNldaON?bIeBr!_dVYg%pc1P_@3h??Q_ECwg)diJ1X0ERpJGk@e(_?pi#CWtA5$aBF zNHwPdWJKRgP6Z@+QElJ8`bya~StwQ`g12>C=|g~rbzIrmuj5`iS7WHQ(_T9J4v?tg z$q#eVV(zXigN!iu-rWMwgJMfWhS*dv+A2VYr3jfWe3^GieA@Ug(Xv19bguf0Puzb2 D;d-~P diff --git a/public/images/pokemon/shiny/658-ash.png b/public/images/pokemon/shiny/658-ash.png index f5de608708eb8b505f1e837a4a697ad7e2c3f4cd..b25693fd24e91b07e97e897fe743ab36853a84b1 100644 GIT binary patch delta 690 zcmV;j0!{tj3Ev5jbqe(K^zaxM=HIC{k$*S^0;V8X=aIZ3f4JYPJe*EE*6O%3Pqb>z z9NeH6~U5K+Cp7j5n#<=%Uuac~pgdy{xt3nWPi~&s8#o00gS{ zP*Ca^+_~hk1p1l|yiYM89JQx684AzCGGh(#j$#9&R+>`GC zFkA6((Y3O8jj1`^XlB?GQo^(RbY=z9`cW#1$HmH0aKF|)4@wrk;_ea%<@$q%q zH_PW3?(U1aR6JV?vXNaOVy_^=Q%|PjshqX4Crdy)zKUjz@>LJ=s`y2Kd?-9cd#P6f zo?pZLzm-p0W#A`IN%#H7e$~GLP5GC9bS-`o3j*AF>muT@S>bWJw641YHMs=!16s*9 zkm&X~;LbxoT=6K{?>mvn-##sF)0^t@b{uBG-m?1dQfj|#J$R=-!`#4{ldEJDJ=Bu% Y4eqk$*S^f~Fu?=8?Q2e|S8qJRDCv*6O%3Pqb>z z9NeH6~U5K+Cp7j5bx<=%Uuad0Ewd8D0VCx#50~*}h(OT~`Gmj=jgAdefxO3m#IS=zZ>yTdlR?nWPi~&s8#o00gS{ zR8Z;@+_%{R#YP~9lkdK))N&*s@yW}3JbN)Z6i>4IcrXC04AzCGGh(#j$#9$*-IMPD zFkAU_(Y3O8jj1`^XlB?GQo^(RbS4GU`cW#1$HmH0aKF|)4@wrk;vNzZZQ*Gm?bmwE zdDR?ylTP}&D%Z80=di5v*op^}sR)I~|vM1$dX_I;a905g>mjXxv z^Nf?>0zv|u!;>WgNdcvkZvzz!l756e*Ma#g-2pjblaK=(EsWI85jVQd6(M@EBI(7Q zTqJKF0`ze$3+2cywD;IT(PWgCQrF=SXS9n80gjW|0~`VvYm@f_E`Pqhpa`Va`1m^Q zo8@y1clSkIDxR$c*~qRCu~!h`sVCF%QqEf0lO-S?Uq!P<`KkwbRs14AJ``S}t?HG4 z=htxmZ{^cg8TiRl(tZE2U-fT5Q~u>2U5lT@f&jPPGK+X@R(RYlEz9meO)f$GfL8Ji zB)WYLxbx5tS3HXLdpl(Ew@-`P_@=tN9fw)4H!uFXl-h4w58mm|FgLK~<{9 diff --git a/public/images/pokemon/variant/658-ash.json b/public/images/pokemon/variant/658-ash.json index 29b5bd2560b..1845b2b1bea 100644 --- a/public/images/pokemon/variant/658-ash.json +++ b/public/images/pokemon/variant/658-ash.json @@ -4,6 +4,7 @@ "3f4447": "466698", "de3431": "3fca9f", "f8f8f8": "a1e9f0", + "f4f4f4": "d7eff4", "7b282e": "0e3e81", "6b1d1d": "206d74", "4ebdd9": "41a7b0", @@ -11,7 +12,7 @@ "bfbfbf": "8cc7d4", "ffb2bf": "b7e9ff", "bf4c60": "4386df", - "fff0a6": "271f4c", + "fff0a6": "208698", "3e7acc": "6b4592", "18335c": "170738", "f2798d": "8dcfff", @@ -25,6 +26,7 @@ "3f4447": "466698", "de3431": "9ceec6", "f8f8f8": "89d2b8", + "f4f4f4": "d7eff4", "7b282e": "152a5c", "6b1d1d": "356e8d", "4ebdd9": "2f6e74", diff --git a/public/images/pokemon/variant/exp/658-ash.json b/public/images/pokemon/variant/exp/658-ash.json index 96b60b02adf..79cad7ea42d 100644 --- a/public/images/pokemon/variant/exp/658-ash.json +++ b/public/images/pokemon/variant/exp/658-ash.json @@ -4,6 +4,7 @@ "3f4447": "466698", "de3431": "3fca9f", "f8f8f8": "a1e9f0", + "f4f4f4": "d7effa", "7b282e": "0e3e81", "6b1d1d": "206d74", "4ebdd9": "41a7b0", @@ -25,6 +26,7 @@ "3f4447": "466698", "de3431": "9ceec6", "f8f8f8": "89d2b8", + "f4f4f4": "d7effa", "7b282e": "152a5c", "6b1d1d": "356e8d", "4ebdd9": "2f6e74", From d0db6a35d2afd6318e6bb9a8e9d2be7e0254b35f Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:27:23 +0100 Subject: [PATCH 04/54] [Bug] fix #5102 Catching Charm now always max weight in Daily Mode (#5103) Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> --- src/modifier/modifier-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 540af8a0b41..e1c8cb04405 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1843,7 +1843,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100) ? 4 : 0, 4), new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), From b0c347e20d8843885e9b3c8a9f569072ce2bdca8 Mon Sep 17 00:00:00 2001 From: Zain <34523777+Zain-A-Abbas@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:29:25 -0500 Subject: [PATCH 05/54] [Bug] Fixed defog not removing the target's Safeguard and Mist (#5107) * Fixed defog not removing the target's Safeguard and Mist * Made requested changes and added unit test * Remove stray newline --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/move.ts | 3 +- src/test/moves/defog.test.ts | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/test/moves/defog.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 7a6f08a5372..c86b168fb57 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -9220,7 +9220,8 @@ export function initMoves() { .attr(ClearWeatherAttr, WeatherType.FOG) .attr(ClearTerrainAttr) .attr(RemoveScreensAttr, false) - .attr(RemoveArenaTrapAttr, true), + .attr(RemoveArenaTrapAttr, true) + .attr(RemoveArenaTagsAttr, [ ArenaTagType.MIST, ArenaTagType.SAFEGUARD ], false), new StatusMove(Moves.TRICK_ROOM, Type.PSYCHIC, -1, 5, -1, -7, 4) .attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5) .ignoresProtect() diff --git a/src/test/moves/defog.test.ts b/src/test/moves/defog.test.ts new file mode 100644 index 00000000000..c83cdc192bf --- /dev/null +++ b/src/test/moves/defog.test.ts @@ -0,0 +1,71 @@ +import { Stat } from "#enums/stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Defog", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MIST, Moves.SAFEGUARD, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.DEFOG, Moves.GROWL ]); + }); + + it("should not allow Safeguard to be active", async () => { + await game.classicMode.startBattle([ Species.REGIELEKI ]); + + const playerPokemon = game.scene.getPlayerField(); + const enemyPokemon = game.scene.getEnemyField(); + + game.move.select(Moves.SAFEGUARD); + await game.forceEnemyMove(Moves.DEFOG); + await game.phaseInterceptor.to("BerryPhase"); + + expect(playerPokemon[0].isSafeguarded(enemyPokemon[0])).toBe(false); + + + expect(true).toBe(true); + }); + + + it("should not allow Mist to be active", async () => { + await game.classicMode.startBattle([ Species.REGIELEKI ]); + + const playerPokemon = game.scene.getPlayerField(); + + game.move.select(Moves.MIST); + await game.forceEnemyMove(Moves.DEFOG); + + await game.toNextTurn(); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.GROWL); + + await game.phaseInterceptor.to("BerryPhase"); + + expect(playerPokemon[0].getStatStage(Stat.ATK)).toBe(-1); + + expect(true).toBe(true); + }); +}); From 29087710b7e180efe31594c5d178ec9770a3c2ad Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:34:16 -0500 Subject: [PATCH 06/54] [Balance] Adjust Orb & Light Ball Weight Functions (#5070) * [Balance] Adjust Orb & Light Ball Weight Functions * Apply Kev's Suggestions Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/modifier/modifier-type.ts | 74 +++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index e1c8cb04405..b6cf78fb414 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1040,7 +1040,8 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { for (const p of party) { const speciesId = p.getSpeciesForm(true).speciesId; const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; - const hasFling = p.getMoveset(true).some(m => m?.moveId === Moves.FLING); + // TODO: Use commented boolean when Fling is implemented + const hasFling = false; /* p.getMoveset(true).some(m => m?.moveId === Moves.FLING) */ for (const i in values) { const checkedSpecies = values[i].species; @@ -1755,56 +1756,69 @@ const modifierPool: ModifierPool = { }, 12), new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { - const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); - - const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] - .some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves from comment they are implemented - const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] - .some(m => moveset.includes(m)); - // Abilities that take advantage of obtaining the actual status effect - const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ] - .some(a => p.hasAbility(a, false, true)); - if (!isHoldingOrb) { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + if (canSetStatus) { - return hasRelevantAbilities || hasStatusMoves; + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [ Abilities.TOXIC_BOOST, Abilities.POISON_HEAL ] + .some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [ Abilities.FLARE_BOOST ] + .some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; } else { return hasItemMoves; } } + return false; }) ? 10 : 0; }, 10), new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { - const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] - .some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves from comment they are implemented - const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] - .some(m => moveset.includes(m)); - // Abilities that take advantage of obtaining the actual status effect - const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ] - .some(a => p.hasAbility(a, false, true)); - if (!isHoldingOrb) { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + if (canSetStatus) { - return hasRelevantAbilities || hasStatusMoves; + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [ Abilities.FLARE_BOOST ] + .some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [ Abilities.TOXIC_BOOST, Abilities.POISON_HEAL ] + .some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; } else { return hasItemMoves; } } + return false; }) ? 10 : 0; }, 10), From d3fafa27702f2121e89852cc45481dd935a6a7fd Mon Sep 17 00:00:00 2001 From: Ori shalhon Date: Sat, 11 Jan 2025 03:10:52 +0100 Subject: [PATCH 07/54] [UI/UX] Add random selection option during starter select (#5075) * Update submodule public/locales to the latest upstream commit * feat: add random selection option during starter select * move random selection behavior to seperate label * Update public/locales submodule reference * Remove debug console.log statement * Update locales * Update src/ui/starter-select-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/ui/starter-select-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/ui/starter-select-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update locales submodule --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- public/locales | 2 +- src/ui/starter-select-ui-handler.ts | 166 +++++++++++++++++++++++----- 2 files changed, 140 insertions(+), 28 deletions(-) diff --git a/public/locales b/public/locales index 2e03bc8f273..4928231e22a 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 2e03bc8f2736269bfa365faad587c3ec54a37621 +Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 691e339eafc..38a2bb85de6 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -130,9 +130,10 @@ const valueReductionMax = 2; const filterBarHeight = 17; const speciesContainerX = 109; // if team on the RIGHT: 109 / if on the LEFT: 143 const teamWindowX = 285; // if team on the RIGHT: 285 / if on the LEFT: 109 -const teamWindowY = 18; +const teamWindowY = 38; const teamWindowWidth = 34; -const teamWindowHeight = 132; +const teamWindowHeight = 107; +const randomSelectionWindowHeight = 20; /** * Calculates the starter position for a Pokemon of a given UI index @@ -318,6 +319,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private starterIconsCursorObj: Phaser.GameObjects.Image; private valueLimitLabel: Phaser.GameObjects.Text; private startCursorObj: Phaser.GameObjects.NineSlice; + private randomCursorObj: Phaser.GameObjects.NineSlice; private iconAnimHandler: PokemonIconAnimHandler; @@ -366,8 +368,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterContainerBg.setOrigin(0, 0); this.starterSelectContainer.add(starterContainerBg); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight)); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY + teamWindowHeight - 5, teamWindowWidth, teamWindowWidth, true)); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY - randomSelectionWindowHeight, teamWindowWidth, randomSelectionWindowHeight, true)); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight )); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY + teamWindowHeight, teamWindowWidth, teamWindowWidth, true)); this.starterSelectContainer.add(starterContainerWindow); // Create and initialise filter bar @@ -605,6 +608,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.startCursorObj.setOrigin(0, 0); this.starterSelectContainer.add(this.startCursorObj); + const randomSelectLabel = addTextObject(this.scene, teamWindowX + 17, 23, i18next.t("starterSelectUiHandler:randomize"), TextStyle.TOOLTIP_CONTENT); + randomSelectLabel.setOrigin(0.5, 0); + this.starterSelectContainer.add(randomSelectLabel); + + this.randomCursorObj = this.scene.add.nineslice(teamWindowX + 4, 21, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); + this.randomCursorObj.setVisible(false); + this.randomCursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.randomCursorObj); + const starterSpecies: Species[] = []; const starterBoxContainer = this.scene.add.container(speciesContainerX + 6, 9); //115 @@ -1337,9 +1349,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterIconsCursorIndex = this.starterSpecies.length - 1; this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { + // TODO: how can we get here if start button can't be selected? this appears to be redundant this.startCursorObj.setVisible(false); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); + this.randomCursorObj.setVisible(true); } success = true; break; @@ -1386,14 +1398,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.UP: if (this.filterBar.openDropDown) { success = this.filterBar.decDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 ) { // UP from the last filter, move to start button this.setFilterMode(false); this.cursorObj.setVisible(false); - this.startCursorObj.setVisible(true); + if (this.starterSpecies.length > 0) { + this.startCursorObj.setVisible(true); + } else { + this.randomCursorObj.setVisible(true); + } success = true; } else if (numberOfStarters > 0) { - // UP from filter bar to bottom of Pokemon list + // UP from filter bar to bottom of Pokemon list this.setFilterMode(false); this.scrollCursor = Math.max(0, numOfRows - 9); this.updateScroll(); @@ -1410,12 +1426,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.DOWN: if (this.filterBar.openDropDown) { success = this.filterBar.incDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // DOWN from the last filter, move to Pokemon in party if any + } else if (this.filterBarCursor === this.filterBar.numFilters - 1) { + // DOWN from the last filter, move to random selection label this.setFilterMode(false); this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + this.randomCursorObj.setVisible(true); success = true; } else if (numberOfStarters > 0) { // DOWN from filter bar to top of Pokemon list @@ -1437,8 +1452,100 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; break; } + } else if (this.randomCursorObj.visible) { + switch (button) { + case Button.ACTION: + if (this.starterSpecies.length >= 6) { + error = true; + break; + } + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number ) => total + this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + // Filter valid starters + const validStarters = this.filteredStarterContainers.filter(starter => { + const species = starter.species; + const [ isDupe ] = this.isInParty(species); + const starterCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); + const isValidForChallenge = new BooleanHolder(true); + Challenge.applyChallenges( + this.scene.gameMode, + Challenge.ChallengeType.STARTER_CHOICE, + species, + isValidForChallenge, + this.scene.gameData.getSpeciesDexAttrProps( + species, + this.getCurrentDexProps(species.speciesId) + ), + this.isPartyValid() + ); + const isCaught = this.scene.gameData.dexData[species.speciesId].caughtAttr; + return ( + !isDupe && + isValidForChallenge.value && + currentPartyValue + starterCost <= this.getValueLimit() && + isCaught + ); + }); + if (validStarters.length === 0) { + error = true; // No valid starters available + break; + } + // Select random starter + const randomStarter = validStarters[Math.floor(Math.random() * validStarters.length)]; + const randomSpecies = randomStarter.species; + // Set species and prepare attributes + this.setSpecies(randomSpecies); + const dexAttr = this.getCurrentDexProps(randomSpecies.speciesId); + const props = this.scene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr); + const abilityIndex = this.abilityCursor; + const nature = this.natureCursor as unknown as Nature; + const moveset = this.starterMoveset?.slice(0) as StarterMoveset; + const starterCost = this.scene.gameData.getSpeciesStarterValue(randomSpecies.speciesId); + const speciesForm = getPokemonSpeciesForm(randomSpecies.speciesId, props.formIndex); + // Load assets and add to party + speciesForm + .loadAssets(this.scene, props.female, props.formIndex, props.shiny, props.variant, true) + .then(() => { + if (this.tryUpdateValue(starterCost, true)) { + this.addToParty(randomSpecies, dexAttr, abilityIndex, nature, moveset, true); + ui.playSelect(); + } + }); + break; + case Button.UP: + this.randomCursorObj.setVisible(false); + this.filterBarCursor = this.filterBar.numFilters - 1; + this.setFilterMode(true); + success = true; + break; + case Button.DOWN: + this.randomCursorObj.setVisible(false); + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.filterBarCursor = this.filterBar.numFilters - 1; + this.setFilterMode(true); + } + success = true; + break; + case Button.LEFT: + if (numberOfStarters > 0) { + this.randomCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + 8); // set last column + success = true; + } + break; + case Button.RIGHT: + if (numberOfStarters > 0) { + this.randomCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex); // set first column + success = true; + } + break; + } } else { - let starterContainer; const starterData = this.scene.gameData.starterData[this.lastSpecies.speciesId]; // prepare persistent starter data to store changes @@ -1466,7 +1573,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.lastSpecies, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)), isPartyValid); - const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newCost = this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE) { // 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 = [ @@ -1605,7 +1712,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.showText(i18next.t("starterSelectUiHandler:selectNature"), null, () => { const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); ui.setModeWithoutClear(Mode.OPTION_SELECT, { - options: natures.map((n: Nature, i: number) => { + options: natures.map((n: Nature, _i: number) => { const option: OptionSelectItem = { label: getNatureName(n, true, true, true, this.scene.uiTheme), handler: () => { @@ -2016,11 +2123,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else { if (this.starterIconsCursorIndex === 0) { - // Up from first Pokemon in the team > go to filter + // Up from first Pokemon in the team > go to Random selection this.starterIconsCursorObj.setVisible(false); this.setSpecies(null); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); + this.randomCursorObj.setVisible(true); } else { this.starterIconsCursorIndex--; this.moveStarterIconsCursor(this.starterIconsCursorIndex); @@ -2065,9 +2171,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor - 1); } else { // LEFT from filtered Pokemon, on the left edge - - if (this.starterSpecies.length === 0) { - // no starter in team > wrap around to the last column + if ( onScreenCurrentRow === 0 ) { + // from the first row of starters we go to the random selection + this.cursorObj.setVisible(false); + this.randomCursorObj.setVisible(true); + } else if (this.starterSpecies.length === 0) { + // no starter in team and not on first row > wrap around to the last column success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); } else if (onScreenCurrentRow < 7) { @@ -2103,7 +2212,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor + 1); } else { // RIGHT from filtered Pokemon, on the right edge - if (this.starterSpecies.length === 0) { + if ( onScreenCurrentRow === 0 ) { + // from the first row of starters we go to the random selection + this.cursorObj.setVisible(false); + this.randomCursorObj.setVisible(true); + } else if (this.starterSpecies.length === 0) { // no selected starter in team > wrap around to the first column success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); @@ -2159,7 +2272,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return [ isDupe, removeIndex ]; } - addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset) { + addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { const props = this.scene.gameData.getSpeciesDexAttrProps(species, dexAttr); 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)); @@ -2170,7 +2283,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterAbilityIndexes.push(abilityIndex); this.starterNatures.push(nature); this.starterMovesets.push(moveset); - if (this.speciesLoaded.get(species.speciesId)) { + if (this.speciesLoaded.get(species.speciesId) || randomSelection ) { getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene); } this.updateInstructions(); @@ -3001,7 +3114,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; - // We will only update the sprite if there is a change to form, shiny/variant // or gender for species with gender sprite differences const shouldUpdateSprite = (species?.genderDiffs && !isNullOrUndefined(female)) @@ -3431,7 +3543,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } tryUpdateValue(add?: integer, addingToParty?: boolean): boolean { - 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 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; From f83ab00ce472511f346e4c6d914484b41c16436f Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Sun, 12 Jan 2025 13:34:21 -0600 Subject: [PATCH 08/54] Add missing 'unused' comment before Max Moves --- src/data/move.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/move.ts b/src/data/move.ts index c86b168fb57..98fb58b2d73 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -10248,6 +10248,7 @@ export function initMoves() { .bitingMove(), new StatusMove(Moves.COURT_CHANGE, Type.NORMAL, 100, 10, -1, 0, 8) .attr(SwapArenaTagsAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.MIST, ArenaTagType.REFLECT, ArenaTagType.SPIKES, ArenaTagType.STEALTH_ROCK, ArenaTagType.STICKY_WEB, ArenaTagType.TAILWIND, ArenaTagType.TOXIC_SPIKES ]), + /* Unused */ new AttackMove(Moves.MAX_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) .unimplemented() From 0107b1d47ea4a898f39dd3534fcccd4241c03470 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:33:05 -0800 Subject: [PATCH 09/54] [Refactor] Create global scene variable (#4766) * Replace various `scene` pass-arounds with global scene variable * Modify tests * Add scene back to `fade[in|out]()` calls Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com> * Fix Bug Superfan ME test Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com> * Re-enable fixed test Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com> * Rename `gScene` to `globalScene` * Move `globalScene` to its own file to fix import/async issues * Fix `SelectModifierPhase` tests * Fix ME tests by removing `scene` from `expect()`s * Resolve merge issues * Remove tsdocs referencing `scene` params Remove missed instances of `.scene` * Remove unnecessary `globalScene` usage in `loading-scene.ts` * Fix merge conflicts * Attempt to fix circular import issue * Found the source of the import issue * Fix merge issues --------- Co-authored-by: Moka <54149968+MokaStitcher@users.noreply.github.com> --- eslint.config.js | 1 + src/@types/common.ts | 4 +- src/@types/i18next.d.ts | 2 +- src/battle-scene.ts | 223 ++++--- src/battle.ts | 109 +-- src/data/ability.ts | 353 +++++----- src/data/arena-tag.ts | 173 +++-- src/data/balance/biomes.ts | 3 +- src/data/balance/pokemon-evolutions.ts | 123 ++-- src/data/battle-anims.ts | 152 +++-- src/data/battler-tags.ts | 330 +++++----- src/data/berry.ts | 14 +- src/data/challenge.ts | 18 +- src/data/custom-pokemon-data.ts | 6 +- src/data/daily-run.ts | 23 +- src/data/egg-hatch-data.ts | 21 +- src/data/egg.ts | 110 ++-- src/data/move.ts | 573 ++++++++-------- .../encounters/a-trainers-test-encounter.ts | 36 +- .../encounters/absolute-avarice-encounter.ts | 138 ++-- .../an-offer-you-cant-refuse-encounter.ts | 45 +- .../encounters/berries-abound-encounter.ts | 130 ++-- .../encounters/bug-type-superfan-encounter.ts | 120 ++-- .../encounters/clowning-around-encounter.ts | 132 ++-- .../encounters/dancing-lessons-encounter.ts | 104 +-- .../encounters/dark-deal-encounter.ts | 37 +- .../encounters/delibirdy-encounter.ts | 154 ++--- .../department-store-sale-encounter.ts | 31 +- .../encounters/field-trip-encounter.ts | 95 +-- .../encounters/fiery-fallout-encounter.ts | 96 +-- .../encounters/fight-or-flight-encounter.ts | 74 ++- .../encounters/fun-and-games-encounter.ts | 199 +++--- .../global-trade-system-encounter.ts | 275 ++++---- .../encounters/lost-at-sea-encounter.ts | 31 +- .../mysterious-challengers-encounter.ts | 60 +- .../encounters/mysterious-chest-encounter.ts | 66 +- .../encounters/part-timer-encounter.ts | 180 ++--- .../encounters/safari-zone-encounter.ts | 241 +++---- .../shady-vitamin-dealer-encounter.ts | 78 +-- .../slumbering-snorlax-encounter.ts | 47 +- .../teleporting-hijinks-encounter.ts | 135 ++-- .../the-expert-pokemon-breeder-encounter.ts | 143 ++-- .../the-pokemon-salesman-encounter.ts | 39 +- .../encounters/the-strong-stuff-encounter.ts | 60 +- .../the-winstrate-challenge-encounter.ts | 171 ++--- .../encounters/training-session-encounter.ts | 132 ++-- .../encounters/trash-to-treasure-encounter.ts | 109 +-- .../encounters/uncommon-breed-encounter.ts | 84 +-- .../encounters/weird-dream-encounter.ts | 177 ++--- .../mystery-encounter-dialogue.ts | 2 +- .../mystery-encounter-option.ts | 48 +- .../mystery-encounter-requirements.ts | 214 +++--- .../mystery-encounter-save-data.ts | 4 +- .../mystery-encounters/mystery-encounter.ts | 127 ++-- .../mystery-encounters/mystery-encounters.ts | 2 +- .../can-learn-move-requirement.ts | 13 +- .../utils/encounter-dialogue-utils.ts | 39 +- .../utils/encounter-phase-utils.ts | 369 +++++------ .../utils/encounter-pokemon-utils.ts | 254 +++---- .../encounter-transformation-sequence.ts | 86 +-- src/data/pokeball.ts | 31 +- src/data/pokemon-forms.ts | 35 +- src/data/pokemon-species.ts | 59 +- src/data/status-effect.ts | 3 +- src/data/terrain.ts | 6 +- src/data/trainer-config.ts | 148 +++-- src/data/weather.ts | 18 +- src/events/arena.ts | 8 +- src/events/battle-scene.ts | 4 +- src/field/anims.ts | 80 +-- src/field/arena.ts | 117 ++-- src/field/damage-number-handler.ts | 23 +- src/field/mystery-encounter-intro.ts | 63 +- src/field/pokemon-sprite-sparkle-handler.ts | 8 +- src/field/pokemon.ts | 566 ++++++++-------- src/field/trainer.ts | 72 +- src/game-mode.ts | 29 +- src/global-scene.ts | 7 + src/inputs-controller.ts | 57 +- src/interfaces/held-modifier-config.ts | 4 +- src/messages.ts | 5 +- src/modifier/modifier-type.ts | 206 +++--- src/modifier/modifier.ts | 294 ++++----- src/overrides.ts | 20 +- src/phase.ts | 14 +- src/phases/add-enemy-buff-modifier-phase.ts | 14 +- src/phases/attempt-capture-phase.ts | 127 ++-- src/phases/attempt-run-phase.ts | 31 +- src/phases/battle-end-phase.ts | 42 +- src/phases/battle-phase.ts | 18 +- src/phases/berry-phase.ts | 15 +- src/phases/check-status-effect-phase.ts | 13 +- src/phases/check-switch-phase.ts | 28 +- src/phases/command-phase.ts | 190 +++--- src/phases/common-anim-phase.ts | 15 +- src/phases/damage-anim-phase.ts | 28 +- src/phases/egg-hatch-phase.ts | 123 ++-- src/phases/egg-lapse-phase.ts | 49 +- src/phases/egg-summary-phase.ts | 16 +- src/phases/encounter-phase.ts | 287 ++++---- src/phases/end-card-phase.ts | 26 +- src/phases/end-evolution-phase.ts | 8 +- src/phases/enemy-command-phase.ts | 16 +- .../enemy-party-member-pokemon-phase.ts | 7 +- src/phases/evolution-phase.ts | 140 ++-- src/phases/exp-phase.ts | 12 +- src/phases/faint-phase.ts | 84 +-- src/phases/field-phase.ts | 5 +- src/phases/form-change-phase.ts | 67 +- src/phases/game-over-modifier-reward-phase.ts | 20 +- src/phases/game-over-phase.ts | 186 +++--- src/phases/hide-party-exp-bar-phase.ts | 8 +- src/phases/learn-move-phase.ts | 69 +- src/phases/level-cap-phase.ts | 12 +- src/phases/level-up-phase.ts | 28 +- src/phases/login-phase.ts | 49 +- src/phases/message-phase.ts | 16 +- src/phases/modifier-reward-phase.ts | 15 +- src/phases/money-reward-phase.ts | 16 +- src/phases/move-anim-test-phase.ts | 16 +- src/phases/move-charge-phase.ts | 22 +- src/phases/move-effect-phase.ts | 53 +- src/phases/move-end-phase.ts | 10 +- src/phases/move-header-phase.ts | 8 +- src/phases/move-phase.ts | 62 +- src/phases/mystery-encounter-phases.ts | 272 ++++---- src/phases/new-battle-phase.ts | 3 +- src/phases/new-biome-encounter-phase.ts | 22 +- src/phases/next-encounter-phase.ts | 44 +- src/phases/obtain-status-effect-phase.ts | 16 +- src/phases/party-exp-phase.ts | 8 +- src/phases/party-heal-phase.ts | 22 +- src/phases/party-member-pokemon-phase.ts | 12 +- .../player-party-member-pokemon-phase.ts | 7 +- src/phases/pokemon-anim-phase.ts | 74 +-- src/phases/pokemon-heal-phase.ts | 29 +- src/phases/pokemon-phase.ts | 14 +- src/phases/post-game-over-phase.ts | 24 +- src/phases/post-summon-phase.ts | 14 +- src/phases/post-turn-status-effect-phase.ts | 18 +- src/phases/quiet-form-change-phase.ts | 40 +- src/phases/reload-session-phase.ts | 14 +- src/phases/return-phase.ts | 10 +- src/phases/ribbon-modifier-reward-phase.ts | 20 +- src/phases/scan-ivs-phase.ts | 34 +- src/phases/select-biome-phase.ts | 44 +- src/phases/select-challenge-phase.ts | 10 +- src/phases/select-gender-phase.ts | 24 +- src/phases/select-modifier-phase.ts | 136 ++-- src/phases/select-starter-phase.ts | 64 +- src/phases/select-target-phase.ts | 24 +- src/phases/shiny-sparkle-phase.ts | 10 +- src/phases/show-ability-phase.ts | 10 +- src/phases/show-party-exp-bar-phase.ts | 26 +- src/phases/show-trainer-phase.ts | 14 +- src/phases/stat-stage-change-phase.ts | 58 +- src/phases/summon-missing-phase.ts | 10 +- src/phases/summon-phase.ts | 115 ++-- src/phases/switch-biome-phase.ts | 54 +- src/phases/switch-phase.ts | 25 +- src/phases/switch-summon-phase.ts | 61 +- src/phases/test-message-phase.ts | 5 +- src/phases/title-phase.ts | 138 ++-- src/phases/toggle-double-position-phase.ts | 12 +- src/phases/trainer-message-test-phase.ts | 10 +- src/phases/trainer-victory-phase.ts | 50 +- src/phases/turn-end-phase.ts | 38 +- src/phases/turn-init-phase.ts | 39 +- src/phases/turn-start-phase.ts | 61 +- src/phases/unavailable-phase.ts | 10 +- src/phases/unlock-phase.ts | 21 +- src/phases/victory-phase.ts | 84 +-- src/phases/weather-effect-phase.ts | 20 +- src/pipelines/field-sprite.ts | 17 +- src/pipelines/invert.ts | 2 +- src/pipelines/sprite.ts | 12 +- src/system/achv.ts | 91 +-- src/system/arena-data.ts | 5 +- src/system/challenge-data.ts | 3 +- src/system/egg-data.ts | 8 +- src/system/game-data.ts | 288 ++++---- src/system/game-speed.ts | 11 +- src/system/modifier-data.ts | 15 +- src/system/pokemon-data.ts | 20 +- src/system/session-history.ts | 6 +- src/system/settings/settings-gamepad.ts | 30 +- src/system/settings/settings-keyboard.ts | 35 +- src/system/settings/settings.ts | 136 ++-- src/system/trainer-data.ts | 7 +- .../version_migration/version_converter.ts | 2 +- .../version_migration/versions/v1_0_4.ts | 3 +- src/system/voucher.ts | 9 +- src/test/abilities/aroma_veil.test.ts | 2 +- src/test/abilities/commander.test.ts | 3 +- src/test/abilities/dancer.test.ts | 2 +- src/test/abilities/gulp_missile.test.ts | 2 +- src/test/abilities/infiltrator.test.ts | 2 +- src/test/abilities/libero.test.ts | 3 +- src/test/abilities/protean.test.ts | 3 +- src/test/abilities/speed_boost.test.ts | 2 +- src/test/abilities/sturdy.test.ts | 2 +- src/test/abilities/unburden.test.ts | 2 +- src/test/achievements/achievement.test.ts | 32 +- src/test/battle/battle.test.ts | 4 +- src/test/battle/error-handling.test.ts | 45 -- src/test/battlerTags/octolock.test.ts | 27 +- src/test/battlerTags/stockpiling.test.ts | 61 +- src/test/battlerTags/substitute.test.ts | 80 +-- src/test/boss-pokemon.test.ts | 2 +- src/test/eggs/egg.test.ts | 28 +- src/test/eggs/manaphy-egg.test.ts | 6 +- src/test/enemy_command.test.ts | 9 +- src/test/escape-calculations.test.ts | 2 +- src/test/field/pokemon.test.ts | 2 +- src/test/game-mode.test.ts | 3 +- src/test/items/dire_hit.test.ts | 2 +- .../double_battle_chance_booster.test.ts | 2 +- src/test/items/exp_booster.test.ts | 2 +- src/test/items/grip_claw.test.ts | 4 +- src/test/items/light_ball.test.ts | 40 +- src/test/items/lock_capsule.test.ts | 2 +- src/test/items/metal_powder.test.ts | 24 +- src/test/items/quick_powder.test.ts | 24 +- .../items/temp_stat_stage_booster.test.ts | 2 +- src/test/items/thick_club.test.ts | 36 +- src/test/moves/aurora_veil.test.ts | 12 +- src/test/moves/burning_jealousy.test.ts | 3 +- src/test/moves/destiny_bond.test.ts | 3 +- src/test/moves/dragon_rage.test.ts | 2 +- src/test/moves/dynamax_cannon.test.ts | 10 +- src/test/moves/effectiveness.test.ts | 2 +- src/test/moves/fissure.test.ts | 2 +- src/test/moves/flame_burst.test.ts | 2 +- src/test/moves/geomancy.test.ts | 3 +- src/test/moves/light_screen.test.ts | 12 +- src/test/moves/order_up.test.ts | 3 +- src/test/moves/parting_shot.test.ts | 10 +- src/test/moves/purify.test.ts | 2 +- src/test/moves/reflect.test.ts | 12 +- src/test/moves/round.test.ts | 2 +- src/test/moves/spit_up.test.ts | 3 +- src/test/moves/steamroller.test.ts | 2 +- src/test/moves/stockpile.test.ts | 3 +- src/test/moves/substitute.test.ts | 2 +- src/test/moves/swallow.test.ts | 3 +- src/test/moves/tera_blast.test.ts | 2 +- src/test/moves/thunder_wave.test.ts | 2 +- src/test/moves/toxic_spikes.test.ts | 13 +- .../mystery-encounter/encounter-test-utils.ts | 14 +- .../a-trainers-test-encounter.test.ts | 6 +- .../absolute-avarice-encounter.test.ts | 2 +- ...an-offer-you-cant-refuse-encounter.test.ts | 10 +- .../berries-abound-encounter.test.ts | 12 +- .../bug-type-superfan-encounter.test.ts | 8 +- .../clowning-around-encounter.test.ts | 34 +- .../dancing-lessons-encounter.test.ts | 2 +- .../encounters/delibirdy-encounter.test.ts | 36 +- .../department-store-sale-encounter.test.ts | 2 +- .../encounters/field-trip-encounter.test.ts | 2 +- .../fiery-fallout-encounter.test.ts | 6 +- .../fight-or-flight-encounter.test.ts | 6 +- .../fun-and-games-encounter.test.ts | 4 +- .../global-trade-system-encounter.test.ts | 6 +- .../encounters/lost-at-sea-encounter.test.ts | 6 +- .../mysterious-challengers-encounter.test.ts | 6 +- .../encounters/part-timer-encounter.test.ts | 12 +- .../encounters/safari-zone.test.ts | 6 +- .../teleporting-hijinks-encounter.test.ts | 6 +- .../the-expert-breeder-encounter.test.ts | 6 +- .../the-pokemon-salesman-encounter.test.ts | 8 +- .../the-strong-stuff-encounter.test.ts | 6 +- .../the-winstrate-challenge-encounter.test.ts | 8 +- .../trash-to-treasure-encounter.test.ts | 6 +- .../uncommon-breed-encounter.test.ts | 12 +- .../encounters/weird-dream-encounter.test.ts | 6 +- .../mystery-encounter-utils.test.ts | 50 +- .../mystery-encounter.test.ts | 2 +- src/test/phases/form-change-phase.test.ts | 2 +- .../phases/mystery-encounter-phase.test.ts | 4 +- src/test/phases/phases.test.ts | 8 +- src/test/phases/select-modifier-phase.test.ts | 125 ++-- src/test/reload.test.ts | 4 +- .../settingMenu/rebinding_setting.test.ts | 2 +- src/test/system/game_data.test.ts | 10 +- src/test/ui/starter-select.test.ts | 10 +- src/test/ui/transfer-item.test.ts | 2 +- src/test/ui/type-hints.test.ts | 2 +- src/test/utils/gameManager.ts | 34 +- src/test/utils/gameManagerUtils.ts | 35 +- src/test/utils/helpers/challengeModeHelper.ts | 10 +- src/test/utils/helpers/classicModeHelper.ts | 6 +- src/test/utils/helpers/dailyModeHelper.ts | 4 +- src/test/utils/helpers/gameManagerHelper.ts | 2 +- src/test/utils/helpers/modifiersHelper.ts | 3 +- src/test/utils/helpers/moveHelper.ts | 4 +- src/test/utils/helpers/overridesHelper.ts | 16 +- src/test/utils/helpers/reloadHelper.ts | 10 +- src/test/utils/inputsHandler.ts | 6 +- src/test/utils/mocks/mockGameObjectCreator.ts | 2 +- src/test/utils/mocks/mockTextureManager.ts | 2 +- src/test/utils/mocks/mockVideoGameObject.ts | 2 +- .../mocks/mocksContainer/mockContainer.ts | 4 +- .../mocks/mocksContainer/mockGraphics.ts | 2 +- .../mocks/mocksContainer/mockRectangle.ts | 2 +- .../utils/mocks/mocksContainer/mockSprite.ts | 2 +- .../utils/mocks/mocksContainer/mockText.ts | 2 +- .../utils/mocks/mocksContainer/mockTexture.ts | 4 +- src/test/utils/phaseInterceptor.ts | 8 +- src/test/vitest.setup.ts | 2 + src/timed-event-manager.ts | 17 +- src/touch-controls.ts | 6 +- src/tutorial.ts | 75 +-- src/ui-inputs.ts | 74 +-- src/ui/ability-bar.ts | 20 +- src/ui/abstact-option-select-ui-handler.ts | 28 +- src/ui/achv-bar.ts | 32 +- src/ui/achvs-ui-handler.ts | 68 +- src/ui/admin-ui-handler.ts | 39 +- src/ui/arena-flyout.ts | 71 +- src/ui/autocomplete-ui-handler.ts | 5 +- src/ui/awaitable-ui-handler.ts | 10 +- src/ui/ball-ui-handler.ts | 34 +- src/ui/battle-flyout.ts | 38 +- src/ui/battle-info.ts | 144 ++-- src/ui/battle-message-ui-handler.ts | 53 +- src/ui/bgm-bar.ts | 16 +- src/ui/candy-bar.ts | 31 +- src/ui/challenges-select-ui-handler.ts | 111 ++-- src/ui/char-sprite.ts | 24 +- src/ui/command-ui-handler.ts | 24 +- src/ui/confirm-ui-handler.ts | 13 +- src/ui/daily-run-scoreboard.ts | 40 +- src/ui/dropdown.ts | 23 +- src/ui/egg-counter-container.ts | 29 +- src/ui/egg-gacha-ui-handler.ts | 154 ++--- src/ui/egg-hatch-scene-handler.ts | 22 +- src/ui/egg-list-ui-handler.ts | 58 +- src/ui/egg-summary-ui-handler.ts | 44 +- src/ui/evolution-scene-handler.ts | 20 +- src/ui/fight-ui-handler.ts | 60 +- src/ui/filter-bar.ts | 21 +- src/ui/form-modal-ui-handler.ts | 25 +- src/ui/game-stats-ui-handler.ts | 43 +- src/ui/hatched-pokemon-container.ts | 25 +- src/ui/loading-modal-ui-handler.ts | 9 +- src/ui/login-form-ui-handler.ts | 47 +- src/ui/menu-ui-handler.ts | 87 +-- src/ui/message-ui-handler.ts | 34 +- src/ui/modal-ui-handler.ts | 30 +- src/ui/modifier-select-ui-handler.ts | 184 +++--- src/ui/move-info-overlay.ts | 52 +- src/ui/mystery-encounter-ui-handler.ts | 102 +-- src/ui/party-exp-bar.ts | 26 +- src/ui/party-ui-handler.ts | 172 ++--- src/ui/pokeball-tray.ts | 34 +- src/ui/pokemon-hatch-info-container.ts | 42 +- src/ui/pokemon-icon-anim-handler.ts | 6 +- src/ui/pokemon-info-container.ts | 131 ++-- src/ui/registration-form-ui-handler.ts | 14 +- src/ui/rename-form-ui-handler.ts | 7 +- src/ui/run-history-ui-handler.ts | 100 ++- src/ui/run-info-ui-handler.ts | 230 +++---- src/ui/save-slot-select-ui-handler.ts | 78 ++- src/ui/saving-icon-handler.ts | 14 +- src/ui/scroll-bar.ts | 13 +- src/ui/scrollable-grid-handler.ts | 9 +- src/ui/session-reload-modal-ui-handler.ts | 12 +- .../settings/abstract-binding-ui-handler.ts | 25 +- .../abstract-control-settings-ui-handler.ts | 63 +- .../settings/abstract-settings-ui-handler.ts | 71 +- src/ui/settings/gamepad-binding-ui-handler.ts | 28 +- .../settings/keyboard-binding-ui-handler.ts | 24 +- .../settings/move-touch-controls-handler.ts | 18 +- src/ui/settings/navigationMenu.ts | 36 +- src/ui/settings/option-select-ui-handler.ts | 5 +- src/ui/settings/settings-audio-ui-handler.ts | 10 +- .../settings/settings-display-ui-handler.ts | 10 +- .../settings/settings-gamepad-ui-handler.ts | 19 +- .../settings/settings-keyboard-ui-handler.ts | 33 +- src/ui/settings/settings-ui-handler.ts | 8 +- src/ui/starter-container.ts | 31 +- src/ui/starter-select-ui-handler.ts | 618 +++++++++--------- src/ui/stats-container.ts | 25 +- src/ui/summary-ui-handler.ts | 227 +++---- src/ui/target-select-ui-handler.ts | 24 +- src/ui/test-dialogue-ui-handler.ts | 13 +- src/ui/text.ts | 30 +- src/ui/time-of-day-widget.ts | 27 +- src/ui/title-ui-handler.ts | 33 +- src/ui/ui-handler.ts | 22 +- src/ui/ui-theme.ts | 38 +- src/ui/ui.ts | 175 +++-- src/ui/unavailable-modal-ui-handler.ts | 17 +- 393 files changed, 9517 insertions(+), 9449 deletions(-) create mode 100644 src/global-scene.ts delete mode 100644 src/test/battle/error-handling.test.ts diff --git a/eslint.config.js b/eslint.config.js index 2f2b466c66f..1cea5563a78 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -46,6 +46,7 @@ export default [ "computed-property-spacing": ["error", "never" ], // Enforces consistent spacing inside computed property brackets "space-infix-ops": ["error", { "int32Hint": false }], // Enforces spacing around infix operators "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], // Disallows multiple empty lines + "@typescript-eslint/consistent-type-imports": "error", // Enforces type-only imports wherever possible } } ] diff --git a/src/@types/common.ts b/src/@types/common.ts index fcd946656dc..93d88a3b680 100644 --- a/src/@types/common.ts +++ b/src/@types/common.ts @@ -1,3 +1 @@ -import BattleScene from "#app/battle-scene"; - -export type ConditionFn = (scene: BattleScene, args?: any[]) => boolean; +export type ConditionFn = (args?: any[]) => boolean; diff --git a/src/@types/i18next.d.ts b/src/@types/i18next.d.ts index 3bd71bc6c61..0eaa1e6ff0f 100644 --- a/src/@types/i18next.d.ts +++ b/src/@types/i18next.d.ts @@ -1,4 +1,4 @@ -import { TOptions } from "i18next"; +import type { TOptions } from "i18next"; // Module declared to make referencing keys in the localization files type-safe. declare module "i18next" { diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ae992f5c00f..6cc33dc476d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1,13 +1,18 @@ import Phaser from "phaser"; import UI from "#app/ui/ui"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; -import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import type { Constructor } from "#app/utils"; +import { isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier } from "./modifier/modifier"; import { PokeballType } from "#enums/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; -import { Phase } from "#app/phase"; +import type { Phase } from "#app/phase"; import { initGameSpeed } from "#app/system/game-speed"; import { Arena, ArenaBase } from "#app/field/arena"; import { GameData } from "#app/system/game-data"; @@ -17,26 +22,32 @@ import { MusicPreference } from "#app/system/settings/settings"; import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import AbilityBar from "#app/ui/ability-bar"; import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr, PostItemLostAbAttr } from "#app/data/ability"; -import Battle, { BattleType, FixedBattleConfig } from "#app/battle"; -import { GameMode, GameModes, getGameMode } from "#app/game-mode"; +import type { FixedBattleConfig } from "#app/battle"; +import Battle, { BattleType } from "#app/battle"; +import type { GameMode } from "#app/game-mode"; +import { GameModes, getGameMode } from "#app/game-mode"; import FieldSpritePipeline from "#app/pipelines/field-sprite"; import SpritePipeline from "#app/pipelines/sprite"; import PartyExpBar from "#app/ui/party-exp-bar"; -import { trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; +import type { TrainerSlot } from "#app/data/trainer-config"; +import { trainerConfigs } from "#app/data/trainer-config"; import Trainer, { TrainerVariant } from "#app/field/trainer"; -import TrainerData from "#app/system/trainer-data"; +import type TrainerData from "#app/system/trainer-data"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import PokeballTray from "#app/ui/pokeball-tray"; import InvertPostFX from "#app/pipelines/invert"; -import { Achv, achvs, ModifierAchv, MoneyAchv } from "#app/system/achv"; -import { Voucher, vouchers } from "#app/system/voucher"; +import type { Achv } from "#app/system/achv"; +import { achvs, ModifierAchv, MoneyAchv } from "#app/system/achv"; +import type { Voucher } from "#app/system/voucher"; +import { vouchers } from "#app/system/voucher"; import { Gender } from "#app/data/gender"; -import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; +import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "#app/ui/ui-theme"; -import PokemonData from "#app/system/pokemon-data"; +import type PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; +import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; +import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger } from "#app/data/pokemon-forms"; import { FormChangePhase } from "#app/phases/form-change-phase"; import { getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; @@ -47,8 +58,9 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { biomeDepths, getBiomeName } from "#app/data/balance/biomes"; import { SceneBase } from "#app/scene-base"; import CandyBar from "#app/ui/candy-bar"; -import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant"; -import { Localizable } from "#app/interfaces/locales"; +import type { Variant, VariantSet } from "#app/data/variant"; +import { variantColorCache, variantData } from "#app/data/variant"; +import type { Localizable } from "#app/interfaces/locales"; import Overrides from "#app/overrides"; import { InputsController } from "#app/inputs-controller"; import { UiInputs } from "#app/ui-inputs"; @@ -58,14 +70,14 @@ import { EaseType } from "#enums/ease-type"; import { BattleSpec } from "#enums/battle-spec"; import { BattleStyle } from "#enums/battle-style"; import { Biome } from "#enums/biome"; -import { ExpNotification } from "#enums/exp-notification"; +import type { ExpNotification } from "#enums/exp-notification"; import { MoneyFormat } from "#enums/money-format"; import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { UiTheme } from "#enums/ui-theme"; import { TimedEventManager } from "#app/timed-event-manager"; -import { PokemonAnimType } from "#enums/pokemon-anim-type"; +import type { PokemonAnimType } from "#enums/pokemon-anim-type"; import i18next from "i18next"; import { TrainerType } from "#enums/trainer-type"; import { battleSpecDialogue } from "#app/data/dialogue"; @@ -92,7 +104,7 @@ import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { ExpPhase } from "#app/phases/exp-phase"; import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -100,6 +112,7 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; +import { globalScene, initGlobalScene } from "#app/global-scene"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -330,6 +343,7 @@ export default class BattleScene extends SceneBase { this.nextCommandPhaseQueue = []; this.eventManager = new TimedEventManager(); this.updateGameInfo(); + initGlobalScene(this); } loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { @@ -375,14 +389,13 @@ export default class BattleScene extends SceneBase { async preload() { if (DEBUG_RNG) { - const scene = this; const originalRealInRange = Phaser.Math.RND.realInRange; Phaser.Math.RND.realInRange = function (min: number, max: number): number { const ret = originalRealInRange.apply(this, [ min, max ]); - const args = [ "RNG", ++scene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; - args.push(`seed: ${scene.rngSeedOverride || scene.waveSeed || scene.seed}`); - if (scene.rngOffset) { - args.push(`offset: ${scene.rngOffset}`); + const args = [ "RNG", ++globalScene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; + args.push(`seed: ${globalScene.rngSeedOverride || globalScene.waveSeed || globalScene.seed}`); + if (globalScene.rngOffset) { + args.push(`offset: ${globalScene.rngOffset}`); } console.log(...args); return ret; @@ -395,14 +408,14 @@ export default class BattleScene extends SceneBase { } create() { - this.scene.remove(LoadingScene.KEY); + globalScene.scene.remove(LoadingScene.KEY); initGameSpeed.apply(this); - this.inputController = new InputsController(this); - this.uiInputs = new UiInputs(this, this.inputController); + this.inputController = new InputsController(); + this.uiInputs = new UiInputs(this.inputController); - this.gameData = new GameData(this); + this.gameData = new GameData(); - addUiThemeOverrides(this); + addUiThemeOverrides(); this.load.setBaseURL(); @@ -489,76 +502,76 @@ export default class BattleScene extends SceneBase { this.modifiers = []; this.enemyModifiers = []; - this.modifierBar = new ModifierBar(this); + this.modifierBar = new ModifierBar(); this.modifierBar.setName("modifier-bar"); this.add.existing(this.modifierBar); uiContainer.add(this.modifierBar); - this.enemyModifierBar = new ModifierBar(this, true); + this.enemyModifierBar = new ModifierBar(true); this.enemyModifierBar.setName("enemy-modifier-bar"); this.add.existing(this.enemyModifierBar); uiContainer.add(this.enemyModifierBar); - this.charSprite = new CharSprite(this); + this.charSprite = new CharSprite(); this.charSprite.setName("sprite-char"); this.charSprite.setup(); this.fieldUI.add(this.charSprite); - this.pbTray = new PokeballTray(this, true); + this.pbTray = new PokeballTray(true); this.pbTray.setName("pb-tray"); this.pbTray.setup(); - this.pbTrayEnemy = new PokeballTray(this, false); + this.pbTrayEnemy = new PokeballTray(false); this.pbTrayEnemy.setName("enemy-pb-tray"); this.pbTrayEnemy.setup(); this.fieldUI.add(this.pbTray); this.fieldUI.add(this.pbTrayEnemy); - this.abilityBar = new AbilityBar(this); + this.abilityBar = new AbilityBar(); this.abilityBar.setName("ability-bar"); this.abilityBar.setup(); this.fieldUI.add(this.abilityBar); - this.partyExpBar = new PartyExpBar(this); + this.partyExpBar = new PartyExpBar(); this.partyExpBar.setName("party-exp-bar"); this.partyExpBar.setup(); this.fieldUI.add(this.partyExpBar); - this.candyBar = new CandyBar(this); + this.candyBar = new CandyBar(); this.candyBar.setName("candy-bar"); this.candyBar.setup(); this.fieldUI.add(this.candyBar); - this.biomeWaveText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); + this.biomeWaveText = addTextObject((this.game.canvas.width / 6) - 2, 0, startingWave.toString(), TextStyle.BATTLE_INFO); this.biomeWaveText.setName("text-biome-wave"); this.biomeWaveText.setOrigin(1, 0.5); this.fieldUI.add(this.biomeWaveText); - this.moneyText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY); + this.moneyText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.MONEY); this.moneyText.setName("text-money"); this.moneyText.setOrigin(1, 0.5); this.fieldUI.add(this.moneyText); - this.scoreText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.scoreText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.scoreText.setName("text-score"); this.scoreText.setOrigin(1, 0.5); this.fieldUI.add(this.scoreText); - this.luckText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); + this.luckText = addTextObject((this.game.canvas.width / 6) - 2, 0, "", TextStyle.PARTY, { fontSize: "54px" }); this.luckText.setName("text-luck"); this.luckText.setOrigin(1, 0.5); this.luckText.setVisible(false); this.fieldUI.add(this.luckText); - this.luckLabelText = addTextObject(this, (this.game.canvas.width / 6) - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, { fontSize: "54px" }); + this.luckLabelText = addTextObject((this.game.canvas.width / 6) - 2, 0, i18next.t("common:luckIndicator"), TextStyle.PARTY, { fontSize: "54px" }); this.luckLabelText.setName("text-luck-label"); this.luckLabelText.setOrigin(1, 0.5); this.luckLabelText.setVisible(false); this.fieldUI.add(this.luckLabelText); - this.arenaFlyout = new ArenaFlyout(this); + this.arenaFlyout = new ArenaFlyout(); this.fieldUI.add(this.arenaFlyout); this.fieldUI.moveBelow(this.arenaFlyout, this.fieldOverlay); @@ -567,9 +580,9 @@ export default class BattleScene extends SceneBase { this.damageNumberHandler = new DamageNumberHandler(); this.spriteSparkleHandler = new PokemonSpriteSparkleHandler(); - this.spriteSparkleHandler.setup(this); + this.spriteSparkleHandler.setup(); - this.pokemonInfoContainer = new PokemonInfoContainer(this, (this.game.canvas.width / 6) + 52, -(this.game.canvas.height / 6) + 66); + this.pokemonInfoContainer = new PokemonInfoContainer((this.game.canvas.width / 6) + 52, -(this.game.canvas.height / 6) + 66); this.pokemonInfoContainer.setup(); this.fieldUI.add(this.pokemonInfoContainer); @@ -578,13 +591,13 @@ export default class BattleScene extends SceneBase { const loadPokemonAssets = []; - this.arenaPlayer = new ArenaBase(this, true); + this.arenaPlayer = new ArenaBase(true); this.arenaPlayer.setName("arena-player"); - this.arenaPlayerTransition = new ArenaBase(this, true); + this.arenaPlayerTransition = new ArenaBase(true); this.arenaPlayerTransition.setName("arena-player-transition"); - this.arenaEnemy = new ArenaBase(this, false); + this.arenaEnemy = new ArenaBase(false); this.arenaEnemy.setName("arena-enemy"); - this.arenaNextEnemy = new ArenaBase(this, false); + this.arenaNextEnemy = new ArenaBase(false); this.arenaNextEnemy.setName("arena-next-enemy"); this.arenaBgTransition.setVisible(false); @@ -625,7 +638,7 @@ export default class BattleScene extends SceneBase { this.reset(false, false, true); - const ui = new UI(this); + const ui = new UI(); this.uiContainer.add(ui); this.ui = ui; @@ -636,12 +649,12 @@ export default class BattleScene extends SceneBase { Promise.all([ Promise.all(loadPokemonAssets), - initCommonAnims(this).then(() => loadCommonAnimAssets(this, true)), - Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(this, m))).then(() => loadMoveAnimAssets(this, defaultMoves, true)), + initCommonAnims().then(() => loadCommonAnimAssets(true)), + Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(defaultMoves, true)), this.initStarterColors() ]).then(() => { - this.pushPhase(new LoginPhase(this)); - this.pushPhase(new TitlePhase(this)); + this.pushPhase(new LoginPhase()); + this.pushPhase(new TitlePhase()); this.shiftPhase(); }); @@ -911,7 +924,7 @@ export default class BattleScene extends SceneBase { } addPlayerPokemon(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData, postProcess?: (playerPokemon: PlayerPokemon) => void): PlayerPokemon { - const pokemon = new PlayerPokemon(this, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); + const pokemon = new PlayerPokemon(species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (postProcess) { postProcess(pokemon); } @@ -929,7 +942,7 @@ export default class BattleScene extends SceneBase { boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1; } - const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource); + const pokemon = new EnemyPokemon(species, level, trainerSlot, boss, shinyLock, dataSource); if (Overrides.OPP_FUSION_OVERRIDE) { pokemon.generateFusionSpecies(); } @@ -1064,7 +1077,7 @@ export default class BattleScene extends SceneBase { /** * Generates a random number using the current battle's seed * - * This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts` + * This calls {@linkcode Battle.randSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle.ts` * which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` * * @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min} @@ -1072,12 +1085,12 @@ export default class BattleScene extends SceneBase { * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ randBattleSeedInt(range: integer, min: integer = 0): integer { - return this.currentBattle?.randSeedInt(this, range, min); + return this.currentBattle?.randSeedInt(range, min); } reset(clearScene: boolean = false, clearData: boolean = false, reloadI18n: boolean = false): void { if (clearData) { - this.gameData = new GameData(this); + this.gameData = new GameData(); } this.gameMode = getGameMode(GameModes.CLASSIC); @@ -1210,7 +1223,7 @@ export default class BattleScene extends SceneBase { battleConfig = this.gameMode.getFixedBattle(newWaveIndex); newDouble = battleConfig.double; newBattleType = battleConfig.battleType; - this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(this), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8); + this.executeWithSeedOffset(() => newTrainer = battleConfig?.getTrainer(), (battleConfig.seedOffsetWaveIndex || newWaveIndex) << 8); if (newTrainer) { this.field.add(newTrainer); } @@ -1236,7 +1249,7 @@ export default class BattleScene extends SceneBase { } } const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT); - newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant); + newTrainer = trainerData !== undefined ? trainerData.toTrainer() : new Trainer(trainerType, variant); this.field.add(newTrainer); } @@ -1314,7 +1327,7 @@ export default class BattleScene extends SceneBase { this.executeWithSeedOffset(() => { this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); }, newWaveIndex << 3, this.waveSeed); - this.currentBattle.incrementTurn(this); + this.currentBattle.incrementTurn(); if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { // Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome @@ -1342,7 +1355,7 @@ export default class BattleScene extends SceneBase { playerField.forEach((pokemon, p) => { if (pokemon.isOnField()) { - this.pushPhase(new ReturnPhase(this, p)); + this.pushPhase(new ReturnPhase(p)); } }); @@ -1352,7 +1365,7 @@ export default class BattleScene extends SceneBase { } if (!this.trainer.visible) { - this.pushPhase(new ShowTrainerPhase(this)); + this.pushPhase(new ShowTrainerPhase()); } } @@ -1361,14 +1374,14 @@ export default class BattleScene extends SceneBase { } if (!this.gameMode.hasRandomBiomes && !isNewBiome) { - this.pushPhase(new NextEncounterPhase(this)); + this.pushPhase(new NextEncounterPhase()); } else { - this.pushPhase(new SelectBiomePhase(this)); - this.pushPhase(new NewBiomeEncounterPhase(this)); + this.pushPhase(new SelectBiomePhase()); + this.pushPhase(new NewBiomeEncounterPhase()); const newMaxExpLevel = this.getMaxExpLevel(); if (newMaxExpLevel > maxExpLevel) { - this.pushPhase(new LevelCapPhase(this)); + this.pushPhase(new LevelCapPhase()); } } } @@ -1377,7 +1390,7 @@ export default class BattleScene extends SceneBase { } newArena(biome: Biome): Arena { - this.arena = new Arena(this, biome, Biome[biome].toLowerCase()); + this.arena = new Arena(biome, Biome[biome].toLowerCase()); this.eventTarget.dispatchEvent(new NewArenaEvent()); this.arenaBg.pipelineData = { terrainColorRatio: this.arena.getBgTerrainColorRatioForBiome() }; @@ -1793,7 +1806,7 @@ export default class BattleScene extends SceneBase { } updateUIPositions(): void { - const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible(this)).length; + const enemyModifierCount = this.enemyModifiers.filter(m => m.isIconVisible()).length; const biomeWaveTextHeight = this.biomeWaveText.getBottomLeft().y - this.biomeWaveText.getTopLeft().y; this.biomeWaveText.setY( -(this.game.canvas.height / 6) + (enemyModifierCount ? enemyModifierCount <= 12 ? 15 : 24 : 0) + (biomeWaveTextHeight / 2) @@ -1880,7 +1893,7 @@ export default class BattleScene extends SceneBase { playBgm(bgmName?: string, fadeOut?: boolean): void { if (bgmName === undefined) { - bgmName = this.currentBattle?.getBgmOverride(this) || this.arena?.bgm; + bgmName = this.currentBattle?.getBgmOverride() || this.arena?.bgm; } if (this.bgm && bgmName === this.bgm.key) { if (!this.bgm.isPlaying) { @@ -2499,7 +2512,7 @@ export default class BattleScene extends SceneBase { * @param defer boolean for which queue to add it to, false -> add to PhaseQueuePrepend, true -> nextCommandPhaseQueue */ queueMessage(message: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, defer?: boolean | null) { - const phase = new MessagePhase(this, message, callbackDelay, prompt, promptDelay); + const phase = new MessagePhase(message, callbackDelay, prompt, promptDelay); if (!defer) { // adds to the end of PhaseQueuePrepend this.unshiftPhase(phase); @@ -2517,7 +2530,7 @@ export default class BattleScene extends SceneBase { this.phaseQueue.push(...this.nextCommandPhaseQueue); this.nextCommandPhaseQueue.splice(0, this.nextCommandPhaseQueue.length); } - this.phaseQueue.push(new TurnInitPhase(this)); + this.phaseQueue.push(new TurnInitPhase()); } addMoney(amount: integer): void { @@ -2548,7 +2561,7 @@ export default class BattleScene extends SceneBase { if (modifier instanceof TerastallizeModifier) { modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId))); } - if ((modifier as PersistentModifier).add(this.modifiers, !!virtual, this)) { + if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { @@ -2629,7 +2642,7 @@ export default class BattleScene extends SceneBase { if (modifier instanceof TerastallizeModifier) { modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId, false))); } - if ((modifier as PersistentModifier).add(this.enemyModifiers, false, this)) { + if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { const pokemon = this.getPokemonById(modifier.pokemonId); if (pokemon) { @@ -2664,7 +2677,7 @@ export default class BattleScene extends SceneBase { */ tryTransferHeldItemModifier(itemModifier: PokemonHeldItemModifier, target: Pokemon, playSound: boolean, transferQuantity: number = 1, instant?: boolean, ignoreUpdate?: boolean, itemLost: boolean = true): Promise { return new Promise(resolve => { - const source = itemModifier.pokemonId ? itemModifier.getPokemon(target.scene) : null; + const source = itemModifier.pokemonId ? itemModifier.getPokemon() : null; const cancelled = new Utils.BooleanHolder(false); Utils.executeIf(!!source && source.isPlayer() !== target.isPlayer(), () => applyAbAttrs(BlockItemTheftAbAttr, source! /* checked in condition*/, cancelled)).then(() => { if (cancelled.value) { @@ -2672,11 +2685,11 @@ export default class BattleScene extends SceneBase { } const newItemModifier = itemModifier.clone() as PokemonHeldItemModifier; newItemModifier.pokemonId = target.id; - const matchingModifier = target.scene.findModifier(m => m instanceof PokemonHeldItemModifier + const matchingModifier = this.findModifier(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).matchType(itemModifier) && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier; let removeOld = true; if (matchingModifier) { - const maxStackCount = matchingModifier.getMaxStackCount(target.scene); + const maxStackCount = matchingModifier.getMaxStackCount(); if (matchingModifier.stackCount >= maxStackCount) { return resolve(false); } @@ -2793,7 +2806,7 @@ export default class BattleScene extends SceneBase { count = Math.max(count, Math.floor(chances / 2)); } getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) - .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); + .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false)); } return true; }); @@ -2817,7 +2830,7 @@ export default class BattleScene extends SceneBase { * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} */ clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon)); + const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon() === pokemon)); for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } @@ -2866,9 +2879,7 @@ export default class BattleScene extends SceneBase { updatePartyForModifiers(party: Pokemon[], instant?: boolean): Promise { return new Promise(resolve => { Promise.allSettled(party.map(p => { - if (p.scene) { - p.calculateStats(); - } + p.calculateStats(); return p.updateInfo(instant); })).then(() => resolve()); }); @@ -2931,15 +2942,14 @@ export default class BattleScene extends SceneBase { /** * Apply all modifiers that match `modifierType` in a random order - * @param scene {@linkcode BattleScene} used to randomize the order of modifiers * @param modifierType The type of modifier to apply; must extend {@linkcode PersistentModifier} * @param player Whether to search the player (`true`) or the enemy (`false`); Defaults to `true` * @param ...args The list of arguments needed to invoke `modifierType.apply` * @returns the list of all modifiers that matched `modifierType` and were applied. */ - applyShuffledModifiers(scene: BattleScene, modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { + applyShuffledModifiers(modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const shuffleModifiers = mods => { if (mods.length < 1) { return mods; @@ -2948,7 +2958,7 @@ export default class BattleScene extends SceneBase { return [ mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand)) ]; }; modifiers = shuffleModifiers(modifiers); - }, scene.currentBattle.turn << 4, scene.waveSeed); + }, globalScene.currentBattle.turn << 4, globalScene.waveSeed); return this.applyModifiersInternal(modifiers, player, args); } @@ -3018,9 +3028,9 @@ export default class BattleScene extends SceneBase { if (matchingFormChange) { let phase: Phase; if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet) { - phase = new FormChangePhase(this, pokemon, matchingFormChange, modal); + phase = new FormChangePhase(pokemon, matchingFormChange, modal); } else { - phase = new QuietFormChangePhase(this, pokemon, matchingFormChange); + phase = new QuietFormChangePhase(pokemon, matchingFormChange); } if (pokemon instanceof PlayerPokemon && !matchingFormChange.quiet && modal) { this.overridePhase(phase); @@ -3037,7 +3047,7 @@ export default class BattleScene extends SceneBase { } triggerPokemonBattleAnim(pokemon: Pokemon, battleAnimType: PokemonAnimType, fieldAssets?: Phaser.GameObjects.Sprite[], delayed: boolean = false): boolean { - const phase: Phase = new PokemonAnimPhase(this, battleAnimType, pokemon, fieldAssets); + const phase: Phase = new PokemonAnimPhase(battleAnimType, pokemon, fieldAssets); if (delayed) { this.pushPhase(phase); } else { @@ -3055,7 +3065,7 @@ export default class BattleScene extends SceneBase { validateAchv(achv: Achv, args?: unknown[]): boolean { if ((!this.gameData.achvUnlocks.hasOwnProperty(achv.id) || Overrides.ACHIEVEMENTS_REUNLOCK_OVERRIDE) - && achv.validate(this, args)) { + && achv.validate(args)) { this.gameData.achvUnlocks[achv.id] = new Date().getTime(); this.ui.achvBar.showAchv(achv); if (vouchers.hasOwnProperty(achv.id)) { @@ -3068,7 +3078,7 @@ export default class BattleScene extends SceneBase { } validateVoucher(voucher: Voucher, args?: unknown[]): boolean { - if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(this, args)) { + if (!this.gameData.voucherUnlocks.hasOwnProperty(voucher.id) && voucher.validate(args)) { this.gameData.voucherUnlocks[voucher.id] = new Date().getTime(); this.ui.achvBar.showAchv(voucher); this.gameData.voucherCounts[voucher.voucherType]++; @@ -3141,9 +3151,9 @@ export default class BattleScene extends SceneBase { this.currentBattle.double = true; const availablePartyMembers = this.getPlayerParty().filter((p) => p.isAllowedInBattle()); if (availablePartyMembers.length > 1) { - this.pushPhase(new ToggleDoublePositionPhase(this, true)); + this.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - this.pushPhase(new SummonPhase(this, 1)); + this.pushPhase(new SummonPhase(1)); } } @@ -3188,7 +3198,7 @@ export default class BattleScene extends SceneBase { if (participated && pokemonDefeated) { partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); - if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) { + if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount()) { machoBraceModifier.stackCount++; this.updateModifiers(true, true); partyMember.updateInfo(); @@ -3250,7 +3260,7 @@ export default class BattleScene extends SceneBase { if (exp) { const partyMemberIndex = party.indexOf(expPartyMembers[pm]); - this.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(this, partyMemberIndex, exp) : new ShowPartyExpBarPhase(this, partyMemberIndex, exp)); + this.unshiftPhase(expPartyMembers[pm].isOnField() ? new ExpPhase(partyMemberIndex, exp) : new ShowPartyExpBarPhase(partyMemberIndex, exp)); } } } @@ -3342,7 +3352,7 @@ export default class BattleScene extends SceneBase { if (encounter) { encounter = new MysteryEncounter(encounter); - encounter.populateDialogueTokensFromRequirements(this); + encounter.populateDialogueTokensFromRequirements(); return encounter; } @@ -3370,8 +3380,9 @@ export default class BattleScene extends SceneBase { } let availableEncounters: MysteryEncounter[] = []; - // New encounter should never be the same as the most recent encounter - const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ? this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type : null; + const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ? + this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type + : null; const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? []; // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available while (availableEncounters.length === 0 && tier !== null) { @@ -3381,27 +3392,27 @@ export default class BattleScene extends SceneBase { if (!encounterCandidate) { return false; } - if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier + if (encounterCandidate.encounterTier !== tier) { return false; } const disallowedGameModes = encounterCandidate.disallowedGameModes; if (disallowedGameModes && disallowedGameModes.length > 0 - && disallowedGameModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode + && disallowedGameModes.includes(this.gameMode.modeId)) { return false; } - if (this.gameMode.modeId === GameModes.CHALLENGE) { // Encounter is enabled for challenges + if (this.gameMode.modeId === GameModes.CHALLENGE) { const disallowedChallenges = encounterCandidate.disallowedChallenges; if (disallowedChallenges && disallowedChallenges.length > 0 && this.gameMode.challenges.some(challenge => disallowedChallenges.includes(challenge.id))) { return false; } } - if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements + if (!encounterCandidate.meetsRequirements()) { return false; } - if (previousEncounter !== null && encounterType === previousEncounter) { // Previous encounter was not this one + if (previousEncounter !== null && encounterType === previousEncounter) { return false; } - if (this.mysteryEncounterSaveData.encounteredEvents.length > 0 && // Encounter has not exceeded max allowed encounters + if (this.mysteryEncounterSaveData.encounteredEvents.length > 0 && (encounterCandidate.maxAllowedEncounters && encounterCandidate.maxAllowedEncounters > 0) && this.mysteryEncounterSaveData.encounteredEvents.filter(e => e.type === encounterType).length >= encounterCandidate.maxAllowedEncounters) { return false; @@ -3429,7 +3440,7 @@ export default class BattleScene extends SceneBase { encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)]; // New encounter object to not dirty flags encounter = new MysteryEncounter(encounter); - encounter.populateDialogueTokensFromRequirements(this); + encounter.populateDialogueTokensFromRequirements(); return encounter; } } diff --git a/src/battle.ts b/src/battle.ts index 38ee7b5eae0..6dae845bfe1 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -1,26 +1,27 @@ -import BattleScene from "./battle-scene"; -import { Command } from "./ui/command-ui-handler"; +import { globalScene } from "#app/global-scene"; +import type { Command } from "./ui/command-ui-handler"; import * as Utils from "./utils"; import Trainer, { TrainerVariant } from "./field/trainer"; -import { GameMode } from "./game-mode"; +import type { GameMode } from "./game-mode"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; -import { PokeballType } from "#enums/pokeball"; +import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import Pokemon, { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { MusicPreference } from "#app/system/settings/settings"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import i18next from "#app/plugins/i18n"; -import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; -import { CustomModifierSettings } from "#app/modifier/modifier-type"; +import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTier } from "#app/modifier/modifier-tier"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; export enum ClassicFixedBossWaves { // TODO: other fixed wave battles should be added here @@ -154,7 +155,7 @@ export default class Battle { return this.double ? 2 : 1; } - incrementTurn(scene: BattleScene): void { + incrementTurn(): void { this.turn++; this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); this.battleSeedState = null; @@ -169,7 +170,7 @@ export default class Battle { } addPostBattleLoot(enemyPokemon: EnemyPokemon): void { - this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => { + this.postBattleLoot.push(...globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => { const ret = i as PokemonHeldItemModifier; //@ts-ignore - this is awful to fix/change ret.pokemonId = null; @@ -177,43 +178,43 @@ export default class Battle { })); } - pickUpScatteredMoney(scene: BattleScene): void { - const moneyAmount = new Utils.IntegerHolder(scene.currentBattle.moneyScattered); - scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + pickUpScatteredMoney(): void { + const moneyAmount = new Utils.IntegerHolder(globalScene.currentBattle.moneyScattered); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - if (scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { + if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; } - scene.addMoney(moneyAmount.value); + globalScene.addMoney(moneyAmount.value); const userLocale = navigator.language || "en-US"; const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount }); - scene.queueMessage(message, undefined, true); + globalScene.queueMessage(message, undefined, true); - scene.currentBattle.moneyScattered = 0; + globalScene.currentBattle.moneyScattered = 0; } - addBattleScore(scene: BattleScene): void { - let partyMemberTurnMultiplier = scene.getEnemyParty().length / 2 + 0.5; + addBattleScore(): void { + let partyMemberTurnMultiplier = globalScene.getEnemyParty().length / 2 + 0.5; if (this.double) { partyMemberTurnMultiplier /= 1.5; } - for (const p of scene.getEnemyParty()) { + for (const p of globalScene.getEnemyParty()) { if (p.isBoss()) { - partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / scene.getEnemyParty().length; + partyMemberTurnMultiplier *= (p.bossSegments / 1.5) / globalScene.getEnemyParty().length; } } const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier)); const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier); - scene.score += finalBattleScore; + globalScene.score += finalBattleScore; console.log(`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`); - console.log(`Total Score: ${scene.score}`); - scene.updateScoreText(); + console.log(`Total Score: ${globalScene.score}`); + globalScene.updateScoreText(); } - getBgmOverride(scene: BattleScene): string | null { + getBgmOverride(): string | null { if (this.isBattleMysteryEncounter() && this.mysteryEncounter?.encounterMode === MysteryEncounterMode.DEFAULT) { // Music is overridden for MEs during ME onInit() // Should not use any BGM overrides before swapping from DEFAULT mode @@ -222,7 +223,7 @@ export default class Battle { if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) { return `encounter_${this.trainer?.getEncounterBgm()}`; } - if (scene.musicPreference === MusicPreference.GENFIVE) { + if (globalScene.musicPreference === MusicPreference.GENFIVE) { return this.trainer?.getBattleBgm() ?? null; } else { return this.trainer?.getMixedBattleBgm() ?? null; @@ -230,7 +231,7 @@ export default class Battle { } else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) { return "end_summit"; } - const wildOpponents = scene.getEnemyParty(); + const wildOpponents = globalScene.getEnemyParty(); for (const pokemon of wildOpponents) { if (this.battleSpec === BattleSpec.FINAL_BOSS) { if (pokemon.species.getFormSpriteKey(pokemon.formIndex) === SpeciesFormKey.ETERNAMAX) { @@ -239,7 +240,7 @@ export default class Battle { return "battle_final_encounter"; } if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) { - if (scene.musicPreference === MusicPreference.GENFIVE) { + if (globalScene.musicPreference === MusicPreference.GENFIVE) { switch (pokemon.species.speciesId) { case Species.REGIROCK: case Species.REGICE: @@ -256,7 +257,7 @@ export default class Battle { } return "battle_legendary_unova"; } - } else if (scene.musicPreference === MusicPreference.ALLGENS) { + } else if (globalScene.musicPreference === MusicPreference.ALLGENS) { switch (pokemon.species.speciesId) { case Species.ARTICUNO: case Species.ZAPDOS: @@ -396,7 +397,7 @@ export default class Battle { } } - if (scene.gameMode.isClassic && this.waveIndex <= 4) { + if (globalScene.gameMode.isClassic && this.waveIndex <= 4) { return "battle_wild"; } @@ -409,12 +410,12 @@ export default class Battle { * @param min The minimum integer to pick, default `0` * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ - randSeedInt(scene: BattleScene, range: number, min: number = 0): number { + randSeedInt(range: number, min: number = 0): number { if (range <= 1) { return min; } - const tempRngCounter = scene.rngCounter; - const tempSeedOverride = scene.rngSeedOverride; + const tempRngCounter = globalScene.rngCounter; + const tempSeedOverride = globalScene.rngSeedOverride; const state = Phaser.Math.RND.state(); if (this.battleSeedState) { Phaser.Math.RND.state(this.battleSeedState); @@ -422,13 +423,13 @@ export default class Battle { Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]); console.log("Battle Seed:", this.battleSeed); } - scene.rngCounter = this.rngCounter++; - scene.rngSeedOverride = this.battleSeed; + globalScene.rngCounter = this.rngCounter++; + globalScene.rngSeedOverride = this.battleSeed; const ret = Utils.randSeedInt(range, min); this.battleSeedState = Phaser.Math.RND.state(); Phaser.Math.RND.state(state); - scene.rngCounter = tempRngCounter; - scene.rngSeedOverride = tempSeedOverride; + globalScene.rngCounter = tempRngCounter; + globalScene.rngSeedOverride = tempSeedOverride; return ret; } @@ -441,16 +442,16 @@ export default class Battle { } export class FixedBattle extends Battle { - constructor(scene: BattleScene, waveIndex: number, config: FixedBattleConfig) { - super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double); + constructor(waveIndex: number, config: FixedBattleConfig) { + super(globalScene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer() : undefined, config.double); if (config.getEnemyParty) { - this.enemyParty = config.getEnemyParty(scene); + this.enemyParty = config.getEnemyParty(); } } } -type GetTrainerFunc = (scene: BattleScene) => Trainer; -type GetEnemyPartyFunc = (scene: BattleScene) => EnemyPokemon[]; +type GetTrainerFunc = () => Trainer; +type GetEnemyPartyFunc = () => EnemyPokemon[]; export class FixedBattleConfig { public battleType: BattleType; @@ -500,11 +501,11 @@ export class FixedBattleConfig { * @returns the generated trainer */ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc { - return (scene: BattleScene) => { + return () => { const rand = Utils.randSeedInt(trainerPool.length); const trainerTypes: TrainerType[] = []; - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { for (const trainerPoolEntry of trainerPool) { const trainerType = Array.isArray(trainerPoolEntry) ? Utils.randSeedItem(trainerPoolEntry) @@ -523,10 +524,10 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand const isEvilTeamGrunt = evilTeamGrunts.includes(trainerTypes[rand]); if (trainerConfigs[trainerTypes[rand]].hasDouble && isEvilTeamGrunt) { - return new Trainer(scene, trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender); + return new Trainer(trainerTypes[rand], (Utils.randInt(3) === 0) ? TrainerVariant.DOUBLE : trainerGender); } - return new Trainer(scene, trainerTypes[rand], trainerGender); + return new Trainer(trainerTypes[rand], trainerGender); }; } @@ -544,16 +545,16 @@ export interface FixedBattleConfigs { */ export const classicFixedBattles: FixedBattleConfigs = { [5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), + .setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), [25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_2, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_2, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), [35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), [55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_3, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), [62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), @@ -562,7 +563,7 @@ export const classicFixedBattles: FixedBattleConfigs = { [66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true)), [95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_4, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), [112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), @@ -572,7 +573,7 @@ export const classicFixedBattles: FixedBattleConfigs = { .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ])) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_5, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), [ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ])) @@ -588,6 +589,6 @@ export const classicFixedBattles: FixedBattleConfigs = { [190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])), [195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) - .setGetTrainerFunc(scene => new Trainer(scene, TrainerType.RIVAL_6, scene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) + .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_6, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }) }; diff --git a/src/data/ability.ts b/src/data/ability.ts index 7fa046e2369..0e8b3c2392d 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1,23 +1,29 @@ -import Pokemon, { EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; +import type { EnemyPokemon } from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; +import { HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; import { Type } from "#enums/type"; -import { Constructor } from "#app/utils"; +import type { Constructor } from "#app/utils"; import * as Utils from "../utils"; import { getPokemonNameWithAffix } from "../messages"; -import { Weather } from "#app/data/weather"; -import { BattlerTag, BattlerTagLapseType, GroundedTag } from "./battler-tags"; +import type { Weather } from "#app/data/weather"; +import type { BattlerTag } from "./battler-tags"; +import { BattlerTagLapseType, GroundedTag } from "./battler-tags"; import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; -import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; +import type Move from "./move"; +import { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; +import type { ArenaTrapTag } from "./arena-tag"; +import { ArenaTagSide } from "./arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; import { SpeciesFormChangeManualTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms"; import i18next from "i18next"; -import { Localizable } from "#app/interfaces/locales"; +import type { Localizable } from "#app/interfaces/locales"; import { Command } from "../ui/command-ui-handler"; import { BerryModifierType } from "#app/modifier/modifier-type"; import { getPokeballName } from "./pokeball"; -import { BattlerIndex, BattleType } from "#app/battle"; +import type { BattlerIndex } from "#app/battle"; +import { BattleType } from "#app/battle"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -28,7 +34,7 @@ import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { SwitchType } from "#app/enums/switch-type"; import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; @@ -226,7 +232,7 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex && !simulated) { - return pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + return globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return false; @@ -251,18 +257,18 @@ export class PostBattleInitStatStageChangeAbAttr extends PostBattleInitAbAttr { if (!simulated) { if (this.selfTarget) { - statStageChangePhases.push(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); + statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } else { for (const opponent of pokemon.getOpponents()) { - statStageChangePhases.push(new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages)); + statStageChangePhases.push(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); } } for (const statStageChangePhase of statStageChangePhases) { if (!this.selfTarget && !statStageChangePhase.getPokemon()?.summonData) { - pokemon.scene.pushPhase(statStageChangePhase); + globalScene.pushPhase(statStageChangePhase); } else { // TODO: This causes the ability bar to be shown at the wrong time - pokemon.scene.unshiftPhase(statStageChangePhase); + globalScene.unshiftPhase(statStageChangePhase); } } } @@ -447,7 +453,7 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr { if (ret) { if (!pokemon.isFullHp() && !simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / 4), i18next.t("abilityTriggers:typeImmunityHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); cancelled.value = true; // Suppresses "No Effect" message } @@ -475,7 +481,7 @@ class TypeImmunityStatStageChangeAbAttr extends TypeImmunityAbAttr { if (ret) { cancelled.value = true; // Suppresses "No Effect" message if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } } @@ -658,7 +664,7 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr { applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { const ret = super.applyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args); if (ret && !simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } return ret; @@ -685,7 +691,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); + globalScene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } return true; } @@ -719,11 +725,11 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { if (this.allOthers) { const otherPokemon = pokemon.getAlly() ? pokemon.getOpponents().concat([ pokemon.getAlly() ]) : pokemon.getOpponents(); for (const other of otherPokemon) { - other.scene.unshiftPhase(new StatStageChangePhase(other.scene, (other).getBattlerIndex(), false, [ this.stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase((other).getBattlerIndex(), false, [ this.stat ], this.stages)); } return true; } - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), this.selfTarget, [ this.stat ], this.stages)); return true; } @@ -755,7 +761,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } return true; } @@ -777,10 +783,10 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { - const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; - if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { + const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag; + if (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!simulated) { - pokemon.scene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.addTag(this.tagType, 0, undefined, pokemon.id, pokemon.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); } return true; } @@ -803,7 +809,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); + globalScene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); } return true; } @@ -849,9 +855,9 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { - return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); + return globalScene.arena.terrain?.terrainType !== (this.terrainType || undefined); } else { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + return globalScene.arena.trySetTerrain(this.terrainType, true); } } @@ -941,7 +947,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { } if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } return true; @@ -1030,11 +1036,11 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { return false; } - if (!pokemon.scene.arena.weather?.isImmutable()) { + if (!globalScene.arena.weather?.isImmutable()) { if (simulated) { - return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + return globalScene.arena.weather?.weatherType !== this.weatherType; } - return pokemon.scene.arena.trySetWeather(this.weatherType, true); + return globalScene.arena.trySetWeather(this.weatherType, true); } return false; @@ -1138,7 +1144,7 @@ export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChang applyPostStatStageChange(pokemon: Pokemon, simulated: boolean, statStagesChanged: BattleStat[], stagesChanged: number, selfTarget: boolean, args: any[]): boolean { if (this.condition(pokemon, statStagesChanged, stagesChanged) && !selfTarget) { if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase((pokemon).getBattlerIndex(), true, this.statsToChange, this.stages)); } return true; } @@ -1661,9 +1667,9 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { + globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { if (success) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, stolenItemType: stolenItem.type.name })); + globalScene.queueMessage(i18next.t("abilityTriggers:postAttackStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), defenderName: defender.name, stolenItemType: stolenItem.type.name })); } resolve(success); }); @@ -1675,7 +1681,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; } } @@ -1754,9 +1760,9 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; - pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { + globalScene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => { if (success) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, stolenItemType: stolenItem.type.name })); + globalScene.queueMessage(i18next.t("abilityTriggers:postDefendStealHeldItem", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), attackerName: attacker.name, stolenItemType: stolenItem.type.name })); } resolve(success); }); @@ -1768,7 +1774,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; } } @@ -1850,7 +1856,7 @@ class PostVictoryStatStageChangeAbAttr extends PostVictoryAbAttr { ? this.stat(pokemon) : this.stat; if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } return true; } @@ -1869,7 +1875,7 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return true; } @@ -1900,7 +1906,7 @@ export class PostKnockOutStatStageChangeAbAttr extends PostKnockOutAbAttr { ? this.stat(pokemon) : this.stat; if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], this.stages)); } return true; } @@ -1915,7 +1921,7 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { if (pokemon.isPlayer() === knockedOut.isPlayer() && !knockedOut.getAbility().hasAttr(UncopiableAbilityAbAttr)) { if (!simulated) { pokemon.summonData.ability = knockedOut.getAbility().id; - pokemon.scene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); + globalScene.queueMessage(i18next.t("abilityTriggers:copyFaintedAllyAbility", { pokemonNameWithAffix: getPokemonNameWithAffix(knockedOut), abilityName: allAbilities[knockedOut.getAbility().id].name })); } return true; } @@ -1987,7 +1993,7 @@ export class PostIntimidateStatStageChangeAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (!simulated) { - pokemon.scene.pushPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.pushPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, this.stages)); } cancelled.value = this.overwrites; return true; @@ -2029,7 +2035,7 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { if (!simulated) { for (const arenaTag of this.arenaTags) { - pokemon.scene.arena.removeTag(arenaTag); + globalScene.arena.removeTag(arenaTag); } } return true; @@ -2047,7 +2053,7 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated) { - pokemon.scene.queueMessage(this.messageFunc(pokemon)); + globalScene.queueMessage(this.messageFunc(pokemon)); } return true; @@ -2066,7 +2072,7 @@ export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated) { - pokemon.scene.queueMessage(this.message); + globalScene.queueMessage(this.message); } return true; @@ -2117,7 +2123,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { if (this.selfTarget) { // we unshift the StatStageChangePhase to put it right after the showAbility and not at the end of the // phase list (which could be after CommandPhase for example) - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); return true; } for (const opponent of pokemon.getOpponents()) { @@ -2131,7 +2137,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { } } if (!cancelled.value) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages)); } } return true; @@ -2153,7 +2159,7 @@ export class PostSummonAllyHealAbAttr extends PostSummonAbAttr { const target = pokemon.getAlly(); if (target?.isActive(true)) { if (!simulated) { - target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / this.healRatio), i18next.t("abilityTriggers:postSummonAllyHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(target), pokemonName: pokemon.name }), true, !this.showAnim)); } @@ -2185,7 +2191,7 @@ export class PostSummonClearAllyStatStagesAbAttr extends PostSummonAbAttr { target.setStatStage(s, 0); } - target.scene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("abilityTriggers:postSummonClearAllyStats", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); } return true; @@ -2237,7 +2243,7 @@ export class DownloadAbAttr extends PostSummonAbAttr { if (this.enemyDef > 0 && this.enemySpDef > 0) { // only activate if there's actually an enemy to download from if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, 1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, this.stats, 1)); } return true; } @@ -2258,11 +2264,11 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if ((this.weatherType === WeatherType.HEAVY_RAIN || this.weatherType === WeatherType.HARSH_SUN || - this.weatherType === WeatherType.STRONG_WINDS) || !pokemon.scene.arena.weather?.isImmutable()) { + this.weatherType === WeatherType.STRONG_WINDS) || !globalScene.arena.weather?.isImmutable()) { if (simulated) { - return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + return globalScene.arena.weather?.weatherType !== this.weatherType; } else { - return pokemon.scene.arena.trySetWeather(this.weatherType, true); + return globalScene.arena.trySetWeather(this.weatherType, true); } } @@ -2281,9 +2287,9 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (simulated) { - return pokemon.scene.arena.terrain?.terrainType !== this.terrainType; + return globalScene.arena.terrain?.terrainType !== this.terrainType; } else { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + return globalScene.arena.trySetTerrain(this.terrainType, true); } } } @@ -2300,7 +2306,7 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - return simulated || pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + return simulated || globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return false; @@ -2320,7 +2326,7 @@ export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr { let target: Pokemon; if (targets.length > 1) { - pokemon.scene.executeWithSeedOffset(() => target = Utils.randSeedItem(targets), pokemon.scene.currentBattle.waveIndex); + globalScene.executeWithSeedOffset(() => target = Utils.randSeedItem(targets), globalScene.currentBattle.waveIndex); } else { target = targets[0]; } @@ -2377,7 +2383,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt * @returns A boolean or a promise that resolves to a boolean indicating the result of the ability application. */ applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - const party = pokemon instanceof PlayerPokemon ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField(); + const party = pokemon instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); const allowedParty = party.filter(p => p.isAllowedInBattle()); if (allowedParty.length < 1) { @@ -2387,7 +2393,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt if (!simulated) { for (const pokemon of allowedParty) { if (pokemon.status && this.statusEffect.includes(pokemon.status.effect)) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.resetStatus(false); pokemon.updateInfo(); } @@ -2401,7 +2407,7 @@ export class PostSummonUserFieldRemoveStatusEffectAbAttr extends PostSummonAbAtt /** Attempt to copy the stat changes on an ally pokemon */ export class PostSummonCopyAllyStatsAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - if (!pokemon.scene.currentBattle.double) { + if (!globalScene.currentBattle.double) { return false; } @@ -2445,7 +2451,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { let target: Pokemon; if (targets.length > 1) { - pokemon.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { // in a double battle, if one of the opposing pokemon is fused the other one will be chosen // if both are fused, then Imposter will fail below if (targets[0].fusionSpecies) { @@ -2456,7 +2462,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { return; } target = Utils.randSeedItem(targets); - }, pokemon.scene.currentBattle.waveIndex); + }, globalScene.currentBattle.waveIndex); } else { target = targets[0]; } @@ -2493,8 +2499,8 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr { pokemon.summonData.types = target.getTypes(); promises.push(pokemon.updateInfo()); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); - pokemon.scene.playSound("battle_anims/PRSFX- Transform"); + globalScene.queueMessage(i18next.t("abilityTriggers:postSummonTransform", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), targetName: target.name, })); + globalScene.playSound("battle_anims/PRSFX- Transform"); promises.push(pokemon.loadAssets(false).then(() => { pokemon.playAnim(); pokemon.updateInfo(); @@ -2520,14 +2526,14 @@ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAtt * @returns whether a Pokemon was reverted to its normal form */ applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]) { - const pokemonToTransform = getPokemonWithWeatherBasedForms(pokemon.scene); + const pokemonToTransform = getPokemonWithWeatherBasedForms(); if (pokemonToTransform.length < 1) { return false; } if (!simulated) { - pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal(); + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } return true; @@ -2567,8 +2573,8 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr { return simulated; } - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeWeatherTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeRevertWeatherFormTrigger); queueShowAbility(pokemon, passive); return true; } @@ -2589,7 +2595,7 @@ export class CommanderAbAttr extends AbAttr { override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: null, args: any[]): boolean { // TODO: Should this work with X + Dondozo fusions? - if (pokemon.scene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO) { + if (globalScene.currentBattle?.double && pokemon.getAlly()?.species.speciesId === Species.DONDOZO) { // If the ally Dondozo is fainted or was previously "commanded" by // another Pokemon, this effect cannot apply. if (pokemon.getAlly().isFainted() || pokemon.getAlly().getTag(BattlerTagType.COMMANDED)) { @@ -2600,11 +2606,11 @@ export class CommanderAbAttr extends AbAttr { // Lapse the source's semi-invulnerable tags (to avoid visual inconsistencies) pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); // Play an animation of the source jumping into the ally Dondozo's mouth - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_APPLY); + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_APPLY); // Apply boosts from this effect to the ally Dondozo pokemon.getAlly().addTag(BattlerTagType.COMMANDED, 0, Moves.NONE, pokemon.id); // Cancel the source Pokemon's next move (if a move is queued) - pokemon.scene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); + globalScene.tryRemovePhase((phase) => phase instanceof MovePhase && phase.pokemon === pokemon); } return true; } @@ -2649,26 +2655,26 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { * @returns {boolean} Returns true if the weather clears, otherwise false. */ applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - const weatherType = pokemon.scene.arena.weather?.weatherType; + const weatherType = globalScene.arena.weather?.weatherType; let turnOffWeather = false; // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { case (WeatherType.HARSH_SUN): if (pokemon.hasAbility(Abilities.DESOLATE_LAND) - && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { + && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { turnOffWeather = true; } break; case (WeatherType.HEAVY_RAIN): if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) - && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { + && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { turnOffWeather = true; } break; case (WeatherType.STRONG_WINDS): if (pokemon.hasAbility(Abilities.DELTA_STREAM) - && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { + && globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { turnOffWeather = true; } break; @@ -2679,7 +2685,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { } if (turnOffWeather) { - pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); + globalScene.arena.trySetWeather(WeatherType.NONE, false); return true; } @@ -2728,7 +2734,7 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return true; } @@ -3159,14 +3165,14 @@ function getSheerForceHitDisableAbCondition(): AbAttrCondition { } function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition { - return (pokemon: Pokemon) => { - if (!pokemon.scene?.arena) { + return () => { + if (!globalScene?.arena) { return false; } - if (pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene)) { + if (globalScene.arena.weather?.isEffectSuppressed()) { return false; } - const weatherType = pokemon.scene.arena.weather?.weatherType; + const weatherType = globalScene.arena.weather?.weatherType; return !!weatherType && weatherTypes.indexOf(weatherType) > -1; }; } @@ -3255,7 +3261,7 @@ export class ForewarnAbAttr extends PostSummonAbAttr { } } if (!simulated) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); + globalScene.queueMessage(i18next.t("abilityTriggers:forewarn", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: maxMove })); } return true; } @@ -3269,7 +3275,7 @@ export class FriskAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated) { for (const opponent of pokemon.getOpponents()) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); + globalScene.queueMessage(i18next.t("abilityTriggers:frisk", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), opponentName: opponent.name, opponentAbilityName: opponent.getAbility().name })); setAbilityRevealed(opponent); } } @@ -3317,12 +3323,12 @@ export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr { return simulated; } - const weatherType = pokemon.scene.arena.weather?.weatherType; + const weatherType = globalScene.arena.weather?.weatherType; if (weatherType && this.formRevertingWeathers.includes(weatherType)) { - pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal(); + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } else { - pokemon.scene.arena.triggerWeatherBasedFormChanges(); + globalScene.arena.triggerWeatherBasedFormChanges(); } return true; } @@ -3386,10 +3392,9 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr { applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { if (!pokemon.isFullHp()) { - const scene = pokemon.scene; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; if (!simulated) { - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.healFactor)), i18next.t("abilityTriggers:postWeatherLapseHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } return true; @@ -3409,14 +3414,13 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr { } applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: Weather, args: any[]): boolean { - const scene = pokemon.scene; if (pokemon.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { return false; } if (!simulated) { const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); + globalScene.queueMessage(i18next.t("abilityTriggers:postWeatherLapseDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName })); pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER); } @@ -3458,7 +3462,7 @@ export class PostTerrainChangeAddBattlerTagAttr extends PostTerrainChangeAbAttr function getTerrainCondition(...terrainTypes: TerrainType[]): AbAttrCondition { return (pokemon: Pokemon) => { - const terrainType = pokemon.scene.arena.terrain?.terrainType; + const terrainType = globalScene.arena.terrain?.terrainType; return !!terrainType && terrainTypes.indexOf(terrainType) > -1; }; } @@ -3494,9 +3498,8 @@ export class PostTurnStatusHealAbAttr extends PostTurnAbAttr { if (pokemon.status && this.effects.includes(pokemon.status.effect)) { if (!pokemon.isFullHp()) { if (!simulated) { - const scene = pokemon.scene; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / 8), i18next.t("abilityTriggers:poisonHeal", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName }), true)); } return true; @@ -3527,7 +3530,7 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr { } if (this.target?.status) { if (!simulated) { - this.target.scene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); + globalScene.queueMessage(getStatusEffectHealText(this.target.status?.effect, getPokemonNameWithAffix(this.target))); this.target.resetStatus(false); this.target.updateInfo(); } @@ -3592,7 +3595,7 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { const chosenBerry = new BerryModifierType(chosenBerryType); berriesEaten.splice(randomIdx); // Remove berry from memory - const berryModifier = pokemon.scene.findModifier( + const berryModifier = globalScene.findModifier( (m) => m instanceof BerryModifier && m.berryType === chosenBerryType, pokemon.isPlayer() ) as BerryModifier | undefined; @@ -3600,16 +3603,16 @@ export class PostTurnLootAbAttr extends PostTurnAbAttr { if (!berryModifier) { const newBerry = new BerryModifier(chosenBerry, pokemon.id, chosenBerryType, 1); if (pokemon.isPlayer()) { - pokemon.scene.addModifier(newBerry); + globalScene.addModifier(newBerry); } else { - pokemon.scene.addEnemyModifier(newBerry); + globalScene.addEnemyModifier(newBerry); } } else if (berryModifier.stackCount < berryModifier.getMaxHeldItemCount(pokemon)) { berryModifier.stackCount++; } - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: chosenBerry.name })); - pokemon.scene.updateModifiers(pokemon.isPlayer()); + globalScene.queueMessage(i18next.t("abilityTriggers:postTurnLootCreateEatenBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: chosenBerry.name })); + globalScene.updateModifiers(pokemon.isPlayer()); return true; } @@ -3642,11 +3645,11 @@ export class MoodyAbAttr extends PostTurnAbAttr { if (canRaise.length > 0) { const raisedStat = canRaise[pokemon.randSeedInt(canRaise.length)]; canLower = canRaise.filter(s => s !== raisedStat); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ raisedStat ], 2)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ raisedStat ], 2)); } if (canLower.length > 0) { const loweredStat = canLower[pokemon.randSeedInt(canLower.length)]; - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ loweredStat ], -1)); } } @@ -3663,7 +3666,7 @@ export class SpeedBoostAbAttr extends PostTurnAbAttr { applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated) { if (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1)); } else { return false; } @@ -3676,9 +3679,8 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr { applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!pokemon.isFullHp()) { if (!simulated) { - const scene = pokemon.scene; const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() / 16), i18next.t("abilityTriggers:postTurnHeal", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), true)); } @@ -3702,7 +3704,7 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); } return true; @@ -3732,7 +3734,7 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr { if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) { if (!simulated) { opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); + globalScene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) })); } hadEffect = true; } @@ -3762,11 +3764,11 @@ export class FetchBallAbAttr extends PostTurnAbAttr { if (simulated) { return false; } - const lastUsed = pokemon.scene.currentBattle.lastUsedPokeball; + const lastUsed = globalScene.currentBattle.lastUsedPokeball; if (lastUsed !== null && !!pokemon.isPlayer) { - pokemon.scene.pokeballCounts[lastUsed]++; - pokemon.scene.currentBattle.lastUsedPokeball = null; - pokemon.scene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) })); + globalScene.pokeballCounts[lastUsed]++; + globalScene.currentBattle.lastUsedPokeball = null; + globalScene.queueMessage(i18next.t("abilityTriggers:fetchBall", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokeballName: getPokeballName(lastUsed) })); return true; } return false; @@ -3785,11 +3787,11 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr { } apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (!pokemon.scene.arena.weather?.isImmutable()) { + if (!globalScene.arena.weather?.isImmutable()) { if (simulated) { - return pokemon.scene.arena.weather?.weatherType !== this.weatherType; + return globalScene.arena.weather?.weatherType !== this.weatherType; } else { - return pokemon.scene.arena.trySetWeather(this.weatherType, true); + return globalScene.arena.trySetWeather(this.weatherType, true); } } @@ -3808,9 +3810,9 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (simulated) { - return pokemon.scene.arena.terrain?.terrainType !== this.terrainType; + return globalScene.arena.terrain?.terrainType !== this.terrainType; } else { - return pokemon.scene.arena.trySetTerrain(this.terrainType, true); + return globalScene.arena.trySetTerrain(this.terrainType, true); } } } @@ -3852,10 +3854,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { const target = this.getTarget(dancer, source, targets); - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true, true)); + globalScene.unshiftPhase(new MovePhase(dancer, target, move, true, true)); } else if (move.getMove() instanceof SelfStatusMove) { // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [ dancer.getBattlerIndex() ], move, true, true)); + globalScene.unshiftPhase(new MovePhase(dancer, [ dancer.getBattlerIndex() ], move, true, true)); } } return true; @@ -3932,7 +3934,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr { export class StatStageChangeCopyAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise { if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, (args[0] as BattleStat[]), (args[1] as number), true, false, false)); } return true; } @@ -4009,9 +4011,8 @@ export class HealFromBerryUseAbAttr extends AbAttr { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, ...args: [Utils.BooleanHolder, any[]]): boolean { const { name: abilityName } = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (!simulated) { - pokemon.scene.unshiftPhase( + globalScene.unshiftPhase( new PokemonHealPhase( - pokemon.scene, pokemon.getBattlerIndex(), Utils.toDmgValue(pokemon.getMaxHp() * this.healPercent), i18next.t("abilityTriggers:healFromBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName }), @@ -4117,13 +4118,13 @@ export class PostBattleLootAbAttr extends PostBattleAbAttr { * @returns `true` if successful */ applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { - const postBattleLoot = pokemon.scene.currentBattle.postBattleLoot; + const postBattleLoot = globalScene.currentBattle.postBattleLoot; if (!simulated && postBattleLoot.length && args[0]) { const randItem = Utils.randSeedItem(postBattleLoot); //@ts-ignore - TODO see below - if (pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!? + if (globalScene.tryTransferHeldItemModifier(randItem, pokemon, true, 1, true, undefined, false)) { // TODO: fix. This is a promise!? postBattleLoot.splice(postBattleLoot.indexOf(randItem), 1); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); + globalScene.queueMessage(i18next.t("abilityTriggers:postBattleLoot", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), itemName: randItem.type.name })); return true; } } @@ -4156,14 +4157,14 @@ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAtt * @returns whether the form change was triggered */ applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const pokemonToTransform = getPokemonWithWeatherBasedForms(pokemon.scene); + const pokemonToTransform = getPokemonWithWeatherBasedForms(); if (pokemonToTransform.length < 1) { return false; } if (!simulated) { - pokemon.scene.arena.triggerWeatherBasedFormChanges(); + globalScene.arena.triggerWeatherBasedFormChanges(); } return true; @@ -4185,26 +4186,26 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { * @returns {boolean} Returns true if the weather clears, otherwise false. */ applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { - const weatherType = pokemon.scene.arena.weather?.weatherType; + const weatherType = globalScene.arena.weather?.weatherType; let turnOffWeather = false; // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { case (WeatherType.HARSH_SUN): if (pokemon.hasAbility(Abilities.DESOLATE_LAND) - && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { + && globalScene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { turnOffWeather = true; } break; case (WeatherType.HEAVY_RAIN): if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) - && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { + && globalScene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { turnOffWeather = true; } break; case (WeatherType.STRONG_WINDS): if (pokemon.hasAbility(Abilities.DELTA_STREAM) - && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { + && globalScene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { turnOffWeather = true; } break; @@ -4215,7 +4216,7 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { } if (turnOffWeather) { - pokemon.scene.arena.trySetWeather(WeatherType.NONE, false); + globalScene.arena.trySetWeather(WeatherType.NONE, false); return true; } @@ -4235,7 +4236,7 @@ export class PostFaintContactDamageAbAttr extends PostFaintAbAttr { applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean { if (move !== undefined && attacker !== undefined && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { //If the mon didn't die to indirect damage const cancelled = new Utils.BooleanHolder(false); - pokemon.scene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); + globalScene.getField(true).map(p => applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled, simulated)); if (cancelled.value || attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { return false; } @@ -4366,7 +4367,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { if (!simulated) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); } return true; } @@ -4584,7 +4585,7 @@ export class MoneyAbAttr extends PostBattleAbAttr { */ applyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { if (!simulated && args[0]) { - pokemon.scene.currentBattle.moneyScattered += pokemon.scene.getWaveMoneyAmount(0.2); + globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); return true; } return false; @@ -4627,7 +4628,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const side = pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (pokemon.scene.arena.getTagOnSide(this.tagType, side)) { + if (globalScene.arena.getTagOnSide(this.tagType, side)) { return super.applyPostSummon(pokemon, passive, simulated, args); } return false; @@ -4725,7 +4726,7 @@ export class BypassSpeedChanceAbAttr extends AbAttr { if (!bypassSpeed.value && pokemon.randSeedInt(100) < this.chance) { const turnCommand = - pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; + globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; const isDamageMove = move?.category === MoveCategory.PHYSICAL || move?.category === MoveCategory.SPECIAL; @@ -4767,7 +4768,7 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr { const bypassSpeed = args[0] as Utils.BooleanHolder; const canCheckHeldItems = args[1] as Utils.BooleanHolder; - const turnCommand = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; + const turnCommand = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; const isCommandFight = turnCommand?.command === Command.FIGHT; const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null; if (this.condition(pokemon, move!) && isCommandFight) { @@ -4792,7 +4793,7 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { if (pokemon.isTerastallized()) { return false; } - const currentTerrain = pokemon.scene.arena.getTerrainType(); + const currentTerrain = globalScene.arena.getTerrainType(); const typeChange: Type[] = this.determineTypeChange(pokemon, currentTerrain); if (typeChange.length !== 0) { if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) { @@ -4839,14 +4840,14 @@ export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr { * @returns `true` if there is an active terrain requiring a type change | `false` if not */ override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise { - if (pokemon.scene.arena.getTerrainType() !== TerrainType.NONE) { + if (globalScene.arena.getTerrainType() !== TerrainType.NONE) { return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []); } return false; } override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) { - const currentTerrain = pokemon.scene.arena.getTerrainType(); + const currentTerrain = globalScene.arena.getTerrainType(); const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon); if (currentTerrain === TerrainType.NONE) { return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix }); @@ -4878,7 +4879,7 @@ async function applyAbAttrsInternal( continue; } - pokemon.scene.setPhaseQueueSplice(); + globalScene.setPhaseQueueSplice(); let result = applyFunc(attr, passive); // TODO Remove this when promises get reworked @@ -4894,7 +4895,7 @@ async function applyAbAttrsInternal( } if (attr.showAbility && !simulated) { if (showAbilityInstant) { - pokemon.scene.abilityBar.showAbility(pokemon, passive); + globalScene.abilityBar.showAbility(pokemon, passive); } else { queueShowAbility(pokemon, passive); } @@ -4902,13 +4903,13 @@ async function applyAbAttrsInternal( const message = attr.getTriggerMessage(pokemon, ability.name, args); if (message) { if (!simulated) { - pokemon.scene.queueMessage(message); + globalScene.queueMessage(message); } } messages.push(message!); } } - pokemon.scene.clearPhaseQueueSplice(); + globalScene.clearPhaseQueueSplice(); } } @@ -4929,28 +4930,27 @@ class ForceSwitchOutHelper { * - If the Pokémon is still alive (hp > 0), and if so, it leaves the field and a new SwitchPhase is initiated. */ if (switchOutTarget instanceof PlayerPokemon) { - if (switchOutTarget.scene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + if (globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - pokemon.scene.prependToPhase(new SwitchPhase(pokemon.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); + globalScene.prependToPhase(new SwitchPhase(this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); return true; } /** * For non-wild battles, it checks if the opposing party has any available Pokémon to switch in. * If yes, the Pokémon leaves the field and a new SwitchSummonPhase is initiated. */ - } else if (pokemon.scene.currentBattle.battleType !== BattleType.WILD) { - if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { + } else if (globalScene.currentBattle.battleType !== BattleType.WILD) { + if (globalScene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - pokemon.scene.prependToPhase(new SwitchSummonPhase(pokemon.scene, this.switchType, switchOutTarget.getFieldIndex(), - (pokemon.scene.currentBattle.trainer ? pokemon.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), - false, false), MoveEndPhase); + const summonIndex = (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0); + globalScene.prependToPhase(new SwitchSummonPhase(this.switchType, switchOutTarget.getFieldIndex(), summonIndex, false, false), MoveEndPhase); return true; } /** @@ -4958,26 +4958,26 @@ class ForceSwitchOutHelper { * It will not flee if it is a Mystery Encounter with fleeing disabled (checked in `getSwitchOutCondition()`) or if it is a wave 10x wild boss */ } else { - if (!pokemon.scene.currentBattle.waveIndex || pokemon.scene.currentBattle.waveIndex % 10 === 0) { + if (!globalScene.currentBattle.waveIndex || globalScene.currentBattle.waveIndex % 10 === 0) { return false; } if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); - pokemon.scene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); + globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); - if (switchOutTarget.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { const allyPokemon = switchOutTarget.getAlly(); - switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon); + globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } if (!switchOutTarget.getAlly()?.isActive(true)) { - pokemon.scene.clearEnemyHeldItemModifiers(); + globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - pokemon.scene.pushPhase(new BattleEndPhase(pokemon.scene, false)); - pokemon.scene.pushPhase(new NewBattlePhase(pokemon.scene)); + globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.pushPhase(new NewBattlePhase()); } } } @@ -5001,20 +5001,20 @@ class ForceSwitchOutHelper { return !blockedByAbility.value; } - if (!player && pokemon.scene.currentBattle.battleType === BattleType.WILD) { - if (!pokemon.scene.currentBattle.waveIndex && pokemon.scene.currentBattle.waveIndex % 10 === 0) { + if (!player && globalScene.currentBattle.battleType === BattleType.WILD) { + if (!globalScene.currentBattle.waveIndex && globalScene.currentBattle.waveIndex % 10 === 0) { return false; } } - if (!player && pokemon.scene.currentBattle.isBattleMysteryEncounter() && !pokemon.scene.currentBattle.mysteryEncounter?.fleeAllowed) { + if (!player && globalScene.currentBattle.isBattleMysteryEncounter() && !globalScene.currentBattle.mysteryEncounter?.fleeAllowed) { return false; } - const party = player ? pokemon.scene.getPlayerParty() : pokemon.scene.getEnemyParty(); - return (!player && pokemon.scene.currentBattle.battleType === BattleType.WILD) + const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); + return (!player && globalScene.currentBattle.battleType === BattleType.WILD) || party.filter(p => p.isAllowedInBattle() - && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > pokemon.scene.currentBattle.getBattlerCount(); + && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount(); } /** @@ -5295,8 +5295,8 @@ export function applyPostItemLostAbAttrs(attrType: Constructor +function getPokemonWithWeatherBasedForms() { + return globalScene.getField(true).filter(p => (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM) || (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM) ); @@ -5412,7 +5411,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .ignorable(), new Ability(Abilities.LEVITATE, 3) - .attr(AttackTypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !pokemon.scene.arena.getTag(ArenaTagType.GRAVITY)) + .attr(AttackTypeImmunityAbAttr, Type.GROUND, (pokemon: Pokemon) => !pokemon.getTag(GroundedTag) && !globalScene.arena.getTag(ArenaTagType.GRAVITY)) .ignorable(), new Ability(Abilities.EFFECT_SPORE, 3) .attr(EffectSporeAbAttr), @@ -5504,9 +5503,9 @@ export function initAbilities() { new Ability(Abilities.CUTE_CHARM, 3) .attr(PostDefendContactApplyTagChanceAbAttr, 30, BattlerTagType.INFATUATED), new Ability(Abilities.PLUS, 3) - .conditionalAttr(p => p.scene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), + .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), new Ability(Abilities.MINUS, 3) - .conditionalAttr(p => p.scene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), + .conditionalAttr(p => globalScene.currentBattle.double && [ Abilities.PLUS, Abilities.MINUS ].some(a => p.getAlly().hasAbility(a)), StatMultiplierAbAttr, Stat.SPATK, 1.5), new Ability(Abilities.FORECAST, 3) .attr(UncopiableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) @@ -5782,7 +5781,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.ANALYTIC, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => { - const movePhase = user?.scene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user.id); + const movePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.id !== user?.id); return Utils.isNullOrUndefined(movePhase); }, 1.3), new Ability(Abilities.ILLUSION, 5) @@ -5906,9 +5905,9 @@ export function initAbilities() { .attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3), new Ability(Abilities.AURA_BREAK, 6) .ignorable() - .conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16) - .conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16) - .conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)), + .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16) + .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16) + .conditionalAttr(pokemon => globalScene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)), PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })), new Ability(Abilities.PRIMORDIAL_SEA, 6) .attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN) @@ -6320,7 +6319,7 @@ export function initAbilities() { new Ability(Abilities.SHARPNESS, 9) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), new Ability(Abilities.SUPREME_OVERLORD, 9) - .attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5)) + .attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? globalScene.currentBattle.playerFaints : globalScene.currentBattle.enemyFaints, 5)) .partial(), // Counter resets every wave instead of on arena reset new Ability(Abilities.COSTAR, 9) .attr(PostSummonCopyAllyStatsAbAttr), diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 8bb74d29a4e..816de3e824c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,12 +1,13 @@ -import { Arena } from "#app/field/arena"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { Arena } from "#app/field/arena"; import { Type } from "#enums/type"; import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils"; import { MoveCategory, allMoves, MoveTarget } from "#app/data/move"; import { getPokemonNameWithAffix } from "#app/messages"; -import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#enums/status-effect"; -import { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#app/battle"; import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; @@ -44,7 +45,7 @@ export abstract class ArenaTag { onRemove(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: this.getMoveName() })); + globalScene.queueMessage(i18next.t(`arenaTag:arenaOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: this.getMoveName() })); } } @@ -74,27 +75,25 @@ export abstract class ArenaTag { /** * Helper function that retrieves the source Pokemon - * @param scene medium to retrieve the source Pokemon * @returns The source {@linkcode Pokemon} or `null` if none is found */ - public getSourcePokemon(scene: BattleScene): Pokemon | null { - return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + public getSourcePokemon(): Pokemon | null { + return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; } /** * Helper function that retrieves the Pokemon affected - * @param scene - medium to retrieve the involved Pokemon * @returns list of PlayerPokemon or EnemyPokemon on the field */ - public getAffectedPokemon(scene: BattleScene): Pokemon[] { + public getAffectedPokemon(): Pokemon[] { switch (this.side) { case ArenaTagSide.PLAYER: - return scene.getPlayerField() ?? []; + return globalScene.getPlayerField() ?? []; case ArenaTagSide.ENEMY: - return scene.getEnemyField() ?? []; + return globalScene.getEnemyField() ?? []; case ArenaTagSide.BOTH: default: - return scene.getField(true) ?? []; + return globalScene.getField(true) ?? []; } } } @@ -112,10 +111,10 @@ export class MistTag extends ArenaTag { super.onAdd(arena); if (this.sourceId) { - const source = arena.scene.getPokemonById(this.sourceId); + const source = globalScene.getPokemonById(this.sourceId); if (!quiet && source) { - arena.scene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); + globalScene.queueMessage(i18next.t("arenaTag:mistOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } else if (!quiet) { console.warn("Failed to get source for MistTag onAdd"); } @@ -146,7 +145,7 @@ export class MistTag extends ArenaTag { cancelled.value = true; if (!simulated) { - arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + globalScene.queueMessage(i18next.t("arenaTag:mistApply")); } return true; @@ -193,7 +192,7 @@ export class WeakenMoveScreenTag extends ArenaTag { if (bypassed.value) { return false; } - damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5; + damageMultiplier.value = globalScene.currentBattle.double ? 2732 / 4096 : 0.5; return true; } return false; @@ -211,7 +210,7 @@ class ReflectTag extends WeakenMoveScreenTag { onAdd(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:reflectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } } @@ -227,7 +226,7 @@ class LightScreenTag extends WeakenMoveScreenTag { onAdd(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:lightScreenOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } } @@ -243,7 +242,7 @@ class AuroraVeilTag extends WeakenMoveScreenTag { onAdd(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:auroraVeilOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } } @@ -268,7 +267,7 @@ export class ConditionalProtectTag extends ArenaTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t(`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: super.getMoveName() })); + globalScene.queueMessage(i18next.t(`arenaTag:conditionalProtectOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`, { moveName: super.getMoveName() })); } // Removes default message for effect removal @@ -296,8 +295,8 @@ export class ConditionalProtectTag extends ArenaTag { if (!simulated) { attacker.stopMultiHit(defender); - new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene); - arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) })); + new CommonBattleAnim(CommonAnim.PROTECT, defender).play(); + globalScene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) })); } } @@ -318,7 +317,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const effectPhase = arena.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { const attacker = effectPhase.getUserPokemon(); @@ -393,9 +392,9 @@ class MatBlockTag extends ConditionalProtectTag { onAdd(arena: Arena) { if (this.sourceId) { - const source = arena.scene.getPokemonById(this.sourceId); + const source = globalScene.getPokemonById(this.sourceId); if (source) { - arena.scene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); + globalScene.queueMessage(i18next.t("arenaTag:matBlockOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } else { console.warn("Failed to get source for MatBlockTag onAdd"); } @@ -448,15 +447,15 @@ export class NoCritTag extends ArenaTag { /** Queues a message upon adding this effect to the field */ onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, { + globalScene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, { moveName: this.getMoveName() })); } /** Queues a message upon removing this effect from the field */ onRemove(arena: Arena): void { - const source = arena.scene.getPokemonById(this.sourceId!); // TODO: is this bang correct? - arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", { + const source = globalScene.getPokemonById(this.sourceId!); // TODO: is this bang correct? + globalScene.queueMessage(i18next.t("arenaTag:noCritOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(source ?? undefined), moveName: this.getMoveName() })); @@ -478,7 +477,7 @@ class WishTag extends ArenaTag { onAdd(arena: Arena): void { if (this.sourceId) { - const user = arena.scene.getPokemonById(this.sourceId); + const user = globalScene.getPokemonById(this.sourceId); if (user) { this.battlerIndex = user.getBattlerIndex(); this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) }); @@ -490,10 +489,10 @@ class WishTag extends ArenaTag { } onRemove(arena: Arena): void { - const target = arena.scene.getField()[this.battlerIndex]; + const target = globalScene.getField()[this.battlerIndex]; if (target?.isActive(true)) { - arena.scene.queueMessage(this.triggerMessage); - arena.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), this.healHp, null, true, false)); + globalScene.queueMessage(this.triggerMessage); + globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), this.healHp, null, true, false)); } } } @@ -546,11 +545,11 @@ class MudSportTag extends WeakenMoveTypeTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); + globalScene.queueMessage(i18next.t("arenaTag:mudSportOnAdd")); } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); + globalScene.queueMessage(i18next.t("arenaTag:mudSportOnRemove")); } } @@ -564,11 +563,11 @@ class WaterSportTag extends WeakenMoveTypeTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); + globalScene.queueMessage(i18next.t("arenaTag:waterSportOnAdd")); } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); + globalScene.queueMessage(i18next.t("arenaTag:waterSportOnRemove")); } } @@ -584,7 +583,7 @@ export class IonDelugeTag extends ArenaTag { /** Queues an on-add message */ onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd")); + globalScene.queueMessage(i18next.t("arenaTag:plasmaFistsOnAdd")); } onRemove(arena: Arena): void { } // Removes default on-remove message @@ -679,9 +678,9 @@ class SpikesTag extends ArenaTrapTag { onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); - const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - arena.scene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); + globalScene.queueMessage(i18next.t("arenaTag:spikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); } } @@ -698,7 +697,7 @@ class SpikesTag extends ArenaTrapTag { const damageHpRatio = 1 / (10 - 2 * this.layers); const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); - pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); if (pokemon.turnData) { pokemon.turnData.damageTaken += damage; @@ -728,9 +727,9 @@ class ToxicSpikesTag extends ArenaTrapTag { onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); - const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - arena.scene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); + globalScene.queueMessage(i18next.t("arenaTag:toxicSpikesOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); } } @@ -747,8 +746,8 @@ class ToxicSpikesTag extends ArenaTrapTag { } if (pokemon.isOfType(Type.POISON)) { this.neutralized = true; - if (pokemon.scene.arena.removeTag(this.tagType)) { - pokemon.scene.queueMessage(i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() })); + if (globalScene.arena.removeTag(this.tagType)) { + globalScene.queueMessage(i18next.t("arenaTag:toxicSpikesActivateTrapPoison", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() })); return true; } } else if (!pokemon.status) { @@ -792,7 +791,7 @@ export class DelayedAttackTag extends ArenaTag { const ret = super.lapse(arena); if (!ret) { - arena.scene.unshiftPhase(new MoveEffectPhase(arena.scene, this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct? + globalScene.unshiftPhase(new MoveEffectPhase(this.sourceId!, [ this.targetIndex ], new PokemonMove(this.sourceMove!, 0, 0, true))); // TODO: are those bangs correct? } return ret; @@ -814,9 +813,9 @@ class StealthRockTag extends ArenaTrapTag { onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); - const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - arena.scene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() })); + globalScene.queueMessage(i18next.t("arenaTag:stealthRockOnAdd", { opponentDesc: source.getOpponentDescriptor() })); } } @@ -864,7 +863,7 @@ class StealthRockTag extends ArenaTrapTag { return true; } const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio); - pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.damageAndUpdate(damage, HitResult.OTHER); if (pokemon.turnData) { pokemon.turnData.damageTaken += damage; @@ -893,9 +892,9 @@ class StickyWebTag extends ArenaTrapTag { onAdd(arena: Arena, quiet: boolean = false): void { super.onAdd(arena); - const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!quiet && source) { - arena.scene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); + globalScene.queueMessage(i18next.t("arenaTag:stickyWebOnAdd", { moveName: this.getMoveName(), opponentDesc: source.getOpponentDescriptor() })); } } @@ -909,9 +908,9 @@ class StickyWebTag extends ArenaTrapTag { } if (!cancelled.value) { - pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); + globalScene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); const stages = new NumberHolder(-1); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); return true; } } @@ -945,14 +944,14 @@ export class TrickRoomTag extends ArenaTag { } onAdd(arena: Arena): void { - const source = this.sourceId ? arena.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (source) { - arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); + globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); + globalScene.queueMessage(i18next.t("arenaTag:trickRoomOnRemove")); } } @@ -967,8 +966,8 @@ export class GravityTag extends ArenaTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); - arena.scene.getField(true).forEach((pokemon) => { + globalScene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); + globalScene.getField(true).forEach((pokemon) => { if (pokemon !== null) { pokemon.removeTag(BattlerTagType.FLOATING); pokemon.removeTag(BattlerTagType.TELEKINESIS); @@ -980,7 +979,7 @@ export class GravityTag extends ArenaTag { } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:gravityOnRemove")); + globalScene.queueMessage(i18next.t("arenaTag:gravityOnRemove")); } } @@ -996,29 +995,29 @@ class TailwindTag extends ArenaTag { onAdd(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:tailwindOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } - const source = arena.scene.getPokemonById(this.sourceId!); //TODO: this bang is questionable! - const party = (source?.isPlayer() ? source.scene.getPlayerField() : source?.scene.getEnemyField()) ?? []; + const source = globalScene.getPokemonById(this.sourceId!); //TODO: this bang is questionable! + const party = (source?.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField()) ?? []; for (const pokemon of party) { // Apply the CHARGED tag to party members with the WIND_POWER ability if (pokemon.hasAbility(Abilities.WIND_POWER) && !pokemon.getTag(BattlerTagType.CHARGED)) { pokemon.addTag(BattlerTagType.CHARGED); - pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() })); + globalScene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() })); } // Raise attack by one stage if party member has WIND_RIDER ability if (pokemon.hasAbility(Abilities.WIND_RIDER)) { - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true)); + globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.getBattlerIndex())); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true)); } } } onRemove(arena: Arena, quiet: boolean = false): void { if (!quiet) { - arena.scene.queueMessage(i18next.t(`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:tailwindOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } } @@ -1033,11 +1032,11 @@ class HappyHourTag extends ArenaTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); + globalScene.queueMessage(i18next.t("arenaTag:happyHourOnAdd")); } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); + globalScene.queueMessage(i18next.t("arenaTag:happyHourOnRemove")); } } @@ -1047,11 +1046,11 @@ class SafeguardTag extends ArenaTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:safeguardOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } onRemove(arena: Arena): void { - arena.scene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:safeguardOnRemove${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } @@ -1074,16 +1073,16 @@ class ImprisonTag extends ArenaTrapTag { * This function applies the effects of Imprison to the opposing Pokemon already present on the field. * @param arena */ - override onAdd({ scene }: Arena) { - const source = this.getSourcePokemon(scene); + override onAdd() { + const source = this.getSourcePokemon(); if (source) { - const party = this.getAffectedPokemon(scene); + const party = this.getAffectedPokemon(); party?.forEach((p: Pokemon ) => { if (p.isAllowedInBattle()) { p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } }); - scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); + globalScene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } } @@ -1092,8 +1091,8 @@ class ImprisonTag extends ArenaTrapTag { * @param _arena * @returns `true` if the source of the tag is still active on the field | `false` if not */ - override lapse({ scene }: Arena): boolean { - const source = this.getSourcePokemon(scene); + override lapse(): boolean { + const source = this.getSourcePokemon(); return source ? source.isActive(true) : false; } @@ -1103,7 +1102,7 @@ class ImprisonTag extends ArenaTrapTag { * @returns `true` */ override activateTrap(pokemon: Pokemon): boolean { - const source = this.getSourcePokemon(pokemon.scene); + const source = this.getSourcePokemon(); if (source && source.isActive(true) && pokemon.isAllowedInBattle()) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } @@ -1114,8 +1113,8 @@ class ImprisonTag extends ArenaTrapTag { * When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon * @param arena */ - override onRemove({ scene }: Arena): void { - const party = this.getAffectedPokemon(scene); + override onRemove(): void { + const party = this.getAffectedPokemon(); party?.forEach((p: Pokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); @@ -1136,19 +1135,19 @@ class FireGrassPledgeTag extends ArenaTag { override onAdd(arena: Arena): void { // "A sea of fire enveloped your/the opposing team!" - arena.scene.queueMessage(i18next.t(`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:fireGrassPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } override lapse(arena: Arena): boolean { const field: Pokemon[] = (this.side === ArenaTagSide.PLAYER) - ? arena.scene.getPlayerField() - : arena.scene.getEnemyField(); + ? globalScene.getPlayerField() + : globalScene.getEnemyField(); field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => { // "{pokemonNameWithAffix} was hurt by the sea of fire!" - pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // TODO: Replace this with a proper animation - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM)); pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); }); @@ -1170,7 +1169,7 @@ class WaterFirePledgeTag extends ArenaTag { override onAdd(arena: Arena): void { // "A rainbow appeared in the sky on your/the opposing team's side!" - arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } /** @@ -1200,7 +1199,7 @@ class GrassWaterPledgeTag extends ArenaTag { override onAdd(arena: Arena): void { // "A swamp enveloped your/the opposing team!" - arena.scene.queueMessage(i18next.t(`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); + globalScene.queueMessage(i18next.t(`arenaTag:grassWaterPledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`)); } } @@ -1217,7 +1216,7 @@ export class FairyLockTag extends ArenaTag { } onAdd(arena: Arena): void { - arena.scene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); + globalScene.queueMessage(i18next.t("arenaTag:fairyLockOnAdd")); } } diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 0f4926cf7c7..240881ad580 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -1,6 +1,7 @@ import { Type } from "#enums/type"; import * as Utils from "#app/utils"; -import { pokemonEvolutions, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index 9e86ea7397b..bf34b5122dc 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -1,6 +1,7 @@ +import { globalScene } from "#app/global-scene"; import { Gender } from "#app/data/gender"; import { PokeballType } from "#enums/pokeball"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { Type } from "#enums/type"; import * as Utils from "#app/utils"; import { WeatherType } from "#enums/weather-type"; @@ -266,8 +267,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ELECTRODE, 30, null, null) ], [Species.CUBONE]: [ - new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.TYROGUE]: [ /** @@ -287,8 +288,8 @@ export const pokemonEvolutions: PokemonEvolutions = { )), ], [Species.KOFFING]: [ - new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.WEEZING, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.RHYHORN]: [ new SpeciesEvolution(Species.RHYDON, 42, null, null) @@ -333,8 +334,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.QUILAVA, 14, null, null) ], [Species.QUILAVA]: [ - new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.TOTODILE]: [ new SpeciesEvolution(Species.CROCONAW, 18, null, null) @@ -436,8 +437,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.LINOONE, 20, null, null) ], [Species.WURMPLE]: [ - new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)), - new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)), + new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) ], [Species.SILCOON]: [ new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null) @@ -478,7 +479,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [Species.NINCADA]: [ new SpeciesEvolution(Species.NINJASK, 20, null, null), - new SpeciesEvolution(Species.SHEDINJA, 20, null, new SpeciesEvolutionCondition(p => p.scene.getPlayerParty().length < 6 && p.scene.pokeballCounts[PokeballType.POKEBALL] > 0)) + new SpeciesEvolution(Species.SHEDINJA, 20, null, new SpeciesEvolutionCondition(p => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0)) ], [Species.WHISMUR]: [ new SpeciesEvolution(Species.LOUDRED, 20, null, null) @@ -660,7 +661,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.LUMINEON, 31, null, null) ], [Species.MANTYKE]: [ - new SpeciesEvolution(Species.MANTINE, 32, null, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.REMORAID].caughtAttr), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.MANTINE, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.REMORAID].caughtAttr), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.SNOVER]: [ new SpeciesEvolution(Species.ABOMASNOW, 40, null, null) @@ -681,8 +682,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DEWOTT, 17, null, null) ], [Species.DEWOTT]: [ - new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.PATRAT]: [ new SpeciesEvolution(Species.WATCHOG, 20, null, null) @@ -832,8 +833,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.KINGAMBIT, 1, EvolutionItem.LEADERS_CREST, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.RUFFLET]: [ - new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.VULLABY]: [ new SpeciesEvolution(Species.MANDIBUZZ, 54, null, null) @@ -890,7 +891,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GOGOAT, 32, null, null) ], [Species.PANCHAM]: [ - new SpeciesEvolution(Species.PANGORO, 32, null, new SpeciesEvolutionCondition(p => !!p.scene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(Type.DARK) > -1)), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.PANGORO, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(Type.DARK) > -1)), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.ESPURR]: [ new SpeciesFormEvolution(Species.MEOWSTIC, "", "female", 25, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)), @@ -912,21 +913,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CLAWITZER, 37, null, null) ], [Species.TYRUNT]: [ - new SpeciesEvolution(Species.TYRANTRUM, 39, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.TYRANTRUM, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.AMAURA]: [ - new SpeciesEvolution(Species.AURORUS, 39, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.AURORUS, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) ], [Species.GOOMY]: [ - new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.SLIGGOO]: [ - new SpeciesEvolution(Species.GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(p.scene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) ], [Species.BERGMITE]: [ - new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.AVALUGG, 37, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.NOIBAT]: [ new SpeciesEvolution(Species.NOIVERN, 48, null, null) @@ -935,8 +936,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DARTRIX, 17, null, null) ], [Species.DARTRIX]: [ - new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.DECIDUEYE, 34, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), + new SpeciesEvolution(Species.DECIDUEYE, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.LITTEN]: [ new SpeciesEvolution(Species.TORRACAT, 17, null, null) @@ -957,7 +958,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.TOUCANNON, 28, null, null) ], [Species.YUNGOOS]: [ - new SpeciesEvolution(Species.GUMSHOOS, 20, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.GUMSHOOS, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.GRUBBIN]: [ new SpeciesEvolution(Species.CHARJABUG, 20, null, null) @@ -975,7 +976,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ARAQUANID, 22, null, null) ], [Species.FOMANTIS]: [ - new SpeciesEvolution(Species.LURANTIS, 34, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.LURANTIS, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) ], [Species.MORELULL]: [ new SpeciesEvolution(Species.SHIINOTIC, 24, null, null) @@ -1012,7 +1013,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.MELMETAL, 48, null, null) ], [Species.ALOLA_RATTATA]: [ - new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) ], [Species.ALOLA_DIGLETT]: [ new SpeciesEvolution(Species.ALOLA_DUGTRIO, 26, null, null) @@ -1135,7 +1136,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GALAR_LINOONE, 20, null, null) ], [Species.GALAR_LINOONE]: [ - new SpeciesEvolution(Species.OBSTAGOON, 35, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.OBSTAGOON, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) ], [Species.GALAR_YAMASK]: [ new SpeciesEvolution(Species.RUNERIGUS, 34, null, null) @@ -1144,7 +1145,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.HISUI_ZOROARK, 30, null, null) ], [Species.HISUI_SLIGGOO]: [ - new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(p.scene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) ], [Species.SPRIGATITO]: [ new SpeciesEvolution(Species.FLORAGATO, 16, null, null) @@ -1183,7 +1184,7 @@ export const pokemonEvolutions: PokemonEvolutions = { [Species.TANDEMAUS]: [ new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new SpeciesEvolutionCondition(p => { let ret = false; - p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); return ret; })), new SpeciesEvolution(Species.MAUSHOLD, 25, null, null) @@ -1243,7 +1244,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GLIMMORA, 35, null, null) ], [Species.GREAVARD]: [ - new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) ], [Species.FRIGIBAX]: [ new SpeciesEvolution(Species.ARCTIBAX, 35, null, null) @@ -1311,10 +1312,10 @@ export const pokemonEvolutions: PokemonEvolutions = { [Species.EEVEE]: [ new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.VAPOREON, "", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.VAPOREON, "partner", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.JOLTEON, "", "", 1, EvolutionItem.THUNDER_STONE, null, SpeciesWildEvolutionDelay.LONG), @@ -1351,17 +1352,17 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => { let ret = false; if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) { - p.scene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); } return ret; }), SpeciesWildEvolutionDelay.LONG), new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG) ], [Species.GLIGAR]: [ - new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SNEASEL]: [ - new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor claw at night*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor claw at night*/), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.URSARING]: [ new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna @@ -1391,8 +1392,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.MIME_JR]: [ - new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), SpeciesWildEvolutionDelay.MEDIUM), - new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY)), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), SpeciesWildEvolutionDelay.MEDIUM), + new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.PANSAGE]: [ new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1442,9 +1443,9 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CRABOMINABLE, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.ROCKRUFF]: [ - new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))), + new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))), new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)), - new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0))) + new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0))) ], [Species.STEENEE]: [ new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG) @@ -1471,15 +1472,15 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(Species.POLTEAGEIST, "antique", "antique", 1, EvolutionItem.CHIPPED_POT, null, SpeciesWildEvolutionDelay.LONG) ], [Species.MILCERY]: [ - new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.TOWN || p.scene.arena.biomeType === Biome.PLAINS || p.scene.arena.biomeType === Biome.GRASS || p.scene.arena.biomeType === Biome.TALL_GRASS || p.scene.arena.biomeType === Biome.METROPOLIS), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.BADLANDS || p.scene.arena.biomeType === Biome.VOLCANO || p.scene.arena.biomeType === Biome.GRAVEYARD || p.scene.arena.biomeType === Biome.FACTORY || p.scene.arena.biomeType === Biome.SLUM), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.FOREST || p.scene.arena.biomeType === Biome.SWAMP || p.scene.arena.biomeType === Biome.MEADOW || p.scene.arena.biomeType === Biome.JUNGLE), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.SEA || p.scene.arena.biomeType === Biome.BEACH || p.scene.arena.biomeType === Biome.LAKE || p.scene.arena.biomeType === Biome.SEABED), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.DESERT || p.scene.arena.biomeType === Biome.POWER_PLANT || p.scene.arena.biomeType === Biome.DOJO || p.scene.arena.biomeType === Biome.RUINS || p.scene.arena.biomeType === Biome.CONSTRUCTION_SITE), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.MOUNTAIN || p.scene.arena.biomeType === Biome.CAVE || p.scene.arena.biomeType === Biome.ICE_CAVE || p.scene.arena.biomeType === Biome.FAIRY_CAVE || p.scene.arena.biomeType === Biome.SNOWY_FOREST), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.WASTELAND || p.scene.arena.biomeType === Biome.LABORATORY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.TEMPLE || p.scene.arena.biomeType === Biome.ISLAND), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => p.scene.arena.biomeType === Biome.ABYSS || p.scene.arena.biomeType === Biome.SPACE || p.scene.arena.biomeType === Biome.END), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TOWN || globalScene.arena.biomeType === Biome.PLAINS || globalScene.arena.biomeType === Biome.GRASS || globalScene.arena.biomeType === Biome.TALL_GRASS || globalScene.arena.biomeType === Biome.METROPOLIS), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.BADLANDS || globalScene.arena.biomeType === Biome.VOLCANO || globalScene.arena.biomeType === Biome.GRAVEYARD || globalScene.arena.biomeType === Biome.FACTORY || globalScene.arena.biomeType === Biome.SLUM), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.FOREST || globalScene.arena.biomeType === Biome.SWAMP || globalScene.arena.biomeType === Biome.MEADOW || globalScene.arena.biomeType === Biome.JUNGLE), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.SEA || globalScene.arena.biomeType === Biome.BEACH || globalScene.arena.biomeType === Biome.LAKE || globalScene.arena.biomeType === Biome.SEABED), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.DESERT || globalScene.arena.biomeType === Biome.POWER_PLANT || globalScene.arena.biomeType === Biome.DOJO || globalScene.arena.biomeType === Biome.RUINS || globalScene.arena.biomeType === Biome.CONSTRUCTION_SITE), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.MOUNTAIN || globalScene.arena.biomeType === Biome.CAVE || globalScene.arena.biomeType === Biome.ICE_CAVE || globalScene.arena.biomeType === Biome.FAIRY_CAVE || globalScene.arena.biomeType === Biome.SNOWY_FOREST), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.WASTELAND || globalScene.arena.biomeType === Biome.LABORATORY), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TEMPLE || globalScene.arena.biomeType === Biome.ISLAND), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.ABYSS || globalScene.arena.biomeType === Biome.SPACE || globalScene.arena.biomeType === Biome.END), SpeciesWildEvolutionDelay.LONG) ], [Species.DURALUDON]: [ new SpeciesFormEvolution(Species.ARCHALUDON, "", "", 1, EvolutionItem.METAL_ALLOY, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1501,7 +1502,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG) ], [Species.HISUI_SNEASEL]: [ - new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.CHARCADET]: [ new SpeciesEvolution(Species.ARMAROUGE, 1, EvolutionItem.AUSPICIOUS_ARMOR, null, SpeciesWildEvolutionDelay.LONG), @@ -1581,10 +1582,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CONKELDURR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.KARRABLAST]: [ - new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.SHELMET].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.SHELMET].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SHELMET]: [ - new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!p.scene.gameData.dexData[Species.KARRABLAST].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.KARRABLAST].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SPRITZEE]: [ new SpeciesEvolution(Species.AROMATISSE, 1, EvolutionItem.SACHET, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1627,13 +1628,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.MARILL, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) ], [Species.BUDEW]: [ - new SpeciesEvolution(Species.ROSELIA, 1, null, new SpeciesFriendshipEvolutionCondition(70, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.ROSELIA, 1, null, new SpeciesFriendshipEvolutionCondition(70, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.SHORT) ], [Species.BUNEARY]: [ new SpeciesEvolution(Species.LOPUNNY, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.CHINGLING]: [ - new SpeciesEvolution(Species.CHIMECHO, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.CHIMECHO, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.HAPPINY]: [ new SpeciesEvolution(Species.CHANSEY, 1, null, new SpeciesFriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) @@ -1642,7 +1643,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.SNORLAX, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) ], [Species.RIOLU]: [ - new SpeciesEvolution(Species.LUCARIO, 1, null, new SpeciesFriendshipEvolutionCondition(120, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.LUCARIO, 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG) ], [Species.WOOBAT]: [ new SpeciesEvolution(Species.SWOOBAT, 1, null, new SpeciesFriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM) @@ -1657,16 +1658,16 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ALOLA_PERSIAN, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) ], [Species.SNOM]: [ - new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.GIMMIGHOUL]: [ new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier + + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier + + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) ] }; diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 26ad65bd9b0..c3da8db57c4 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -1,16 +1,14 @@ -//import { battleAnimRawData } from "./battle-anim-raw-data"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, MoveFlags, SelfStatusMove, allMoves } from "./move"; -import Pokemon from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; import * as Utils from "../utils"; -import { BattlerIndex } from "../battle"; -import { Element } from "json-stable-stringify"; +import type { BattlerIndex } from "../battle"; +import type { Element } from "json-stable-stringify"; import { Moves } from "#enums/moves"; import { SubstituteTag } from "./battler-tags"; import { isNullOrUndefined } from "../utils"; import Phaser from "phaser"; import { EncounterAnim } from "#enums/encounter-anims"; -//import fs from 'vite-plugin-fs/browser'; export enum AnimFrameTarget { USER, @@ -308,7 +306,7 @@ abstract class AnimTimedEvent { this.resourceName = resourceName; } - abstract execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer; + abstract execute(battleAnim: BattleAnim, priority?: number): integer; abstract getEventType(): string; } @@ -326,15 +324,15 @@ class AnimTimedSoundEvent extends AnimTimedEvent { } } - execute(scene: BattleScene, battleAnim: BattleAnim, priority?: number): integer { + execute(battleAnim: BattleAnim, priority?: number): integer { const soundConfig = { rate: (this.pitch * 0.01), volume: (this.volume * 0.01) }; if (this.resourceName) { try { - scene.playSound(`battle_anims/${this.resourceName}`, soundConfig); + globalScene.playSound(`battle_anims/${this.resourceName}`, soundConfig); } catch (err) { console.error(err); } - return Math.ceil((scene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33); + return Math.ceil((globalScene.sound.get(`battle_anims/${this.resourceName}`).totalDuration * 1000) / 33.33); } else { return Math.ceil((battleAnim.user!.cry(soundConfig).totalDuration * 1000) / 33.33); // TODO: is the bang behind user correct? } @@ -388,7 +386,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { super(frameIndex, resourceName, source); } - execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer { + execute(moveAnim: MoveAnim, priority?: number): integer { const tweenProps = {}; if (this.bgX !== undefined) { tweenProps["x"] = (this.bgX * 0.5) - 320; @@ -400,7 +398,7 @@ class AnimTimedUpdateBgEvent extends AnimTimedBgEvent { tweenProps["alpha"] = (this.opacity || 0) / 255; } if (Object.keys(tweenProps).length) { - scene.tweens.add(Object.assign({ + globalScene.tweens.add(Object.assign({ targets: moveAnim.bgSprite, duration: Utils.getFrameMs(this.duration * 3) }, tweenProps)); @@ -418,25 +416,25 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { super(frameIndex, resourceName, source); } - execute(scene: BattleScene, moveAnim: MoveAnim, priority?: number): integer { + execute(moveAnim: MoveAnim, priority?: number): integer { if (moveAnim.bgSprite) { moveAnim.bgSprite.destroy(); } moveAnim.bgSprite = this.resourceName - ? scene.add.tileSprite(this.bgX - 320, this.bgY - 284, 896, 576, this.resourceName) - : scene.add.rectangle(this.bgX - 320, this.bgY - 284, 896, 576, 0); + ? globalScene.add.tileSprite(this.bgX - 320, this.bgY - 284, 896, 576, this.resourceName) + : globalScene.add.rectangle(this.bgX - 320, this.bgY - 284, 896, 576, 0); moveAnim.bgSprite.setOrigin(0, 0); moveAnim.bgSprite.setScale(1.25); moveAnim.bgSprite.setAlpha(this.opacity / 255); - scene.field.add(moveAnim.bgSprite); - const fieldPokemon = scene.getEnemyPokemon(false) ?? scene.getPlayerPokemon(false); + globalScene.field.add(moveAnim.bgSprite); + const fieldPokemon = globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false); if (!isNullOrUndefined(priority)) { - scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority); + globalScene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority); } else if (fieldPokemon?.isOnField()) { - scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); + globalScene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); } - scene.tweens.add({ + globalScene.tweens.add({ targets: moveAnim.bgSprite, duration: Utils.getFrameMs(this.duration * 3) }); @@ -454,14 +452,14 @@ export const chargeAnims = new Map(); export const encounterAnims = new Map(); -export function initCommonAnims(scene: BattleScene): Promise { +export function initCommonAnims(): Promise { return new Promise(resolve => { const commonAnimNames = Utils.getEnumKeys(CommonAnim); const commonAnimIds = Utils.getEnumValues(CommonAnim); const commonAnimFetches: Promise>[] = []; for (let ca = 0; ca < commonAnimIds.length; ca++) { const commonAnimId = commonAnimIds[ca]; - commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`) + commonAnimFetches.push(globalScene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, "-")}.json`) .then(response => response.json()) .then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas)))); } @@ -469,7 +467,7 @@ export function initCommonAnims(scene: BattleScene): Promise { }); } -export function initMoveAnim(scene: BattleScene, move: Moves): Promise { +export function initMoveAnim(move: Moves): Promise { return new Promise(resolve => { if (moveAnims.has(move)) { if (moveAnims.get(move) !== null) { @@ -494,7 +492,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP; const fetchAnimAndResolve = (move: Moves) => { - scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`) + globalScene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`) .then(response => { const contentType = response.headers.get("content-type"); if (!response.ok || contentType?.indexOf("application/json") === -1) { @@ -516,7 +514,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { : (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); if (chargeAnimSource) { - initMoveChargeAnim(scene, chargeAnimSource.chargeAnim).then(() => resolve()); + initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve()); } else { resolve(); } @@ -557,10 +555,9 @@ function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) { /** * Fetches animation configs to be used in a Mystery Encounter - * @param scene * @param encounterAnim one or more animations to fetch */ -export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise { +export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise { const anims = Array.isArray(encounterAnim) ? encounterAnim : [ encounterAnim ]; const encounterAnimNames = Utils.getEnumKeys(EncounterAnim); const encounterAnimFetches: Promise>[] = []; @@ -568,14 +565,14 @@ export async function initEncounterAnims(scene: BattleScene, encounterAnim: Enco if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) { continue; } - encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`) + encounterAnimFetches.push(globalScene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`) .then(response => response.json()) .then(cas => encounterAnims.set(anim, new AnimConfig(cas)))); } await Promise.allSettled(encounterAnimFetches); } -export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise { +export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise { return new Promise(resolve => { if (chargeAnims.has(chargeAnim)) { if (chargeAnims.get(chargeAnim) !== null) { @@ -590,7 +587,7 @@ export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): } } else { chargeAnims.set(chargeAnim, null); - scene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`) + globalScene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, "-")}.json`) .then(response => response.json()) .then(ca => { if (Array.isArray(ca)) { @@ -623,23 +620,22 @@ function populateMoveChargeAnim(chargeAnim: ChargeAnim, animSource: any) { chargeAnims.set(chargeAnim, [ chargeAnims.get(chargeAnim) as AnimConfig, moveChargeAnim ]); } -export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise { +export function loadCommonAnimAssets(startLoad?: boolean): Promise { return new Promise(resolve => { - loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve()); + loadAnimAssets(Array.from(commonAnims.values()), startLoad).then(() => resolve()); }); } /** * Loads encounter animation assets to scene * MUST be called after {@linkcode initEncounterAnims()} to load all required animations properly - * @param scene * @param startLoad */ -export async function loadEncounterAnimAssets(scene: BattleScene, startLoad?: boolean): Promise { - await loadAnimAssets(scene, Array.from(encounterAnims.values()), startLoad); +export async function loadEncounterAnimAssets(startLoad?: boolean): Promise { + await loadAnimAssets(Array.from(encounterAnims.values()), startLoad); } -export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLoad?: boolean): Promise { +export function loadMoveAnimAssets(moveIds: Moves[], startLoad?: boolean): Promise { return new Promise(resolve => { const moveAnimations = moveIds.map(m => moveAnims.get(m) as AnimConfig).flat(); for (const moveId of moveIds) { @@ -655,11 +651,11 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo } } } - loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve()); + loadAnimAssets(moveAnimations, startLoad).then(() => resolve()); }); } -function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boolean): Promise { +function loadAnimAssets(anims: AnimConfig[], startLoad?: boolean): Promise { return new Promise(resolve => { const backgrounds = new Set(); const sounds = new Set(); @@ -676,19 +672,19 @@ function loadAnimAssets(scene: BattleScene, anims: AnimConfig[], startLoad?: boo backgrounds.add(abg); } if (a.graphic) { - scene.loadSpritesheet(a.graphic, "battle_anims", 96); + globalScene.loadSpritesheet(a.graphic, "battle_anims", 96); } } for (const bg of backgrounds) { - scene.loadImage(bg, "battle_anims"); + globalScene.loadImage(bg, "battle_anims"); } for (const s of sounds) { - scene.loadSe(s, "battle_anims", s); + globalScene.loadSe(s, "battle_anims", s); } if (startLoad) { - scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); - if (!scene.load.isLoading()) { - scene.load.start(); + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } } else { resolve(); @@ -778,7 +774,7 @@ export abstract class BattleAnim { return false; } - private getGraphicFrameData(scene: BattleScene, frames: AnimFrame[], onSubstitute?: boolean): Map> { + private getGraphicFrameData(frames: AnimFrame[], onSubstitute?: boolean): Map> { const ret: Map> = new Map([ [ AnimFrameTarget.GRAPHIC, new Map() ], [ AnimFrameTarget.USER, new Map() ], @@ -835,7 +831,7 @@ export abstract class BattleAnim { return ret; } - play(scene: BattleScene, onSubstitute?: boolean, callback?: Function) { + play(onSubstitute?: boolean, callback?: Function) { const isOppAnim = this.isOppAnim(); const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct? const target = !isOppAnim ? this.target! : this.user!; @@ -907,7 +903,7 @@ export abstract class BattleAnim { } }; - if (!scene.moveAnimations && !this.playRegardlessOfIssues) { + if (!globalScene.moveAnimations && !this.playRegardlessOfIssues) { return cleanUpAndComplete(); } @@ -924,7 +920,7 @@ export abstract class BattleAnim { let r = anim?.frames.length ?? 0; let f = 0; - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.getFrameMs(3), repeat: anim?.frames.length ?? 0, onRepeat: () => { @@ -934,7 +930,7 @@ export abstract class BattleAnim { } const spriteFrames = anim!.frames[f]; // TODO: is the bang correcT? - const frameData = this.getGraphicFrameData(scene, anim!.frames[f], onSubstitute); // TODO: is the bang correct? + const frameData = this.getGraphicFrameData(anim!.frames[f], onSubstitute); // TODO: is the bang correct? let u = 0; let t = 0; let g = 0; @@ -950,19 +946,19 @@ export abstract class BattleAnim { const spriteSource = isUser ? userSprite : targetSprite; if ((isUser ? u : t) === sprites.length) { if (isUser || !targetSubstitute) { - const sprite = scene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct? + const sprite = globalScene.addPokemonSprite(isUser ? user! : target, 0, 0, spriteSource!.texture, spriteSource!.frame.name, true); // TODO: are those bangs correct? [ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = (isUser ? user! : target).getSprite().pipelineData[k]); // TODO: are those bangs correct? sprite.setPipelineData("spriteKey", (isUser ? user! : target).getBattleSpriteKey()); sprite.setPipelineData("shiny", (isUser ? user : target).shiny); sprite.setPipelineData("variant", (isUser ? user : target).variant); sprite.setPipelineData("ignoreFieldPos", true); spriteSource.on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame)); - scene.field.add(sprite); + globalScene.field.add(sprite); sprites.push(sprite); } else { - const sprite = scene.addFieldSprite(spriteSource.x, spriteSource.y, spriteSource.texture); + const sprite = globalScene.addFieldSprite(spriteSource.x, spriteSource.y, spriteSource.texture); spriteSource.on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame)); - scene.field.add(sprite); + globalScene.field.add(sprite); sprites.push(sprite); } } @@ -987,9 +983,9 @@ export abstract class BattleAnim { } else { const sprites = spriteCache[AnimFrameTarget.GRAPHIC]; if (g === sprites.length) { - const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct? + const newSprite: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(0, 0, anim!.graphic, 1); // TODO: is the bang correct? sprites.push(newSprite); - scene.field.add(newSprite); + globalScene.field.add(newSprite); spritePriorities.push(1); } @@ -1000,22 +996,22 @@ export abstract class BattleAnim { const setSpritePriority = (priority: integer) => { switch (priority) { case 0: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getEnemyPokemon(false) ?? scene.getPlayerPokemon(false)!); // TODO: is this bang correct? + globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, globalScene.getEnemyPokemon(false) ?? globalScene.getPlayerPokemon(false)!); // TODO: is this bang correct? break; case 1: - scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); + globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1); break; case 2: switch (frame.focus) { case AnimFocus.USER: if (this.bgSprite) { - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); + globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); } else { - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? } break; case AnimFocus.TARGET: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + globalScene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? break; default: setSpritePriority(1); @@ -1025,10 +1021,10 @@ export abstract class BattleAnim { case 3: switch (frame.focus) { case AnimFocus.USER: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? break; case AnimFocus.TARGET: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + globalScene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? break; default: setSpritePriority(1); @@ -1056,7 +1052,7 @@ export abstract class BattleAnim { } if (anim?.frameTimedEvents.has(f)) { for (const event of anim.frameTimedEvents.get(f)!) { // TODO: is this bang correct? - r = Math.max((anim.frames.length - f) + event.execute(scene, this), r); + r = Math.max((anim.frames.length - f) + event.execute(this), r); } } const targets = Utils.getEnumValues(AnimFrameTarget); @@ -1086,7 +1082,7 @@ export abstract class BattleAnim { } } if (r) { - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.getFrameMs(r), onComplete: () => cleanUpAndComplete() }); @@ -1123,8 +1119,6 @@ export abstract class BattleAnim { } /** - * - * @param scene * @param targetInitialX * @param targetInitialY * @param frameTimeMult @@ -1135,7 +1129,7 @@ export abstract class BattleAnim { * - 5 is on top of player sprite * @param callback */ - playWithoutTargets(scene: BattleScene, targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) { + playWithoutTargets(targetInitialX: number, targetInitialY: number, frameTimeMult: number, frameTimedEventPriority?: 0 | 1 | 3 | 5, callback?: Function) { const spriteCache: SpriteCache = { [AnimFrameTarget.GRAPHIC]: [], [AnimFrameTarget.USER]: [], @@ -1156,7 +1150,7 @@ export abstract class BattleAnim { } }; - if (!scene.moveAnimations && !this.playRegardlessOfIssues) { + if (!globalScene.moveAnimations && !this.playRegardlessOfIssues) { return cleanUpAndComplete(); } @@ -1168,13 +1162,13 @@ export abstract class BattleAnim { let totalFrames = anim!.frames.length; let frameCount = 0; - let existingFieldSprites = scene.field.getAll().slice(0); + let existingFieldSprites = globalScene.field.getAll().slice(0); - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.getFrameMs(3) * frameTimeMult, repeat: anim!.frames.length, onRepeat: () => { - existingFieldSprites = scene.field.getAll().slice(0); + existingFieldSprites = globalScene.field.getAll().slice(0); const spriteFrames = anim!.frames[frameCount]; const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[frameCount], targetInitialX, targetInitialY); let graphicFrameCount = 0; @@ -1186,9 +1180,9 @@ export abstract class BattleAnim { const sprites = spriteCache[AnimFrameTarget.GRAPHIC]; if (graphicFrameCount === sprites.length) { - const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1); + const newSprite: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(0, 0, anim!.graphic, 1); sprites.push(newSprite); - scene.field.add(newSprite); + globalScene.field.add(newSprite); } const graphicIndex = graphicFrameCount++; @@ -1197,11 +1191,11 @@ export abstract class BattleAnim { const setSpritePriority = (priority: integer) => { if (existingFieldSprites.length > priority) { // Move to specified priority index - const index = scene.field.getIndex(existingFieldSprites[priority]); - scene.field.moveTo(moveSprite, index); + const index = globalScene.field.getIndex(existingFieldSprites[priority]); + globalScene.field.moveTo(moveSprite, index); } else { // Move to top of scene - scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); + globalScene.field.moveTo(moveSprite, globalScene.field.getAll().length - 1); } }; setSpritePriority(frame.priority); @@ -1221,7 +1215,7 @@ export abstract class BattleAnim { } if (anim?.frameTimedEvents.get(frameCount)) { for (const event of anim.frameTimedEvents.get(frameCount)!) { - totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(scene, this, frameTimedEventPriority), totalFrames); + totalFrames = Math.max((anim.frames.length - frameCount) + event.execute(this, frameTimedEventPriority), totalFrames); } } const targets = Utils.getEnumValues(AnimFrameTarget); @@ -1248,7 +1242,7 @@ export abstract class BattleAnim { } } if (totalFrames) { - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.getFrameMs(totalFrames), onComplete: () => cleanUpAndComplete() }); @@ -1282,7 +1276,7 @@ export class MoveAnim extends BattleAnim { public move: Moves; constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField: boolean = false) { - super(user, user.scene.getField()[target], playOnEmptyField); + super(user, globalScene.getField()[target], playOnEmptyField); this.move = move; } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 0c0b8e9e034..2743c36e7b5 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { allAbilities, applyAbAttrs, @@ -8,7 +8,8 @@ import { ReverseDrainAbAttr } from "#app/data/ability"; import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; -import Move, { +import type Move from "#app/data/move"; +import { allMoves, applyMoveAttrs, ConsecutiveUseDoublePowerAttr, @@ -21,14 +22,16 @@ import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { Type } from "#enums/type"; -import Pokemon, { HitResult, MoveResult } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import { StatStageChangeCallback, StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; +import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import i18next from "#app/plugins/i18n"; import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; @@ -110,11 +113,10 @@ export class BattlerTag { /** * Helper function that retrieves the source Pokemon object - * @param scene medium to retrieve the source Pokemon * @returns The source {@linkcode Pokemon} or `null` if none is found */ - public getSourcePokemon(scene: BattleScene): Pokemon | null { - return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + public getSourcePokemon(): Pokemon | null { + return this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; } } @@ -143,12 +145,12 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { // Cancel the affected pokemon's selected move - const phase = pokemon.scene.getCurrentPhase() as MovePhase; + const phase = globalScene.getCurrentPhase() as MovePhase; const move = phase.move; if (this.isMoveRestricted(move.moveId, pokemon)) { if (this.interruptedText(pokemon, move.moveId)) { - pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId)); + globalScene.queueMessage(this.interruptedText(pokemon, move.moveId)); } phase.cancel(); } @@ -280,14 +282,14 @@ export class DisabledTag extends MoveRestrictionBattlerTag { this.moveId = move.move; - pokemon.scene.queueMessage(i18next.t("battlerTags:disabledOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name })); + globalScene.queueMessage(i18next.t("battlerTags:disabledOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name })); } /** @override */ override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:disabledLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name })); + globalScene.queueMessage(i18next.t("battlerTags:disabledLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name })); } /** @override */ @@ -406,8 +408,8 @@ export class RechargingTag extends BattlerTag { /** Cancels the source's move this turn and queues a "__ must recharge!" message */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - pokemon.scene.queueMessage(i18next.t("battlerTags:rechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); + globalScene.queueMessage(i18next.t("battlerTags:rechargingLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + (globalScene.getCurrentPhase() as MovePhase).cancel(); pokemon.getMoveQueue().shift(); } return super.lapse(pokemon, lapseType); @@ -426,10 +428,10 @@ export class BeakBlastChargingTag extends BattlerTag { onAdd(pokemon: Pokemon): void { // Play Beak Blast's charging animation - new MoveChargeAnim(ChargeAnim.BEAK_BLAST_CHARGING, this.sourceMove, pokemon).play(pokemon.scene); + new MoveChargeAnim(ChargeAnim.BEAK_BLAST_CHARGING, this.sourceMove, pokemon).play(); // Queue Beak Blast's header message - pokemon.scene.queueMessage(i18next.t("moveTriggers:startedHeatingUpBeak", { pokemonName: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("moveTriggers:startedHeatingUpBeak", { pokemonName: getPokemonNameWithAffix(pokemon) })); } /** @@ -464,7 +466,7 @@ export class ShellTrapTag extends BattlerTag { } onAdd(pokemon: Pokemon): void { - pokemon.scene.queueMessage(i18next.t("moveTriggers:setUpShellTrap", { pokemonName: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("moveTriggers:setUpShellTrap", { pokemonName: getPokemonNameWithAffix(pokemon) })); } /** @@ -479,17 +481,17 @@ export class ShellTrapTag extends BattlerTag { // Trap should only be triggered by opponent's Physical moves if (phaseData?.move.category === MoveCategory.PHYSICAL && pokemon.isOpponent(phaseData.attacker)) { - const shellTrapPhaseIndex = pokemon.scene.phaseQueue.findIndex( + const shellTrapPhaseIndex = globalScene.phaseQueue.findIndex( phase => phase instanceof MovePhase && phase.pokemon === pokemon ); - const firstMovePhaseIndex = pokemon.scene.phaseQueue.findIndex( + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex( phase => phase instanceof MovePhase ); // Only shift MovePhase timing if it's not already next up if (shellTrapPhaseIndex !== -1 && shellTrapPhaseIndex !== firstMovePhaseIndex) { - const shellTrapMovePhase = pokemon.scene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; - pokemon.scene.prependToPhase(shellTrapMovePhase, MovePhase); + const shellTrapMovePhase = globalScene.phaseQueue.splice(shellTrapPhaseIndex, 1)[0]; + globalScene.prependToPhase(shellTrapMovePhase, MovePhase); } this.activated = true; @@ -508,7 +510,7 @@ export class TrappedTag extends BattlerTag { } canAdd(pokemon: Pokemon): boolean { - const source = pokemon.scene.getPokemonById(this.sourceId!)!; + const source = globalScene.getPokemonById(this.sourceId!)!; const move = allMoves[this.sourceMove]; const isGhost = pokemon.isOfType(Type.GHOST); @@ -521,13 +523,13 @@ export class TrappedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(this.getTrapMessage(pokemon)); + globalScene.queueMessage(this.getTrapMessage(pokemon)); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:trappedOnRemove", { + globalScene.queueMessage(i18next.t("battlerTags:trappedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() })); @@ -585,8 +587,8 @@ export class FlinchedTag extends BattlerTag { */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); - pokemon.scene.queueMessage(i18next.t("battlerTags:flinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + (globalScene.getCurrentPhase() as MovePhase).cancel(); + globalScene.queueMessage(i18next.t("battlerTags:flinchedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } return super.lapse(pokemon, lapseType); @@ -614,7 +616,7 @@ export class InterruptedTag extends BattlerTag { } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.getCurrentPhase() as MovePhase).cancel(); return super.lapse(pokemon, lapseType); } } @@ -628,44 +630,44 @@ export class ConfusedTag extends BattlerTag { } canAdd(pokemon: Pokemon): boolean { - return pokemon.scene.arena.terrain?.terrainType !== TerrainType.MISTY || !pokemon.isGrounded(); + return globalScene.arena.terrain?.terrainType !== TerrainType.MISTY || !pokemon.isGrounded(); } onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); - pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); + globalScene.queueMessage(i18next.t("battlerTags:confusedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:confusedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:confusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:confusedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM && super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); + globalScene.queueMessage(i18next.t("battlerTags:confusedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION)); // 1/3 chance of hitting self with a 40 base power move if (pokemon.randSeedInt(3) === 0) { const atk = pokemon.getEffectiveStat(Stat.ATK); const def = pokemon.getEffectiveStat(Stat.DEF); const damage = toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100)); - pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); + globalScene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself")); pokemon.damageAndUpdate(damage); pokemon.battleData.hitCount++; - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); + (globalScene.getCurrentPhase() as MovePhase).cancel(); } } @@ -700,7 +702,7 @@ export class DestinyBondTag extends BattlerTag { if (lapseType !== BattlerTagLapseType.CUSTOM) { return super.lapse(pokemon, lapseType); } - const source = this.sourceId ? pokemon.scene.getPokemonById(this.sourceId) : null; + const source = this.sourceId ? globalScene.getPokemonById(this.sourceId) : null; if (!source?.isFainted()) { return true; } @@ -710,11 +712,11 @@ export class DestinyBondTag extends BattlerTag { } if (pokemon.isBossImmune()) { - pokemon.scene.queueMessage(i18next.t("battlerTags:destinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:destinyBondLapseIsBoss", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); return false; } - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:destinyBondLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(source), pokemonNameWithAffix2: getPokemonNameWithAffix(pokemon) @@ -732,7 +734,7 @@ export class InfatuatedTag extends BattlerTag { canAdd(pokemon: Pokemon): boolean { if (this.sourceId) { - const pkm = pokemon.scene.getPokemonById(this.sourceId); + const pkm = globalScene.getPokemonById(this.sourceId); if (pkm) { return pokemon.isOppositeGender(pkm); @@ -749,10 +751,10 @@ export class InfatuatedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:infatuatedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct? + sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct? }) ); } @@ -760,24 +762,24 @@ export class InfatuatedTag extends BattlerTag { onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:infatuatedOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:infatuatedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct? + sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined) // TODO: is that bang correct? }) ); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT)); if (pokemon.randSeedInt(2)) { - pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); + globalScene.queueMessage(i18next.t("battlerTags:infatuatedLapseImmobilize", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + (globalScene.getCurrentPhase() as MovePhase).cancel(); } } @@ -787,7 +789,7 @@ export class InfatuatedTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:infatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:infatuatedOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } isSourceLinked(): boolean { @@ -822,8 +824,8 @@ export class SeedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:seededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? + globalScene.queueMessage(i18next.t("battlerTags:seededOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -836,11 +838,11 @@ export class SeedTag extends BattlerTag { applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); + globalScene.unshiftPhase(new CommonAnimPhase(source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED)); const damage = pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8)); const reverseDrain = pokemon.hasAbilityWithAttr(ReverseDrainAbAttr, false); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(source.getBattlerIndex(), !reverseDrain ? damage : damage * -1, !reverseDrain ? i18next.t("battlerTags:seededLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }) : i18next.t("battlerTags:seededLapseShed", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), false, true)); @@ -870,7 +872,7 @@ export class PowderTag extends BattlerTag { super.onAdd(pokemon); // "{Pokemon} is covered in powder!" - pokemon.scene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:powderOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } /** @@ -882,15 +884,15 @@ export class PowderTag extends BattlerTag { */ lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.PRE_MOVE) { - const movePhase = pokemon.scene.getCurrentPhase(); + const movePhase = globalScene.getCurrentPhase(); if (movePhase instanceof MovePhase) { const move = movePhase.move.getMove(); - const weather = pokemon.scene.arena.weather; - if (pokemon.getMoveType(move) === Type.FIRE && !(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed(pokemon.scene))) { + const weather = globalScene.arena.weather; + if (pokemon.getMoveType(move) === Type.FIRE && !(weather && weather.weatherType === WeatherType.HEAVY_RAIN && !weather.isEffectSuppressed())) { movePhase.fail(); movePhase.showMoveText(); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.POWDER)); const cancelDamage = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelDamage); @@ -899,7 +901,7 @@ export class PowderTag extends BattlerTag { } // "When the flame touched the powder\non the Pokémon, it exploded!" - pokemon.scene.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); + globalScene.queueMessage(i18next.t("battlerTags:powderLapse", { moveName: move.name })); } } return true; @@ -917,21 +919,21 @@ export class NightmareTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:nightmareOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } onOverlap(pokemon: Pokemon): void { super.onOverlap(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:nightmareOnOverlap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type + globalScene.queueMessage(i18next.t("battlerTags:nightmareLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1014,15 +1016,15 @@ export class EncoreTag extends MoveRestrictionBattlerTag { onAdd(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:encoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:encoreOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - const movePhase = pokemon.scene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon); + const movePhase = globalScene.findPhase(m => m instanceof MovePhase && m.pokemon === pokemon); if (movePhase) { const movesetMove = pokemon.getMoveset().find(m => m!.moveId === this.moveId); // TODO: is this bang correct? if (movesetMove) { const lastMove = pokemon.getLastXMoves(1)[0]; - pokemon.scene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon), - new MovePhase(pokemon.scene, pokemon, lastMove.targets!, movesetMove)); // TODO: is this bang correct? + globalScene.tryReplacePhase((m => m instanceof MovePhase && m.pokemon === pokemon), + new MovePhase(pokemon, lastMove.targets!, movesetMove)); // TODO: is this bang correct? } } } @@ -1063,7 +1065,7 @@ export class EncoreTag extends MoveRestrictionBattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:encoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:encoreOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -1073,9 +1075,9 @@ export class HelpingHandTag extends BattlerTag { } onAdd(pokemon: Pokemon): void { - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:helpingHandOnAdd", { - pokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + pokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? pokemonName: getPokemonNameWithAffix(pokemon) }) ); @@ -1106,9 +1108,8 @@ export class IngrainTag extends TrappedTag { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.unshiftPhase( + globalScene.unshiftPhase( new PokemonHealPhase( - pokemon.scene, pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:ingrainLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), @@ -1142,7 +1143,7 @@ export class OctolockTag extends TrappedTag { const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (shouldLapse) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.DEF, Stat.SPDEF ], -1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), false, [ Stat.DEF, Stat.SPDEF ], -1)); return true; } @@ -1158,16 +1159,15 @@ export class AquaRingTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:aquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:aquaRingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.unshiftPhase( + globalScene.unshiftPhase( new PokemonHealPhase( - pokemon.scene, pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16), i18next.t("battlerTags:aquaRingLapse", { @@ -1206,13 +1206,13 @@ export class DrowsyTag extends BattlerTag { } canAdd(pokemon: Pokemon): boolean { - return pokemon.scene.arena.terrain?.terrainType !== TerrainType.ELECTRIC || !pokemon.isGrounded(); + return globalScene.arena.terrain?.terrainType !== TerrainType.ELECTRIC || !pokemon.isGrounded(); } onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:drowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:drowsyOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -1255,13 +1255,13 @@ export abstract class DamagingTrapTag extends TrappedTag { const ret = super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:damagingTrapLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() }) ); - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), undefined, this.commonAnim)); const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1283,7 +1283,7 @@ export class BindTag extends DamagingTrapTag { getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:bindOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? moveName: this.getMoveName() }); } @@ -1297,7 +1297,7 @@ export class WrapTag extends DamagingTrapTag { getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:wrapOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonName: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? }); } } @@ -1331,7 +1331,7 @@ export class ClampTag extends DamagingTrapTag { getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:clampOnTrap", { - sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? pokemonName: getPokemonNameWithAffix(pokemon), }); } @@ -1378,7 +1378,7 @@ export class ThunderCageTag extends DamagingTrapTag { getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:thunderCageOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? }); } } @@ -1391,7 +1391,7 @@ export class InfestationTag extends DamagingTrapTag { getTrapMessage(pokemon: Pokemon): string { return i18next.t("battlerTags:infestationOnTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), - sourcePokemonNameWithAffix: getPokemonNameWithAffix(pokemon.scene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? + sourcePokemonNameWithAffix: getPokemonNameWithAffix(globalScene.getPokemonById(this.sourceId!) ?? undefined), // TODO: is that bang correct? }); } } @@ -1405,16 +1405,16 @@ export class ProtectedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:protectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:protectedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { - new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(pokemon.scene); - pokemon.scene.queueMessage(i18next.t("battlerTags:protectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(); + globalScene.queueMessage(i18next.t("battlerTags:protectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Stop multi-hit moves early - const effectPhase = pokemon.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { effectPhase.stopMultiHit(pokemon); } @@ -1454,7 +1454,7 @@ export class ContactDamageProtectedTag extends ProtectedTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { @@ -1496,10 +1496,10 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels)); + globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ this.stat ], this.levels)); } } @@ -1516,7 +1516,7 @@ export class ContactPoisonProtectedTag extends ProtectedTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); attacker.trySetStatus(StatusEffect.POISON, true, pokemon); @@ -1540,7 +1540,7 @@ export class ContactBurnProtectedTag extends DamageProtectedTag { const ret = super.lapse(pokemon, lapseType); if (lapseType === BattlerTagLapseType.CUSTOM) { - const effectPhase = pokemon.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); attacker.trySetStatus(StatusEffect.BURN, true); @@ -1564,12 +1564,12 @@ export class EnduringTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:enduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:enduringOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { - pokemon.scene.queueMessage(i18next.t("battlerTags:enduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:enduringLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); return true; } @@ -1584,7 +1584,7 @@ export class SturdyTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { if (lapseType === BattlerTagLapseType.CUSTOM) { - pokemon.scene.queueMessage(i18next.t("battlerTags:sturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:sturdyLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); return true; } @@ -1605,7 +1605,7 @@ export class PerishSongTag extends BattlerTag { const ret = super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:perishSongLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), turnCount: this.turnCount @@ -1634,7 +1634,7 @@ export class CenterOfAttentionTag extends BattlerTag { /** "Center of Attention" can't be added if an ally is already the Center of Attention. */ canAdd(pokemon: Pokemon): boolean { - const activeTeam = pokemon.isPlayer() ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField(); + const activeTeam = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); return !activeTeam.find(p => p.getTag(BattlerTagType.CENTER_OF_ATTENTION)); } @@ -1642,7 +1642,7 @@ export class CenterOfAttentionTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:centerOfAttentionOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:centerOfAttentionOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -1695,9 +1695,9 @@ export class TruantTag extends AbilityBattlerTag { const lastMove = pokemon.getLastXMoves().find(() => true); if (lastMove && lastMove.move !== Moves.NONE) { - (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, passive)); - pokemon.scene.queueMessage(i18next.t("battlerTags:truantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + (globalScene.getCurrentPhase() as MovePhase).cancel(); + globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); + globalScene.queueMessage(i18next.t("battlerTags:truantLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } return true; @@ -1712,7 +1712,7 @@ export class SlowStartTag extends AbilityBattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:slowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null, true); + globalScene.queueMessage(i18next.t("battlerTags:slowStartOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null, true); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -1726,7 +1726,7 @@ export class SlowStartTag extends AbilityBattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:slowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null); + globalScene.queueMessage(i18next.t("battlerTags:slowStartOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null); } } @@ -1772,13 +1772,13 @@ export class HighestStatBoostTag extends AbilityBattlerTag { break; } - pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); + globalScene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name })); + globalScene.queueMessage(i18next.t("battlerTags:highestStatBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name })); } } @@ -1831,7 +1831,7 @@ export class SemiInvulnerableTag extends BattlerTag { onRemove(pokemon: Pokemon): void { // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible - pokemon.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: getFrameMs(2), onComplete: () => pokemon.setVisible(true) }); @@ -1871,7 +1871,7 @@ export class FloatingTag extends TypeImmuneTag { super.onAdd(pokemon); if (this.sourceMove === Moves.MAGNET_RISE) { - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -1879,7 +1879,7 @@ export class FloatingTag extends TypeImmuneTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); if (this.sourceMove === Moves.MAGNET_RISE) { - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } } @@ -1921,7 +1921,7 @@ export class CritBoostTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:critBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:critBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -1931,7 +1931,7 @@ export class CritBoostTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:critBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:critBoostOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -1973,15 +1973,15 @@ export class SaltCuredTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:saltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? + globalScene.queueMessage(i18next.t("battlerTags:saltCuredOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); @@ -1990,7 +1990,7 @@ export class SaltCuredTag extends BattlerTag { const pokemonSteelOrWater = pokemon.isOfType(Type.STEEL) || pokemon.isOfType(Type.WATER); pokemon.damageAndUpdate(toDmgValue(pokemonSteelOrWater ? pokemon.getMaxHp() / 4 : pokemon.getMaxHp() / 8)); - pokemon.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:saltCuredLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: this.getMoveName() @@ -2021,21 +2021,21 @@ export class CursedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - this.sourceIndex = pokemon.scene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? + this.sourceIndex = globalScene.getPokemonById(this.sourceId!)!.getBattlerIndex(); // TODO: are those bangs correct? } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); if (ret) { - pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); + globalScene.unshiftPhase(new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.SALT_CURE)); const cancelled = new BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 4)); - pokemon.scene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:cursedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2149,7 +2149,7 @@ export class FormBlockDamageTag extends BattlerTag { super.onAdd(pokemon); if (pokemon.formIndex !== 0) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); } } @@ -2161,7 +2161,7 @@ export class FormBlockDamageTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); } } /** Provides the additional weather-based effects of the Ice Face ability */ @@ -2176,7 +2176,7 @@ export class IceFaceBlockDamageTag extends FormBlockDamageTag { * @returns {boolean} True if the tag can be added, false otherwise. */ canAdd(pokemon: Pokemon): boolean { - const weatherType = pokemon.scene.arena.weather?.weatherType; + const weatherType = globalScene.arena.weather?.weatherType; const isWeatherSnowOrHail = weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW; return super.canAdd(pokemon) || isWeatherSnowOrHail; @@ -2200,16 +2200,14 @@ export class CommandedTag extends BattlerTag { /** Caches the Tatsugiri's form key and sharply boosts the tagged Pokemon's stats */ override onAdd(pokemon: Pokemon): void { - this._tatsugiriFormKey = this.getSourcePokemon(pokemon.scene)?.getFormKey() ?? "curly"; - pokemon.scene.unshiftPhase(new StatStageChangePhase( - pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2 - )); + this._tatsugiriFormKey = this.getSourcePokemon()?.getFormKey() ?? "curly"; + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2)); } /** Triggers an {@linkcode PokemonAnimType | animation} of the tagged Pokemon "spitting out" Tatsugiri */ override onRemove(pokemon: Pokemon): void { - if (this.getSourcePokemon(pokemon.scene)?.isActive(true)) { - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_REMOVE); + if (this.getSourcePokemon()?.isActive(true)) { + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.COMMANDER_REMOVE); } } @@ -2271,14 +2269,14 @@ export class StockpilingTag extends BattlerTag { if (this.stockpiledCount < 3) { this.stockpiledCount++; - pokemon.scene.queueMessage(i18next.t("battlerTags:stockpilingOnAdd", { + globalScene.queueMessage(i18next.t("battlerTags:stockpilingOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), stockpiledCount: this.stockpiledCount })); // Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes. - pokemon.scene.unshiftPhase(new StatStageChangePhase( - pokemon.scene, pokemon.getBattlerIndex(), true, + globalScene.unshiftPhase(new StatStageChangePhase( + pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.DEF ], 1, true, false, true, this.onStatStagesChanged )); } @@ -2297,11 +2295,11 @@ export class StockpilingTag extends BattlerTag { const spDefChange = this.statChangeCounts[Stat.SPDEF]; if (defChange) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF ], -defChange, true, false, true)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.DEF ], -defChange, true, false, true)); } if (spDefChange) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF ], -spDefChange, true, false, true)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF ], -spDefChange, true, false, true)); } } } @@ -2320,7 +2318,7 @@ export class GulpMissileTag extends BattlerTag { return true; } - const moveEffectPhase = pokemon.scene.getCurrentPhase(); + const moveEffectPhase = globalScene.getCurrentPhase(); if (moveEffectPhase instanceof MoveEffectPhase) { const attacker = moveEffectPhase.getUserPokemon(); @@ -2340,7 +2338,7 @@ export class GulpMissileTag extends BattlerTag { } if (this.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ Stat.DEF ], -1)); + globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [ Stat.DEF ], -1)); } else { attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon); } @@ -2363,12 +2361,12 @@ export class GulpMissileTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); } } @@ -2476,7 +2474,7 @@ export class HealBlockTag extends MoveRestrictionBattlerTag { override onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHealBlockOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null); + globalScene.queueMessage(i18next.t("battle:battlerTagsHealBlockOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, false, null); } } @@ -2499,7 +2497,7 @@ export class TarShotTag extends BattlerTag { } override onAdd(pokemon: Pokemon): void { - pokemon.scene.queueMessage(i18next.t("battlerTags:tarShotOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:tarShotOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2514,7 +2512,7 @@ export class ElectrifiedTag extends BattlerTag { override onAdd(pokemon: Pokemon): void { // "{pokemonNameWithAffix}'s moves have been electrified!" - pokemon.scene.queueMessage(i18next.t("battlerTags:electrifiedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:electrifiedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2536,7 +2534,7 @@ export class AutotomizedTag extends BattlerTag { onAdd(pokemon: Pokemon): void { const minWeight = 0.1; if (pokemon.getWeight() > minWeight) { - pokemon.scene.queueMessage(i18next.t("battlerTags:autotomizeOnAdd", { + globalScene.queueMessage(i18next.t("battlerTags:autotomizeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } @@ -2567,15 +2565,15 @@ export class SubstituteTag extends BattlerTag { /** Sets the Substitute's HP and queues an on-add battle animation that initializes the Substitute's sprite. */ onAdd(pokemon: Pokemon): void { - this.hp = Math.floor(pokemon.scene.getPokemonById(this.sourceId!)!.getMaxHp() / 4); + this.hp = Math.floor(globalScene.getPokemonById(this.sourceId!)!.getMaxHp() / 4); this.sourceInFocus = false; // Queue battle animation and message - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_ADD); + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_ADD); if (this.sourceMove === Moves.SHED_TAIL) { - pokemon.scene.queueMessage(i18next.t("battlerTags:shedTailOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + globalScene.queueMessage(i18next.t("battlerTags:shedTailOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } else { - pokemon.scene.queueMessage(i18next.t("battlerTags:substituteOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + globalScene.queueMessage(i18next.t("battlerTags:substituteOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } // Remove any binding effects from the user @@ -2586,11 +2584,11 @@ export class SubstituteTag extends BattlerTag { onRemove(pokemon: Pokemon): void { // Only play the animation if the cause of removal isn't from the source's own move if (!this.sourceInFocus) { - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_REMOVE, [ this.sprite ]); + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_REMOVE, [ this.sprite ]); } else { this.sprite.destroy(); } - pokemon.scene.queueMessage(i18next.t("battlerTags:substituteOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:substituteOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { @@ -2610,19 +2608,19 @@ export class SubstituteTag extends BattlerTag { /** Triggers an animation that brings the Pokemon into focus before it uses a move */ onPreMove(pokemon: Pokemon): void { - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_PRE_MOVE, [ this.sprite ]); + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_PRE_MOVE, [ this.sprite ]); this.sourceInFocus = true; } /** Triggers an animation that brings the Pokemon out of focus after it uses a move */ onAfterMove(pokemon: Pokemon): void { - pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_POST_MOVE, [ this.sprite ]); + globalScene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_POST_MOVE, [ this.sprite ]); this.sourceInFocus = false; } /** If the Substitute redirects damage, queue a message to indicate it. */ onHit(pokemon: Pokemon): void { - const moveEffectPhase = pokemon.scene.getCurrentPhase(); + const moveEffectPhase = globalScene.getCurrentPhase(); if (moveEffectPhase instanceof MoveEffectPhase) { const attacker = moveEffectPhase.getUserPokemon(); if (!attacker) { @@ -2632,7 +2630,7 @@ export class SubstituteTag extends BattlerTag { const firstHit = (attacker.turnData.hitCount === attacker.turnData.hitsLeft); if (firstHit && move.hitsSubstitute(attacker, pokemon)) { - pokemon.scene.queueMessage(i18next.t("battlerTags:substituteOnHit", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:substituteOnHit", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } } @@ -2704,7 +2702,7 @@ export class TormentTag extends MoveRestrictionBattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + globalScene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } /** @@ -2758,7 +2756,7 @@ export class TauntTag extends MoveRestrictionBattlerTag { override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); + globalScene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } /** @@ -2796,7 +2794,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * @returns `true` if the source is still active */ public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { - const source = this.getSourcePokemon(pokemon.scene); + const source = this.getSourcePokemon(); if (source) { if (lapseType === BattlerTagLapseType.PRE_MOVE) { return super.lapse(pokemon, lapseType) && source.isActive(true); @@ -2814,7 +2812,7 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { * @returns `false` if either condition is not met */ public override isMoveRestricted(move: Moves, user: Pokemon): boolean { - const source = this.getSourcePokemon(user.scene); + const source = this.getSourcePokemon(); if (source) { const sourceMoveset = source.getMoveset().map(m => m!.moveId); return sourceMoveset?.includes(move) && source.isActive(true); @@ -2847,7 +2845,7 @@ export class SyrupBombTag extends BattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:syrupBombOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } /** @@ -2857,13 +2855,13 @@ export class SyrupBombTag extends BattlerTag { * @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) { + if (this.sourceId && !globalScene.getPokemonById(this.sourceId)?.isActive(true)) { return false; } // Custom message in lieu of an animation in mainline - pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); - pokemon.scene.unshiftPhase(new StatStageChangePhase( - pokemon.scene, pokemon.getBattlerIndex(), true, + globalScene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.unshiftPhase(new StatStageChangePhase( + pokemon.getBattlerIndex(), true, [ Stat.SPD ], -1, true, false, true )); return --this.turnCount > 0; @@ -2882,7 +2880,7 @@ export class TelekinesisTag extends BattlerTag { } override onAdd(pokemon: Pokemon) { - pokemon.scene.queueMessage(i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } } @@ -2897,12 +2895,12 @@ export class PowerTrickTag extends BattlerTag { onAdd(pokemon: Pokemon): void { this.swapStat(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } onRemove(pokemon: Pokemon): void { this.swapStat(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } /** @@ -2936,7 +2934,7 @@ export class GrudgeTag extends BattlerTag { onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:grudgeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("battlerTags:grudgeOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } /** @@ -2953,7 +2951,7 @@ export class GrudgeTag extends BattlerTag { const lastMoveData = sourcePokemon.getMoveset().find(m => m?.moveId === lastMove.move); if (lastMoveData && lastMove.move !== Moves.STRUGGLE) { lastMoveData.ppUsed = lastMoveData.getMovePp(); - pokemon.scene.queueMessage(i18next.t("battlerTags:grudgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: lastMoveData.getName() })); + globalScene.queueMessage(i18next.t("battlerTags:grudgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: lastMoveData.getName() })); } } return false; @@ -2977,7 +2975,7 @@ export class PsychoShiftTag extends BattlerTag { */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { if (pokemon.status && pokemon.isActive(true)) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.resetStatus(); pokemon.updateInfo(); } @@ -3199,7 +3197,7 @@ export function loadBattlerTag(source: BattlerTag | any): BattlerTag { * corresponding {@linkcode Move} and user {@linkcode Pokemon} */ function getMoveEffectPhaseData(pokemon: Pokemon): {phase: MoveEffectPhase, attacker: Pokemon, move: Move} | null { - const phase = pokemon.scene.getCurrentPhase(); + const phase = globalScene.getCurrentPhase(); if (phase instanceof MoveEffectPhase) { return { phase : phase, diff --git a/src/data/berry.ts b/src/data/berry.ts index dfd6a7ddcf0..06f52b2f38b 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,5 +1,6 @@ import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { HitResult } from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; +import { HitResult } from "../field/pokemon"; import { getStatusEffectHealText } from "./status-effect"; import * as Utils from "../utils"; import { DoubleBerryEffectAbAttr, PostItemLostAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs, applyPostItemLostAbAttrs } from "./ability"; @@ -9,6 +10,7 @@ import { BerryType } from "#enums/berry-type"; import { Stat, type BattleStat } from "#app/enums/stat"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import { globalScene } from "#app/global-scene"; export function getBerryName(berryType: BerryType): string { return i18next.t(`berry:${BerryType[berryType]}.name`); @@ -73,7 +75,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { } const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; @@ -83,7 +85,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { pokemon.battleData.berriesEaten.push(berryType); } if (pokemon.status) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); } pokemon.resetStatus(true, true); pokemon.updateInfo(); @@ -102,7 +104,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const stat: BattleStat = berryType - BerryType.ENIGMA; const statStages = new Utils.NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LANSAT: @@ -121,7 +123,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); const stages = new Utils.NumberHolder(2); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); }; case BerryType.LEPPA: @@ -132,7 +134,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? if (ppRestoreMove !== undefined) { ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); - pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); + globalScene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); applyPostItemLostAbAttrs(PostItemLostAbAttr, berryOwner ?? pokemon, false); } }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 4301ea7b375..b5eca55cb71 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,18 +1,22 @@ import * as Utils from "#app/utils"; import i18next from "i18next"; -import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import type { DexAttrProps, GameData } from "#app/system/game-data"; +import { defaultStarterSpecies } from "#app/system/game-data"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; -import { BattleType, FixedBattleConfig } from "#app/battle"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; +import type { FixedBattleConfig } from "#app/battle"; +import { BattleType } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; -import { GameMode } from "#app/game-mode"; +import type { GameMode } from "#app/game-mode"; import { Type } from "#enums/type"; import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { TypeColor, TypeShadow } from "#enums/color"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; @@ -467,7 +471,7 @@ export class SingleGenerationChallenge extends Challenge { if (trainerTypes.length === 0) { return false; } else { - battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(scene => new Trainer(scene, trainerTypes[this.value - 1], TrainerVariant.DEFAULT)); + battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(trainerTypes[this.value - 1], TrainerVariant.DEFAULT)); return true; } } diff --git a/src/data/custom-pokemon-data.ts b/src/data/custom-pokemon-data.ts index 7bc884cff50..1c3bbbc3180 100644 --- a/src/data/custom-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -1,7 +1,7 @@ -import { Abilities } from "#enums/abilities"; -import { Type } from "#enums/type"; +import type { Abilities } from "#enums/abilities"; +import type { Type } from "#enums/type"; import { isNullOrUndefined } from "#app/utils"; -import { Nature } from "#enums/nature"; +import type { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index 506ea0471c6..b0ce38cebd2 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -1,10 +1,11 @@ import { PartyMemberStrength } from "#enums/party-member-strength"; -import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; +import type { Species } from "#enums/species"; +import { globalScene } from "#app/global-scene"; import { PlayerPokemon } from "#app/field/pokemon"; -import { Starter } from "#app/ui/starter-select-ui-handler"; +import type { Starter } from "#app/ui/starter-select-ui-handler"; import * as Utils from "#app/utils"; -import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; +import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; @@ -21,17 +22,17 @@ export function fetchDailyRunSeed(): Promise { }); } -export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] { +export function getDailyRunStarters(seed: string): Starter[] { const starters: Starter[] = []; - scene.executeWithSeedOffset(() => { - const startingLevel = scene.gameMode.getStartingLevel(); + globalScene.executeWithSeedOffset(() => { + const startingLevel = globalScene.gameMode.getStartingLevel(); if (/\d{18}$/.test(seed)) { for (let s = 0; s < 3; s++) { const offset = 6 + s * 6; const starterSpeciesForm = getPokemonSpeciesForm(parseInt(seed.slice(offset, offset + 4)) as Species, parseInt(seed.slice(offset + 4, offset + 6))); - starters.push(getDailyRunStarter(scene, starterSpeciesForm, startingLevel)); + starters.push(getDailyRunStarter(starterSpeciesForm, startingLevel)); } return; } @@ -48,17 +49,17 @@ export function getDailyRunStarters(scene: BattleScene, seed: string): Starter[] .filter(s => speciesStarterCosts[s] === cost); const randPkmSpecies = getPokemonSpecies(Utils.randSeedItem(costSpecies)); const starterSpecies = getPokemonSpecies(randPkmSpecies.getTrainerSpeciesForLevel(startingLevel, true, PartyMemberStrength.STRONGER)); - starters.push(getDailyRunStarter(scene, starterSpecies, startingLevel)); + starters.push(getDailyRunStarter(starterSpecies, startingLevel)); } }, 0, seed); return starters; } -function getDailyRunStarter(scene: BattleScene, starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter { +function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLevel: integer): Starter { const starterSpecies = starterSpeciesForm instanceof PokemonSpecies ? starterSpeciesForm : getPokemonSpecies(starterSpeciesForm.speciesId); const formIndex = starterSpeciesForm instanceof PokemonSpecies ? undefined : starterSpeciesForm.formIndex; - const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined); + const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, formIndex, undefined, undefined, undefined, undefined, undefined, undefined); const starter: Starter = { species: starterSpecies, dexAttr: pokemon.getDexAttr(), diff --git a/src/data/egg-hatch-data.ts b/src/data/egg-hatch-data.ts index ba553b55c4f..37ee9bede09 100644 --- a/src/data/egg-hatch-data.ts +++ b/src/data/egg-hatch-data.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; -import { DexEntry, StarterDataEntry } from "#app/system/game-data"; +import { globalScene } from "#app/global-scene"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { DexEntry, StarterDataEntry } from "#app/system/game-data"; /** * Stores data associated with a specific egg and the hatched pokemon @@ -17,11 +17,8 @@ export class EggHatchData { public dexEntryBeforeUpdate: DexEntry; /** stored copy of the hatched pokemon's starter entry before it was updated due to hatch */ public starterDataEntryBeforeUpdate: StarterDataEntry; - /** reference to the battle scene to get gamedata and update dex */ - private scene: BattleScene; - constructor(scene: BattleScene, pokemon: PlayerPokemon, eggMoveIndex: number) { - this.scene = scene; + constructor(pokemon: PlayerPokemon, eggMoveIndex: number) { this.pokemon = pokemon; this.eggMoveIndex = eggMoveIndex; } @@ -39,8 +36,8 @@ export class EggHatchData { * Used before updating the dex, so comparing the pokemon to these entries will show the new attributes */ setDex() { - const currDexEntry = this.scene.gameData.dexData[this.pokemon.species.speciesId]; - const currStarterDataEntry = this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()]; + const currDexEntry = globalScene.gameData.dexData[this.pokemon.species.speciesId]; + const currStarterDataEntry = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()]; this.dexEntryBeforeUpdate = { seenAttr: currDexEntry.seenAttr, caughtAttr: currDexEntry.caughtAttr, @@ -86,9 +83,9 @@ export class EggHatchData { */ updatePokemon(showMessage : boolean = false) { return new Promise(resolve => { - this.scene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => { - this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); - this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => { + globalScene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => { + globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); + globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => { this.setEggMoveUnlocked(value); resolve(); }); diff --git a/src/data/egg.ts b/src/data/egg.ts index 7f1deecc63f..2599994ecf6 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -1,11 +1,13 @@ -import BattleScene from "#app/battle-scene"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { VariantTier } from "#enums/variant-tier"; import * as Utils from "#app/utils"; import Overrides from "#app/overrides"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import i18next from "i18next"; import { EggTier } from "#enums/egg-type"; import { Species } from "#enums/species"; @@ -22,9 +24,8 @@ export interface IEggOptions { /** Timestamp when this egg got created */ timestamp?: number; /** - * Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applyed. + * Defines if the egg got pulled from a gacha or not. If true, egg pity and pull statistics will be applied. * Egg will be automaticly added to the game data. - * NEEDS `scene` `eggOption` to work. */ pulled?: boolean; /** @@ -32,7 +33,7 @@ export interface IEggOptions { * Will also define the text displayed in the egg list. */ sourceType?: EggSourceType; - /** Needs to be defined if `eggOption` pulled is defined or if no species or `isShiny` is defined since this will be needed to generate them. */ + /** Legacy field, kept for backwards-compatibility */ scene?: BattleScene; /** * Sets the tier of the egg. Only species of this tier can be hatched from this egg. @@ -41,10 +42,7 @@ export interface IEggOptions { tier?: EggTier; /** Sets how many waves it will take till this egg hatches. */ hatchWaves?: number; - /** - * Sets the exact species that will hatch from this egg. - * Needs `scene` `eggOption` if not provided. - */ + /** Sets the exact species that will hatch from this egg. */ species?: Species; /** Defines if the hatched pokemon will be a shiny. */ isShiny?: boolean; @@ -56,8 +54,7 @@ export interface IEggOptions { * Defines if the egg will hatch with the hidden ability of this species. * If no hidden ability exist, a random one will get choosen. */ - overrideHiddenAbility?: boolean, - + overrideHiddenAbility?: boolean; /** Can customize the message displayed for where the egg was obtained */ eggDescriptor?: string; } @@ -148,7 +145,7 @@ export class Egg { // If egg was pulled, check if egg pity needs to override the egg tier if (eggOptions?.pulled) { // Needs this._tier and this._sourceType to work - this.checkForPityTierOverrides(eggOptions.scene!); // TODO: is this bang correct? + this.checkForPityTierOverrides(); } this._id = eggOptions?.id ?? Utils.randInt(EGG_SEED, EGG_SEED * this._tier); @@ -160,7 +157,7 @@ export class Egg { // First roll shiny and variant so we can filter if species with an variant exist this._isShiny = eggOptions?.isShiny ?? (Overrides.EGG_SHINY_OVERRIDE || this.rollShiny()); this._variantTier = eggOptions?.variantTier ?? (Overrides.EGG_VARIANT_OVERRIDE ?? this.rollVariant()); - this._species = eggOptions?.species ?? this.rollSpecies(eggOptions!.scene!)!; // TODO: Are those bangs correct? + this._species = eggOptions?.species ?? this.rollSpecies()!; // TODO: Is this bang correct? this._overrideHiddenAbility = eggOptions?.overrideHiddenAbility ?? false; @@ -178,19 +175,15 @@ export class Egg { // Needs this._tier so it needs to be generated afer the tier override if bought from same species this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex(); if (eggOptions?.pulled) { - this.increasePullStatistic(eggOptions.scene!); // TODO: is this bang correct? - this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct? + this.increasePullStatistic(); + this.addEggToGameData(); } }; - if (eggOptions?.scene) { - const seedOverride = Utils.randomString(24); - eggOptions?.scene.executeWithSeedOffset(() => { - generateEggProperties(eggOptions); - }, 0, seedOverride); - } else { // For legacy eggs without scene + const seedOverride = Utils.randomString(24); + globalScene.executeWithSeedOffset(() => { generateEggProperties(eggOptions); - } + }, 0, seedOverride); this._eggDescriptor = eggOptions?.eggDescriptor; } @@ -212,14 +205,14 @@ export class Egg { } // Generates a PlayerPokemon from an egg - public generatePlayerPokemon(scene: BattleScene): PlayerPokemon { + public generatePlayerPokemon(): PlayerPokemon { let ret: PlayerPokemon; - const generatePlayerPokemonHelper = (scene: BattleScene) => { + const generatePlayerPokemonHelper = () => { // Legacy egg wants to hatch. Generate missing properties if (!this._species) { this._isShiny = this.rollShiny(); - this._species = this.rollSpecies(scene!)!; // TODO: are these bangs correct? + this._species = this.rollSpecies()!; // TODO: is this bang correct? } let pokemonSpecies = getPokemonSpecies(this._species); @@ -238,7 +231,7 @@ export class Egg { } // This function has way to many optional parameters - ret = scene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false); + ret = globalScene.addPlayerPokemon(pokemonSpecies, 1, abilityIndex, undefined, undefined, false); ret.shiny = this._isShiny; ret.variant = this._variantTier; @@ -250,16 +243,16 @@ export class Egg { }; ret = ret!; // Tell TS compiler it's defined now - scene.executeWithSeedOffset(() => { - generatePlayerPokemonHelper(scene); + globalScene.executeWithSeedOffset(() => { + generatePlayerPokemonHelper(); }, this._id, EGG_SEED.toString()); return ret; } // Doesn't need to be called if the egg got pulled by a gacha machiene - public addEggToGameData(scene: BattleScene): void { - scene.gameData.eggs.push(this); + public addEggToGameData(): void { + globalScene.gameData.eggs.push(this); } public getEggDescriptor(): string { @@ -291,12 +284,12 @@ export class Egg { return i18next.t("egg:hatchWavesMessageLongTime"); } - public getEggTypeDescriptor(scene: BattleScene): string { + public getEggTypeDescriptor(): string { switch (this.sourceType) { case EggSourceType.SAME_SPECIES_EGG: return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); case EggSourceType.GACHA_LEGENDARY: - return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; + return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.timestamp)).getName()})`; case EggSourceType.GACHA_SHINY: return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); case EggSourceType.GACHA_MOVE: @@ -356,8 +349,8 @@ export class Egg { return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY; } - private rollSpecies(scene: BattleScene): Species | null { - if (!scene) { + private rollSpecies(): Species | null { + if (!globalScene) { return null; } /** @@ -376,7 +369,7 @@ export class Egg { } else if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) { if (!Utils.randSeedInt(2)) { - return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp); + return getLegendaryGachaSpeciesForTimestamp(this.timestamp); } } @@ -410,8 +403,8 @@ export class Egg { .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); // If this is the 10th egg without unlocking something new, attempt to force it. - if (scene.gameData.unlockPity[this.tier] >= 9) { - const lockedPool = speciesPool.filter(s => !scene.gameData.dexData[s].caughtAttr && !scene.gameData.eggs.some(e => e.species === s)); + if (globalScene.gameData.unlockPity[this.tier] >= 9) { + const lockedPool = speciesPool.filter(s => !globalScene.gameData.dexData[s].caughtAttr && !globalScene.gameData.eggs.some(e => e.species === s)); if (lockedPool.length) { // Skip this if everything is unlocked speciesPool = lockedPool; } @@ -454,10 +447,10 @@ export class Egg { } species = species!; // tell TS compiled it's defined now! - if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) { - scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10); + if (globalScene.gameData.dexData[species].caughtAttr || globalScene.gameData.eggs.some(e => e.species === species)) { + globalScene.gameData.unlockPity[this.tier] = Math.min(globalScene.gameData.unlockPity[this.tier] + 1, 10); } else { - scene.gameData.unlockPity[this.tier] = 0; + globalScene.gameData.unlockPity[this.tier] = 0; } return species; @@ -465,7 +458,7 @@ export class Egg { /** * Rolls whether the egg is shiny or not. - * @returns True if the egg is shiny + * @returns `true` if the egg is shiny **/ private rollShiny(): boolean { let shinyChance = GACHA_DEFAULT_SHINY_RATE; @@ -485,6 +478,7 @@ export class Egg { // Uses the same logic as pokemon.generateVariant(). I would like to only have this logic in one // place but I don't want to touch the pokemon class. + // TODO: Remove this or replace the one in the Pokemon class. private rollVariant(): VariantTier { if (!this.isShiny) { return VariantTier.STANDARD; @@ -500,38 +494,38 @@ export class Egg { } } - private checkForPityTierOverrides(scene: BattleScene): void { + private checkForPityTierOverrides(): void { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; - scene.gameData.eggPity[EggTier.RARE] += 1; - scene.gameData.eggPity[EggTier.EPIC] += 1; - scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; + globalScene.gameData.eggPity[EggTier.RARE] += 1; + globalScene.gameData.eggPity[EggTier.EPIC] += 1; + globalScene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. - if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { + if (globalScene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.LEGENDARY; - } else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { + } else if (globalScene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.EPIC; - } else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { + } else if (globalScene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { this._tier = EggTier.RARE; } - scene.gameData.eggPity[this._tier] = 0; + globalScene.gameData.eggPity[this._tier] = 0; } - private increasePullStatistic(scene: BattleScene): void { - scene.gameData.gameStats.eggsPulled++; + private increasePullStatistic(): void { + globalScene.gameData.gameStats.eggsPulled++; if (this.isManaphyEgg()) { - scene.gameData.gameStats.manaphyEggsPulled++; + globalScene.gameData.gameStats.manaphyEggsPulled++; this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC); return; } switch (this.tier) { case EggTier.RARE: - scene.gameData.gameStats.rareEggsPulled++; + globalScene.gameData.gameStats.rareEggsPulled++; break; case EggTier.EPIC: - scene.gameData.gameStats.epicEggsPulled++; + globalScene.gameData.gameStats.epicEggsPulled++; break; case EggTier.LEGENDARY: - scene.gameData.gameStats.legendaryEggsPulled++; + globalScene.gameData.gameStats.legendaryEggsPulled++; break; } } @@ -552,7 +546,7 @@ export function getValidLegendaryGachaSpecies() : Species[] { .filter(s => getPokemonSpecies(s).isObtainable() && s !== Species.ETERNATUS); } -export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { +export function getLegendaryGachaSpeciesForTimestamp(timestamp: number): Species { const legendarySpecies = getValidLegendaryGachaSpecies(); let ret: Species; @@ -563,7 +557,7 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta const offset = Math.floor(Math.floor(dayTimestamp / 86400000) / legendarySpecies.length); // Cycle number const index = Math.floor(dayTimestamp / 86400000) % legendarySpecies.length; // Index within cycle - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { ret = Phaser.Math.RND.shuffle(legendarySpecies)[index]; }, offset, EGG_SEED.toString()); ret = ret!; // tell TS compiler it's diff --git a/src/data/move.ts b/src/data/move.ts index 98fb58b2d73..b83b2d06394 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,22 +1,27 @@ import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims"; import { CommandedTag, EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags"; import { getPokemonNameWithAffix } from "../messages"; -import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon"; +import type { AttackMoveResult, TurnMove } from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; +import { EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove } from "../field/pokemon"; import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect } from "./status-effect"; import { getTypeDamageMultiplier } from "./type"; import { Type } from "#enums/type"; -import { Constructor, NumberHolder } from "#app/utils"; +import type { Constructor } from "#app/utils"; +import { NumberHolder } from "#app/utils"; import * as Utils from "../utils"; import { WeatherType } from "#enums/weather-type"; -import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag"; +import type { ArenaTrapTag } from "./arena-tag"; +import { ArenaTagSide, WeakenMoveTypeTag } from "./arena-tag"; import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPostItemLostAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ChangeMovePriorityAbAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, InfiltratorAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, PostDamageForceSwitchAbAttr, PostItemLostAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability"; import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier"; -import { BattlerIndex, BattleType } from "../battle"; +import type { BattlerIndex } from "../battle"; +import { BattleType } from "../battle"; import { TerrainType } from "./terrain"; import { ModifierPoolType } from "#app/modifier/modifier-type"; import { Command } from "../ui/command-ui-handler"; import i18next from "i18next"; -import { Localizable } from "#app/interfaces/locales"; +import type { Localizable } from "#app/interfaces/locales"; import { getBerryEffectFunc } from "./berry"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; @@ -36,10 +41,11 @@ import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms"; -import { GameMode } from "#app/game-mode"; +import type { GameMode } from "#app/game-mode"; import { applyChallenges, ChallengeType } from "./challenge"; import { SwitchType } from "#enums/switch-type"; import { StatusEffect } from "enums/status-effect"; +import { globalScene } from "#app/global-scene"; export enum MoveCategory { PHYSICAL, @@ -716,7 +722,7 @@ export default class Move implements Localizable { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { let score = 0; - if (target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(target.scene) === target) { + if (target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target) { return 20 * (target.isPlayer() === user.isPlayer() ? -1 : 1); // always -20 with how the AI handles this score } @@ -748,10 +754,10 @@ export default class Move implements Localizable { const isOhko = this.hasAttr(OneHitKOAccuracyAttr); if (!isOhko) { - user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); } - if (user.scene.arena.weather?.weatherType === WeatherType.FOG) { + if (globalScene.arena.weather?.weatherType === WeatherType.FOG) { /** * The 0.9 multiplier is PokeRogue-only implementation, Bulbapedia uses 3/5 * See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog} @@ -759,7 +765,7 @@ export default class Move implements Localizable { moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9); } - if (!isOhko && user.scene.arena.getTag(ArenaTagType.GRAVITY)) { + if (!isOhko && globalScene.arena.getTag(ArenaTagType.GRAVITY)) { moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67); } @@ -784,7 +790,7 @@ export default class Move implements Localizable { applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { power.value = 60; } @@ -795,7 +801,7 @@ export default class Move implements Localizable { } const fieldAuras = new Set( - source.scene.getField(true) + globalScene.getField(true) .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => { const condition = attr.getCondition(); return (!condition || condition(p)); @@ -806,7 +812,7 @@ export default class Move implements Localizable { aura.applyPreAttack(source, null, simulated, target, this, [ power ]); } - const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField(); + const alliedField: Pokemon[] = source instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, simulated, power)); power.value *= typeChangeMovePowerMultiplier.value; @@ -819,8 +825,8 @@ export default class Move implements Localizable { applyMoveAttrs(VariablePowerAttr, source, target, this, power); if (!this.hasAttr(TypelessAttr)) { - source.scene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); - source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); + globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); + globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); } if (source.getTag(HelpingHandTag)) { @@ -982,7 +988,7 @@ function ChargeMove(Base: TBase) { * @param target the {@linkcode Pokemon} targeted by this move (optional) */ showChargeText(user: Pokemon, target?: Pokemon): void { - user.scene.queueMessage(this._chargeText + globalScene.queueMessage(this._chargeText .replace("{USER}", getPokemonNameWithAffix(user)) .replace("{TARGET}", getPokemonNameWithAffix(target)) ); @@ -1213,7 +1219,7 @@ export class MoveEffectAttr extends MoveAttr { if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); + globalScene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); } if (!selfEffect) { @@ -1253,7 +1259,7 @@ export class MessageHeaderAttr extends MoveHeaderAttr { : this.message(user, move); if (message) { - user.scene.queueMessage(message); + globalScene.queueMessage(message); return true; } return false; @@ -1306,7 +1312,7 @@ export class PreMoveMessageAttr extends MoveAttr { ? this.message as string : this.message(user, target, move); if (message) { - user.scene.queueMessage(message, 500); + globalScene.queueMessage(message, 500); return true; } return false; @@ -1562,7 +1568,7 @@ export class RecoilAttr extends MoveEffectAttr { } user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true, true); - user.scene.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:hitWithRecoil", { pokemonName: getPokemonNameWithAffix(user) })); user.turnData.damageTaken += recoilDamage; return true; @@ -1674,7 +1680,7 @@ export class HalfSacrificialAttr extends MoveEffectAttr { applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled); if (!cancelled.value) { user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), HitResult.OTHER, false, true, true); - user.scene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message + globalScene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", { pokemonName: getPokemonNameWithAffix(user) })); // Queue recoil message } return true; } @@ -1779,7 +1785,7 @@ export class HealAttr extends MoveEffectAttr { * This heals the target and shows the appropriate message. */ addHealPhase(target: Pokemon, healRatio: number) { - target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), Utils.toDmgValue(target.getMaxHp() * healRatio), i18next.t("moveTriggers:healHp", { pokemonName: getPokemonNameWithAffix(target) }), true, !this.showAnim)); } @@ -1819,11 +1825,11 @@ export class PartyStatusCureAttr extends MoveEffectAttr { if (!this.canApply(user, target, move, args)) { return false; } - const partyPokemon = user.isPlayer() ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); + const partyPokemon = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); partyPokemon.forEach(p => this.cureStatus(p, user.id)); if (this.message) { - user.scene.queueMessage(this.message); + globalScene.queueMessage(this.message); } return true; @@ -1842,7 +1848,7 @@ export class PartyStatusCureAttr extends MoveEffectAttr { pokemon.resetStatus(); pokemon.updateInfo(); } else { - pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); + globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, pokemon.getPassiveAbility()?.id === this.abilityCondition)); } } } @@ -1897,11 +1903,10 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { } // We don't know which party member will be chosen, so pick the highest max HP in the party - const maxPartyMemberHp = user.scene.getPlayerParty().map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0); + const maxPartyMemberHp = globalScene.getPlayerParty().map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0); - user.scene.pushPhase( + globalScene.pushPhase( new PokemonHealPhase( - user.scene, user.getBattlerIndex(), maxPartyMemberHp, i18next.t(this.moveMessage, { pokemonName: getPokemonNameWithAffix(user) }), @@ -1921,7 +1926,7 @@ export class SacrificialFullRestoreAttr extends SacrificialAttr { } getCondition(): MoveConditionFunc { - return (user, _target, _move) => user.scene.getPlayerParty().filter(p => p.isActive()).length > user.scene.currentBattle.getBattlerCount(); + return (user, _target, _move) => globalScene.getPlayerParty().filter(p => p.isActive()).length > globalScene.currentBattle.getBattlerCount(); } } @@ -1950,7 +1955,7 @@ export class IgnoreWeatherTypeDebuffAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const weatherModifier = args[0] as Utils.NumberHolder; //If the type-based attack power modifier due to weather (e.g. Water moves in Sun) is below 1, set it to 1 - if (user.scene.arena.weather?.weatherType === this.weather) { + if (globalScene.arena.weather?.weatherType === this.weather) { weatherModifier.value = Math.max(weatherModifier.value, 1); } return true; @@ -1964,8 +1969,8 @@ export abstract class WeatherHealAttr extends HealAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { let healRatio = 0.5; - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { - const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; + if (!globalScene.arena.weather?.isEffectSuppressed()) { + const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; healRatio = this.getWeatherHealRatio(weatherType); } this.addHealPhase(user, healRatio); @@ -2112,7 +2117,7 @@ export class HitHealAttr extends MoveEffectAttr { message = ""; } } - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true)); + globalScene.unshiftPhase(new PokemonHealPhase(user.getBattlerIndex(), healAmount, message, false, true)); return true; } @@ -2251,7 +2256,7 @@ export class MultiHitAttr extends MoveAttr { case MultiHitType._10: return 10; case MultiHitType.BEAT_UP: - const party = user.isPlayer() ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); + const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); // No status means the ally pokemon can contribute to Beat Up return party.reduce((total, pokemon) => { return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); @@ -2309,7 +2314,7 @@ export class StatusEffectAttr extends MoveEffectAttr { if (user !== target && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { - user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } return false; } @@ -2414,9 +2419,9 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct? const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)]; - user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { + globalScene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => { if (success) { - user.scene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); + globalScene.queueMessage(i18next.t("moveTriggers:stoleItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name })); } resolve(success); }); @@ -2428,7 +2433,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; } @@ -2496,13 +2501,13 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { // Decrease item amount and update icon target.loseHeldItem(removedItem); - target.scene.updateModifiers(target.isPlayer()); + globalScene.updateModifiers(target.isPlayer()); if (this.berriesOnly) { - user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.queueMessage(i18next.t("moveTriggers:incineratedItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } else { - user.scene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); + globalScene.queueMessage(i18next.t("moveTriggers:knockedOffItem", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name })); } } @@ -2510,7 +2515,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr { } getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] { - return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[]; } @@ -2552,7 +2557,7 @@ export class EatBerryAttr extends MoveEffectAttr { } this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; const preserve = new Utils.BooleanHolder(false); - target.scene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation + globalScene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation if (!preserve.value) { this.reduceBerryModifier(target); } @@ -2561,7 +2566,7 @@ export class EatBerryAttr extends MoveEffectAttr { } getTargetHeldBerries(target: Pokemon): BerryModifier[] { - return target.scene.findModifiers(m => m instanceof BerryModifier + return globalScene.findModifiers(m => m instanceof BerryModifier && (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[]; } @@ -2569,7 +2574,7 @@ export class EatBerryAttr extends MoveEffectAttr { if (this.chosenBerry) { target.loseHeldItem(this.chosenBerry); } - target.scene.updateModifiers(target.isPlayer()); + globalScene.updateModifiers(target.isPlayer()); } eatBerry(consumer: Pokemon, berryOwner?: Pokemon) { @@ -2612,7 +2617,7 @@ export class StealEatBerryAttr extends EatBerryAttr { this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)]; applyPostItemLostAbAttrs(PostItemLostAbAttr, target, false); const message = i18next.t("battle:stealEatBerry", { pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name }); - user.scene.queueMessage(message); + globalScene.queueMessage(message); this.reduceBerryModifier(target); this.eatBerry(user, target); return true; @@ -2660,7 +2665,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr { const pokemon = this.selfTarget ? user : target; if (pokemon.status && this.effects.includes(pokemon.status.effect)) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); pokemon.resetStatus(); pokemon.updateInfo(); @@ -2731,11 +2736,11 @@ export class WeatherChangeAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - return user.scene.arena.trySetWeather(this.weatherType, true); + return globalScene.arena.trySetWeather(this.weatherType, true); } getCondition(): MoveConditionFunc { - return (user, target, move) => !user.scene.arena.weather || (user.scene.arena.weather.weatherType !== this.weatherType && !user.scene.arena.weather.isImmutable()); + return (user, target, move) => !globalScene.arena.weather || (globalScene.arena.weather.weatherType !== this.weatherType && !globalScene.arena.weather.isImmutable()); } } @@ -2749,8 +2754,8 @@ export class ClearWeatherAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (user.scene.arena.weather?.weatherType === this.weatherType) { - return user.scene.arena.trySetWeather(WeatherType.NONE, true); + if (globalScene.arena.weather?.weatherType === this.weatherType) { + return globalScene.arena.trySetWeather(WeatherType.NONE, true); } return false; @@ -2767,16 +2772,16 @@ export class TerrainChangeAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - return user.scene.arena.trySetTerrain(this.terrainType, true, true); + return globalScene.arena.trySetTerrain(this.terrainType, true, true); } getCondition(): MoveConditionFunc { - return (user, target, move) => !user.scene.arena.terrain || (user.scene.arena.terrain.terrainType !== this.terrainType); + return (user, target, move) => !globalScene.arena.terrain || (globalScene.arena.terrain.terrainType !== this.terrainType); } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { // TODO: Expand on this - return user.scene.arena.terrain ? 0 : 6; + return globalScene.arena.terrain ? 0 : 6; } } @@ -2786,7 +2791,7 @@ export class ClearTerrainAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - return user.scene.arena.trySetTerrain(TerrainType.NONE, true, true); + return globalScene.arena.trySetTerrain(TerrainType.NONE, true, true); } } @@ -2855,12 +2860,12 @@ export class InstantChargeAttr extends MoveAttr { export class WeatherInstantChargeAttr extends InstantChargeAttr { constructor(weatherTypes: WeatherType[]) { super((user, move) => { - const currentWeather = user.scene.arena.weather; + const currentWeather = globalScene.arena.weather; if (Utils.isNullOrUndefined(currentWeather?.weatherType)) { return false; } else { - return !currentWeather?.isEffectSuppressed(user.scene) + return !currentWeather?.isEffectSuppressed() && weatherTypes.includes(currentWeather?.weatherType); } }); @@ -2904,16 +2909,16 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr { const side = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; return new Promise(resolve => { if (args.length < 2 || !args[1]) { - new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, false, () => { + new MoveChargeAnim(this.chargeAnim, move.id, user).play(false, () => { (args[0] as Utils.BooleanHolder).value = true; - user.scene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); + globalScene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user))); user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER }); - user.scene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); + globalScene.arena.addTag(this.tagType, 3, move.id, user.id, side, false, target.getBattlerIndex()); resolve(true); }); } else { - user.scene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(user.scene.getPokemonById(target.id) ?? undefined), moveName: move.name }), null, () => resolve(true)); + globalScene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", { pokemonName: getPokemonNameWithAffix(globalScene.getPokemonById(target.id) ?? undefined), moveName: move.name }), null, () => resolve(true)); } }); } @@ -2942,29 +2947,29 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (user.turnData.combiningPledge) { // "The two moves have become one!\nIt's a combined move!" - user.scene.queueMessage(i18next.t("moveTriggers:combiningPledge")); + globalScene.queueMessage(i18next.t("moveTriggers:combiningPledge")); return false; } const overridden = args[0] as Utils.BooleanHolder; - const allyMovePhase = user.scene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); + const allyMovePhase = globalScene.findPhase((phase) => phase instanceof MovePhase && phase.pokemon.isPlayer() === user.isPlayer()); if (allyMovePhase) { const allyMove = allyMovePhase.move.getMove(); if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { [ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id); // "{userPokemonName} is waiting for {allyPokemonName}'s move..." - user.scene.queueMessage(i18next.t("moveTriggers:awaitingPledge", { + globalScene.queueMessage(i18next.t("moveTriggers:awaitingPledge", { userPokemonName: getPokemonNameWithAffix(user), allyPokemonName: getPokemonNameWithAffix(allyMovePhase.pokemon) })); // Move the ally's MovePhase (if needed) so that the ally moves next - const allyMovePhaseIndex = user.scene.phaseQueue.indexOf(allyMovePhase); - const firstMovePhaseIndex = user.scene.phaseQueue.findIndex((phase) => phase instanceof MovePhase); + const allyMovePhaseIndex = globalScene.phaseQueue.indexOf(allyMovePhase); + const firstMovePhaseIndex = globalScene.phaseQueue.findIndex((phase) => phase instanceof MovePhase); if (allyMovePhaseIndex !== firstMovePhaseIndex) { - user.scene.prependToPhase(user.scene.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); + globalScene.prependToPhase(globalScene.phaseQueue.splice(allyMovePhaseIndex, 1)[0], MovePhase); } overridden.value = true; @@ -3058,7 +3063,7 @@ export class StatStageChangeAttr extends MoveEffectAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { const stages = this.getLevels(user); - user.scene.unshiftPhase(new StatStageChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage)); + globalScene.unshiftPhase(new StatStageChangePhase((this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, stages, this.showMessage)); return true; } @@ -3129,11 +3134,11 @@ export class SecretPowerAttr extends MoveEffectAttr { return false; } let secondaryEffect: MoveEffectAttr; - const terrain = user.scene.arena.getTerrainType(); + const terrain = globalScene.arena.getTerrainType(); if (terrain !== TerrainType.NONE) { secondaryEffect = this.determineTerrainEffect(terrain); } else { - const biome = user.scene.arena.biomeType; + const biome = globalScene.arena.biomeType; secondaryEffect = this.determineBiomeEffect(biome); } return secondaryEffect.apply(user, target, move, []); @@ -3283,7 +3288,7 @@ export class AcupressureStatStageChangeAttr extends MoveEffectAttr { const randStats = BATTLE_STATS.filter(s => target.getStatStage(s) < 6); if (randStats.length > 0) { const boostStat = [ randStats[user.randSeedInt(randStats.length)] ]; - user.scene.unshiftPhase(new StatStageChangePhase(user.scene, target.getBattlerIndex(), this.selfTarget, boostStat, 2)); + globalScene.unshiftPhase(new StatStageChangePhase(target.getBattlerIndex(), this.selfTarget, boostStat, 2)); return true; } return false; @@ -3296,8 +3301,8 @@ export class GrowthStatStageChangeAttr extends StatStageChangeAttr { } getLevels(user: Pokemon): number { - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { - const weatherType = user.scene.arena.weather?.weatherType; + if (!globalScene.arena.weather?.isEffectSuppressed()) { + const weatherType = globalScene.arena.weather?.weatherType; if (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN) { return this.stages + 1; } @@ -3365,7 +3370,7 @@ export class OrderUpStatBoostAttr extends MoveEffectAttr { break; } - user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); + globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), this.selfTarget, [ increasedStat ], 1)); return true; } } @@ -3388,7 +3393,7 @@ export class CopyStatsAttr extends MoveEffectAttr { } target.updateInfo(); user.updateInfo(); - target.scene.queueMessage(i18next.t("moveTriggers:copiedStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:copiedStatChanges", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); return true; } @@ -3407,7 +3412,7 @@ export class InvertStatsAttr extends MoveEffectAttr { target.updateInfo(); user.updateInfo(); - target.scene.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:invertStats", { pokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -3422,13 +3427,13 @@ export class ResetStatsAttr extends MoveEffectAttr { async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { const promises: Promise[] = []; if (this.targetAllPokemon) { // Target all pokemon on the field when Freezy Frost or Haze are used - const activePokemon = user.scene.getField(true); + const activePokemon = globalScene.getField(true); activePokemon.forEach(p => promises.push(this.resetStats(p))); - target.scene.queueMessage(i18next.t("moveTriggers:statEliminated")); + globalScene.queueMessage(i18next.t("moveTriggers:statEliminated")); } else { // Affects only the single target when Clear Smog is used if (!move.hitsSubstitute(user, target)) { promises.push(this.resetStats(target)); - target.scene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) })); } } @@ -3481,9 +3486,9 @@ export class SwapStatStagesAttr extends MoveEffectAttr { user.updateInfo(); if (this.stats.length === 7) { - user.scene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", { pokemonName: getPokemonNameWithAffix(user) })); } else if (this.stats.length === 2) { - user.scene.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { + globalScene.queueMessage(i18next.t("moveTriggers:switchedTwoStatChanges", { pokemonName: getPokemonNameWithAffix(user), firstStat: i18next.t(getStatKey(this.stats[0])), secondStat: i18next.t(getStatKey(this.stats[1])) @@ -3508,12 +3513,12 @@ export class HpSplitAttr extends MoveEffectAttr { if (user.hp < hpValue) { const healing = user.heal(hpValue - user.hp); if (healing) { - user.scene.damageNumberHandler.add(user, healing, HitResult.HEAL); + globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL); } } else if (user.hp > hpValue) { const damage = user.damage(user.hp - hpValue, true); if (damage) { - user.scene.damageNumberHandler.add(user, damage); + globalScene.damageNumberHandler.add(user, damage); } } infoUpdates.push(user.updateInfo()); @@ -3521,12 +3526,12 @@ export class HpSplitAttr extends MoveEffectAttr { if (target.hp < hpValue) { const healing = target.heal(hpValue - target.hp); if (healing) { - user.scene.damageNumberHandler.add(user, healing, HitResult.HEAL); + globalScene.damageNumberHandler.add(user, healing, HitResult.HEAL); } } else if (target.hp > hpValue) { const damage = target.damage(target.hp - hpValue, true); if (damage) { - target.scene.damageNumberHandler.add(target, damage); + globalScene.damageNumberHandler.add(target, damage); } } infoUpdates.push(target.updateInfo()); @@ -3609,7 +3614,7 @@ export class MovePowerMultiplierAttr extends VariablePowerAttr { * @returns The base power of the Beat Up hit. */ const beatUpFunc = (user: Pokemon, allyIndex: number): number => { - const party = user.isPlayer() ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); + const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); for (let i = allyIndex; i < party.length; i++) { const pokemon = party[i]; @@ -3637,7 +3642,7 @@ export class BeatUpAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const power = args[0] as Utils.NumberHolder; - const party = user.isPlayer() ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); + const party = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); const allyCount = party.filter(pokemon => { return pokemon.id === user.id || !pokemon.status?.effect; }).length; @@ -3649,19 +3654,19 @@ export class BeatUpAttr extends VariablePowerAttr { const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { let message: string = ""; - user.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const rand = Utils.randSeedInt(100); if (rand < move.chance) { message = i18next.t("moveTriggers:goingAllOutForAttack", { pokemonName: getPokemonNameWithAffix(user) }); } - }, user.scene.currentBattle.turn << 6, user.scene.waveSeed); + }, globalScene.currentBattle.turn << 6, globalScene.waveSeed); return message; }; export class DoublePowerChanceAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { let rand: integer; - user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed); + globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); if (rand! < move.chance) { const power = args[0] as Utils.NumberHolder; power.value *= 2; @@ -3895,8 +3900,8 @@ export class OpponentHighHpPowerAttr extends VariablePowerAttr { export class FirstAttackDoublePowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - console.log(target.getLastXMoves(1), target.scene.currentBattle.turn); - if (!target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn)) { + console.log(target.getLastXMoves(1), globalScene.currentBattle.turn); + if (!target.getLastXMoves(1).find(m => m.turn === globalScene.currentBattle.turn)) { (args[0] as Utils.NumberHolder).value *= 2; return true; } @@ -3919,7 +3924,7 @@ export class TurnDamagedDoublePowerAttr extends VariablePowerAttr { const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { let message: string; - user.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ]; const rand = Utils.randSeedInt(100); @@ -3932,7 +3937,7 @@ const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { } message = i18next.t("moveTriggers:magnitudeMessage", { magnitude: m + 4 }); - }, user.scene.currentBattle.turn << 6, user.scene.waveSeed); + }, globalScene.currentBattle.turn << 6, globalScene.waveSeed); return message!; }; @@ -3945,7 +3950,7 @@ export class MagnitudePowerAttr extends VariablePowerAttr { let rand: integer; - user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed); + globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), globalScene.currentBattle.turn << 6, globalScene.waveSeed); let m = 0; for (; m < magnitudeThresholds.length; m++) { @@ -3962,9 +3967,9 @@ export class MagnitudePowerAttr extends VariablePowerAttr { export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { + if (!globalScene.arena.weather?.isEffectSuppressed()) { const power = args[0] as Utils.NumberHolder; - const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; + const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.RAIN: case WeatherType.SANDSTORM: @@ -4080,7 +4085,7 @@ export class PresentPowerAttr extends VariablePowerAttr { } else if (80 < powerSeed && powerSeed <= 100) { // If this move is multi-hit, disable all other hits user.stopMultiHit(); - target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(), Utils.toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true)); } @@ -4230,7 +4235,7 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { pokemonActed.push(enemy); } - if (user.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { const userAlly = user.getAlly(); const enemyAlly = enemy?.getAlly(); @@ -4332,7 +4337,7 @@ export class CueNextRoundAttr extends MoveEffectAttr { } override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { - const nextRoundPhase = user.scene.findPhase(phase => + const nextRoundPhase = globalScene.findPhase(phase => phase instanceof MovePhase && phase.move.moveId === Moves.ROUND ); @@ -4341,10 +4346,10 @@ export class CueNextRoundAttr extends MoveEffectAttr { } // Update the phase queue so that the next Pokemon using Round moves next - const nextRoundIndex = user.scene.phaseQueue.indexOf(nextRoundPhase); - const nextMoveIndex = user.scene.phaseQueue.findIndex(phase => phase instanceof MovePhase); + const nextRoundIndex = globalScene.phaseQueue.indexOf(nextRoundPhase); + const nextMoveIndex = globalScene.phaseQueue.findIndex(phase => phase instanceof MovePhase); if (nextRoundIndex !== nextMoveIndex) { - user.scene.prependToPhase(user.scene.phaseQueue.splice(nextRoundIndex, 1)[0], MovePhase); + globalScene.prependToPhase(globalScene.phaseQueue.splice(nextRoundIndex, 1)[0], MovePhase); } // Mark the corresponding Pokemon as having "joined the Round" (for doubling power later) @@ -4419,9 +4424,9 @@ export class VariableAccuracyAttr extends MoveAttr { */ export class ThunderAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { + if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as Utils.NumberHolder; - const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; + const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.SUNNY: case WeatherType.HARSH_SUN: @@ -4445,9 +4450,9 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { */ export class StormAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { + if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as Utils.NumberHolder; - const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; + const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { case WeatherType.RAIN: case WeatherType.HEAVY_RAIN: @@ -4501,9 +4506,9 @@ export class ToxicAccuracyAttr extends VariableAccuracyAttr { export class BlizzardAccuracyAttr extends VariableAccuracyAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { + if (!globalScene.arena.weather?.isEffectSuppressed()) { const accuracy = args[0] as Utils.NumberHolder; - const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; + const weatherType = globalScene.arena.weather?.weatherType || WeatherType.NONE; if (weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW) { accuracy.value = -1; return true; @@ -4800,8 +4805,8 @@ export class WeatherBallTypeAttr extends VariableMoveTypeAttr { return false; } - if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { - switch (user.scene.arena.weather?.weatherType) { + if (!globalScene.arena.weather?.isEffectSuppressed()) { + switch (globalScene.arena.weather?.weatherType) { case WeatherType.SUNNY: case WeatherType.HARSH_SUN: moveType.value = Type.FIRE; @@ -4851,7 +4856,7 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { return false; } - const currentTerrain = user.scene.arena.getTerrainType(); + const currentTerrain = globalScene.arena.getTerrainType(); switch (currentTerrain) { case TerrainType.MISTY: moveType.value = Type.FAIRY; @@ -5146,7 +5151,7 @@ const crashDamageFunc = (user: Pokemon, move: Move) => { } user.damageAndUpdate(Utils.toDmgValue(user.getMaxHp() / 2), HitResult.OTHER, false, true); - user.scene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", { pokemonName: getPokemonNameWithAffix(user) })); user.turnData.damageTaken += Utils.toDmgValue(user.getMaxHp() / 2); return true; @@ -5436,12 +5441,12 @@ export class CurseAttr extends MoveEffectAttr { apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean { if (user.getTypes(true).includes(Type.GHOST)) { if (target.getTag(BattlerTagType.CURSED)) { - user.scene.queueMessage(i18next.t("battle:attackFailed")); + globalScene.queueMessage(i18next.t("battle:attackFailed")); return false; } const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2)); user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true); - user.scene.queueMessage( + globalScene.queueMessage( i18next.t("battlerTags:cursedOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user), pokemonName: getPokemonNameWithAffix(target) @@ -5451,8 +5456,8 @@ export class CurseAttr extends MoveEffectAttr { target.addTag(BattlerTagType.CURSED, 0, move.id, user.id); return true; } else { - user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); - user.scene.unshiftPhase(new StatStageChangePhase(user.scene, user.getBattlerIndex(), true, [ Stat.SPD ], -1)); + globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF ], 1)); + globalScene.unshiftPhase(new StatStageChangePhase(user.getBattlerIndex(), true, [ Stat.SPD ], -1)); return true; } } @@ -5523,7 +5528,7 @@ export class ConfuseAttr extends AddBattlerTagAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { if (!this.selfTarget && target.isSafeguarded(user)) { if (move.category === MoveCategory.STATUS) { - user.scene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) })); } return false; } @@ -5583,7 +5588,7 @@ export class IgnoreAccuracyAttr extends AddBattlerTagAttr { return false; } - user.scene.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); return true; } @@ -5599,7 +5604,7 @@ export class FaintCountdownAttr extends AddBattlerTagAttr { return false; } - user.scene.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 })); + globalScene.queueMessage(i18next.t("moveTriggers:faintCountdown", { pokemonName: getPokemonNameWithAffix(target), turnCount: this.turnCountMin - 1 })); return true; } @@ -5629,7 +5634,7 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr { return false; } - user.scene.getField(true).forEach(pokemon => + globalScene.getField(true).forEach(pokemon => pokemon.findAndRemoveTags(tag => tag.tagType === BattlerTagType.SUBSTITUTE)); return true; } @@ -5692,7 +5697,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); + globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); return true; } @@ -5701,7 +5706,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc | null { return this.failOnOverlap - ? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY) + ? (user, target, move) => !globalScene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY) : null; } } @@ -5730,7 +5735,7 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr { const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; for (const tagType of this.tagTypes) { - user.scene.arena.removeTagOnSide(tagType, side); + globalScene.arena.removeTagOnSide(tagType, side); } return true; @@ -5741,7 +5746,7 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; + const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; if (!tag) { return true; } @@ -5765,9 +5770,9 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; + const tag = globalScene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { - user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side); + globalScene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; } @@ -5793,20 +5798,20 @@ export class RemoveArenaTrapAttr extends MoveEffectAttr { } if (this.targetBothSides) { - user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.ENEMY); } else { - user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER); } return true; @@ -5829,17 +5834,17 @@ export class RemoveScreensAttr extends MoveEffectAttr { } if (this.targetBothSides) { - user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.PLAYER); + globalScene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.PLAYER); - user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.ENEMY); } else { - user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); - user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.REFLECT, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); + globalScene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); } return true; @@ -5865,25 +5870,25 @@ export class SwapArenaTagsAttr extends MoveEffectAttr { return false; } - const tagPlayerTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.PLAYER); - const tagEnemyTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.ENEMY); + const tagPlayerTemp = globalScene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.PLAYER); + const tagEnemyTemp = globalScene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.ENEMY); if (tagPlayerTemp) { for (const swapTagsType of tagPlayerTemp) { - user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true); - user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.ENEMY, true); // TODO: is the bang correct? + globalScene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true); + globalScene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.ENEMY, true); // TODO: is the bang correct? } } if (tagEnemyTemp) { for (const swapTagsType of tagEnemyTemp) { - user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true); - user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.PLAYER, true); // TODO: is the bang correct? + globalScene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true); + globalScene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId!, ArenaTagSide.PLAYER, true); // TODO: is the bang correct? } } - user.scene.queueMessage(i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:swapArenaTags", { pokemonName: getPokemonNameWithAffix(user) })); return true; } } @@ -5936,40 +5941,40 @@ export class RevivalBlessingAttr extends MoveEffectAttr { return new Promise(resolve => { // If user is player, checks if the user has fainted pokemon if (user instanceof PlayerPokemon - && user.scene.getPlayerParty().findIndex(p => p.isFainted()) > -1) { + && globalScene.getPlayerParty().findIndex(p => p.isFainted()) > -1) { (user as PlayerPokemon).revivalBlessing().then(() => { resolve(true); }); // If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party } else if (user instanceof EnemyPokemon && user.hasTrainer() - && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { + && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { // Selects a random fainted pokemon - const faintedPokemon = user.scene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); + const faintedPokemon = globalScene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; - const slotIndex = user.scene.getEnemyParty().findIndex(p => pokemon.id === p.id); + const slotIndex = globalScene.getEnemyParty().findIndex(p => pokemon.id === p.id); pokemon.resetStatus(); pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - user.scene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); + globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true); - if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) { + if (globalScene.currentBattle.double && globalScene.getEnemyParty().length > 1) { const allyPokemon = user.getAlly(); if (slotIndex <= 1) { - user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false)); + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false)); } else if (allyPokemon.isFainted()) { - user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false)); } } resolve(true); } else { - user.scene.queueMessage(i18next.t("battle:attackFailed")); + globalScene.queueMessage(i18next.t("battle:attackFailed")); resolve(false); } }); } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { - if (user.hasTrainer() && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { + if (user.hasTrainer() && globalScene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { return 20; } @@ -6018,7 +6023,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { // Find indices of off-field Pokemon that are eligible to be switched into const eligibleNewIndices: number[] = []; - switchOutTarget.scene.getPlayerParty().forEach((pokemon, index) => { + globalScene.getPlayerParty().forEach((pokemon, index) => { if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) { eligibleNewIndices.push(index); } @@ -6032,9 +6037,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)]; - user.scene.prependToPhase( + globalScene.prependToPhase( new SwitchSummonPhase( - user.scene, this.switchType, switchOutTarget.getFieldIndex(), slotIndex, @@ -6045,9 +6049,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - user.scene.prependToPhase( + globalScene.prependToPhase( new SwitchPhase( - user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, @@ -6059,10 +6062,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } } return false; - } else if (user.scene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers + } else if (globalScene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers // Find indices of off-field Pokemon that are eligible to be switched into const eligibleNewIndices: number[] = []; - switchOutTarget.scene.getEnemyParty().forEach((pokemon, index) => { + globalScene.getEnemyParty().forEach((pokemon, index) => { if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) { eligibleNewIndices.push(index); } @@ -6076,9 +6079,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (this.switchType === SwitchType.FORCE_SWITCH) { switchOutTarget.leaveField(true); const slotIndex = eligibleNewIndices[user.randSeedInt(eligibleNewIndices.length)]; - user.scene.prependToPhase( + globalScene.prependToPhase( new SwitchSummonPhase( - user.scene, this.switchType, switchOutTarget.getFieldIndex(), slotIndex, @@ -6089,12 +6091,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { ); } else { switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); - user.scene.prependToPhase( + globalScene.prependToPhase( new SwitchSummonPhase( - user.scene, this.switchType, switchOutTarget.getFieldIndex(), - (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), + (globalScene.currentBattle.trainer ? globalScene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, false ), @@ -6115,7 +6116,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { } } - if (user.scene.currentBattle.waveIndex % 10 === 0) { + if (globalScene.currentBattle.waveIndex % 10 === 0) { return false; } @@ -6126,21 +6127,21 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { if (switchOutTarget.hp > 0) { switchOutTarget.leaveField(false); - user.scene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); + globalScene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon - if (switchOutTarget.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { const allyPokemon = switchOutTarget.getAlly(); - switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon); + globalScene.redirectPokemonMoves(switchOutTarget, allyPokemon); } } if (!switchOutTarget.getAlly()?.isActive(true)) { - user.scene.clearEnemyHeldItemModifiers(); + globalScene.clearEnemyHeldItemModifiers(); if (switchOutTarget.hp) { - user.scene.pushPhase(new BattleEndPhase(user.scene, false)); - user.scene.pushPhase(new NewBattlePhase(user.scene)); + globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.pushPhase(new NewBattlePhase()); } } } @@ -6170,11 +6171,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { // Dondozo with an allied Tatsugiri in its mouth cannot be forced out const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED); - if (commandedTag?.getSourcePokemon(switchOutTarget.scene)?.isActive(true)) { + if (commandedTag?.getSourcePokemon()?.isActive(true)) { return false; } - if (!player && user.scene.currentBattle.isBattleMysteryEncounter() && !user.scene.currentBattle.mysteryEncounter?.fleeAllowed) { + if (!player && globalScene.currentBattle.isBattleMysteryEncounter() && !globalScene.currentBattle.mysteryEncounter?.fleeAllowed) { // Don't allow wild opponents to be force switched during MEs with flee disabled return false; } @@ -6184,25 +6185,25 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return !blockedByAbility.value; } - if (!player && user.scene.currentBattle.battleType === BattleType.WILD) { + if (!player && globalScene.currentBattle.battleType === BattleType.WILD) { if (this.isBatonPass()) { return false; } // Don't allow wild opponents to flee on the boss stage since it can ruin a run early on - if (user.scene.currentBattle.waveIndex % 10 === 0) { + if (globalScene.currentBattle.waveIndex % 10 === 0) { return false; } } - const party = player ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); - return (!player && !user.scene.currentBattle.battleType) + const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); + return (!player && !globalScene.currentBattle.battleType) || party.filter(p => p.isAllowedInBattle() - && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount(); + && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount(); }; } getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { - if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) { + if (!globalScene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) { return -20; } let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move); @@ -6231,13 +6232,13 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { export class ChillyReceptionAttr extends ForceSwitchOutAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - user.scene.arena.trySetWeather(WeatherType.SNOW, true); + globalScene.arena.trySetWeather(WeatherType.SNOW, true); return super.apply(user, target, move, args); } getCondition(): MoveConditionFunc { // chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail - return (user, target, move) => user.scene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move); + return (user, target, move) => globalScene.arena.weather?.weatherType !== WeatherType.SNOW || super.getSwitchOutCondition()(user, target, move); } } export class RemoveTypeAttr extends MoveEffectAttr { @@ -6295,7 +6296,7 @@ export class CopyTypeAttr extends MoveEffectAttr { user.summonData.types = targetTypes; user.updateInfo(); - user.scene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -6315,18 +6316,18 @@ export class CopyBiomeTypeAttr extends MoveEffectAttr { return false; } - const terrainType = user.scene.arena.getTerrainType(); + const terrainType = globalScene.arena.getTerrainType(); let typeChange: Type; if (terrainType !== TerrainType.NONE) { - typeChange = this.getTypeForTerrain(user.scene.arena.getTerrainType()); + typeChange = this.getTypeForTerrain(globalScene.arena.getTerrainType()); } else { - typeChange = this.getTypeForBiome(user.scene.arena.biomeType); + typeChange = this.getTypeForBiome(globalScene.arena.biomeType); } user.summonData.types = [ typeChange ]; user.updateInfo(); - user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:Type.${Type[typeChange]}`) })); + globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:Type.${Type[typeChange]}`) })); return true; } @@ -6431,7 +6432,7 @@ export class ChangeTypeAttr extends MoveEffectAttr { target.summonData.types = [ this.type ]; target.updateInfo(); - user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) })); + globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoType", { pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) })); return true; } @@ -6454,7 +6455,7 @@ export class AddTypeAttr extends MoveEffectAttr { target.summonData.addedType = this.type; target.updateInfo(); - user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -6476,7 +6477,7 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { const firstMoveType = target.getMoveset()[0]?.getMove().type!; // TODO: is this bang correct? user.summonData.types = [ firstMoveType ]; - user.scene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${Type[firstMoveType]}`) })); + globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${Type[firstMoveType]}`) })); return true; } @@ -6519,7 +6520,7 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } const targets = selectTargets; user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? - user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex]!, true)); // There's a PR to re-do the move(s) that use this Attr, gonna put `!` for now + globalScene.unshiftPhase(new MovePhase(user, targets, moveset[moveIndex]!, true)); // There's a PR to re-do the move(s) that use this Attr, gonna put `!` for now return true; } @@ -6551,9 +6552,9 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { ? [ target.getBattlerIndex() ] : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); - user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(user.scene, moveId).then(() => { - loadMoveAnimAssets(user.scene, [ moveId ], true) + globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(moveId, 0, 0, true), true)); + initMoveAnim(moveId).then(() => { + loadMoveAnimAssets([ moveId ], true) .then(() => resolve(true)); }); }); @@ -6564,10 +6565,10 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { let moveId; - switch (user.scene.arena.getTerrainType()) { + switch (globalScene.arena.getTerrainType()) { // this allows terrains to 'override' the biome move case TerrainType.NONE: - switch (user.scene.arena.biomeType) { + switch (globalScene.arena.biomeType) { case Biome.TOWN: moveId = Moves.ROUND; break; @@ -6694,9 +6695,9 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); - user.scene.unshiftPhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(user.scene, moveId).then(() => { - loadMoveAnimAssets(user.scene, [ moveId ], true) + globalScene.unshiftPhase(new MovePhase(user, [ target.getBattlerIndex() ], new PokemonMove(moveId, 0, 0, true), true)); + initMoveAnim(moveId).then(() => { + loadMoveAnimAssets([ moveId ], true) .then(() => resolve(true)); }); }); @@ -6704,7 +6705,7 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { - const copiableMove = user.scene.currentBattle.lastMove; + const copiableMove = globalScene.currentBattle.lastMove; if (!copiableMove) { return false; @@ -6721,7 +6722,7 @@ const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { export class CopyMoveAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const lastMove = user.scene.currentBattle.lastMove; + const lastMove = globalScene.currentBattle.lastMove; const moveTargets = getMoveTargets(user, lastMove); if (!moveTargets.targets.length) { @@ -6735,7 +6736,7 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr { : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true }); - user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); + globalScene.unshiftPhase(new MovePhase(user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); return true; } @@ -6770,13 +6771,13 @@ export class RepeatMoveAttr extends MoveEffectAttr { const movesetMove = target.getMoveset().find(m => m?.moveId === lastMove.move)!; const moveTargets = lastMove.targets ?? []; - user.scene.queueMessage(i18next.t("moveTriggers:instructingMove", { + globalScene.queueMessage(i18next.t("moveTriggers:instructingMove", { userPokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); target.getMoveQueue().unshift({ move: lastMove.move, targets: moveTargets, ignorePP: false }); target.turnData.extraTurns++; - target.scene.appendToPhase(new MovePhase(target.scene, target, moveTargets, movesetMove), MoveEndPhase); + globalScene.appendToPhase(new MovePhase(target, moveTargets, movesetMove), MoveEndPhase); return true; } @@ -6893,8 +6894,8 @@ export class ReducePpMoveAttr extends MoveEffectAttr { movesetMove!.ppUsed = Math.min((movesetMove?.ppUsed!) + this.reduction, movesetMove?.getMovePp()!); // TODO: is the bang correct? const message = i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove?.getName(), reduction: (movesetMove?.ppUsed!) - lastPpUsed }); // TODO: is the bang correct? - user.scene.eventTarget.dispatchEvent(new MoveUsedEvent(target?.id, movesetMove?.getMove()!, movesetMove?.ppUsed!)); // TODO: are these bangs correct? - user.scene.queueMessage(message); + globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(target?.id, movesetMove?.getMove()!, movesetMove?.ppUsed!)); // TODO: are these bangs correct? + globalScene.queueMessage(message); return true; } @@ -7005,7 +7006,7 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { user.summonData.moveset = user.getMoveset().slice(0); user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id, 0, 0); - user.scene.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); + globalScene.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); return true; } @@ -7055,7 +7056,7 @@ export class SketchAttr extends MoveEffectAttr { user.setMove(sketchIndex, sketchedMove.id); - user.scene.queueMessage(i18next.t("moveTriggers:sketchedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name })); + globalScene.queueMessage(i18next.t("moveTriggers:sketchedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name })); return true; } @@ -7113,9 +7114,9 @@ export class AbilityChangeAttr extends MoveEffectAttr { const moveTarget = this.selfTarget ? user : target; moveTarget.summonData.ability = this.ability; - user.scene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); + globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger); - user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name })); + globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name })); return true; } @@ -7141,11 +7142,11 @@ export class AbilityCopyAttr extends MoveEffectAttr { user.summonData.ability = target.getAbility().id; - user.scene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); + globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); - if (this.copyToPartner && user.scene.currentBattle?.double && user.getAlly().hp) { + if (this.copyToPartner && globalScene.currentBattle?.double && user.getAlly().hp) { user.getAlly().summonData.ability = target.getAbility().id; - user.getAlly().scene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); + globalScene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", { pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name })); } return true; @@ -7154,7 +7155,7 @@ export class AbilityCopyAttr extends MoveEffectAttr { getCondition(): MoveConditionFunc { return (user, target, move) => { let ret = !target.getAbility().hasAttr(UncopiableAbilityAbAttr) && !user.getAbility().hasAttr(UnsuppressableAbilityAbAttr); - if (this.copyToPartner && user.scene.currentBattle?.double) { + if (this.copyToPartner && globalScene.currentBattle?.double) { ret = ret && (!user.getAlly().hp || !user.getAlly().getAbility().hasAttr(UnsuppressableAbilityAbAttr)); } else { ret = ret && user.getAbility().id !== target.getAbility().id; @@ -7178,7 +7179,7 @@ export class AbilityGiveAttr extends MoveEffectAttr { target.summonData.ability = user.getAbility().id; - user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name })); + globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name })); return true; } @@ -7198,11 +7199,11 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr { user.summonData.ability = target.getAbility().id; target.summonData.ability = tempAbilityId; - user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", { pokemonName: getPokemonNameWithAffix(user) })); // Swaps Forecast/Flower Gift from Castform/Cherrim - user.scene.arena.triggerWeatherBasedFormChangesToNormal(); + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); // Swaps Forecast/Flower Gift to Castform/Cherrim (edge case) - user.scene.arena.triggerWeatherBasedFormChanges(); + globalScene.arena.triggerWeatherBasedFormChanges(); return true; } @@ -7228,9 +7229,9 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { } target.summonData.abilitySuppressed = true; - target.scene.arena.triggerWeatherBasedFormChangesToNormal(); + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); - target.scene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -7309,7 +7310,7 @@ export class TransformAttr extends MoveEffectAttr { user.summonData.types = target.getTypes(); promises.push(user.updateInfo()); - user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", { pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target) })); promises.push(user.loadAssets(false).then(() => { user.playAnim(); @@ -7352,7 +7353,7 @@ export class SwapStatAttr extends MoveEffectAttr { user.setStat(this.stat, target.getStat(this.stat, false), false); target.setStat(this.stat, temp, false); - user.scene.queueMessage(i18next.t("moveTriggers:switchedStat", { + globalScene.queueMessage(i18next.t("moveTriggers:switchedStat", { pokemonName: getPokemonNameWithAffix(user), stat: i18next.t(getStatKey(this.stat)), })); @@ -7398,7 +7399,7 @@ export class ShiftStatAttr extends MoveEffectAttr { user.setStat(this.statToSwitch, secondStat, false); user.setStat(this.statToSwitchWith, firstStat, false); - user.scene.queueMessage(i18next.t("moveTriggers:shiftedStats", { + globalScene.queueMessage(i18next.t("moveTriggers:shiftedStats", { pokemonName: getPokemonNameWithAffix(user), statToSwitch: i18next.t(getStatKey(this.statToSwitch)), statToSwitchWith: i18next.t(getStatKey(this.statToSwitchWith)) @@ -7457,7 +7458,7 @@ export class AverageStatsAttr extends MoveEffectAttr { target.setStat(s, avg, false); } - user.scene.queueMessage(i18next.t(this.msgKey, { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t(this.msgKey, { pokemonName: getPokemonNameWithAffix(user) })); return true; } @@ -7485,8 +7486,8 @@ export class MoneyAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move): boolean { - user.scene.currentBattle.moneyScattered += user.scene.getWaveMoneyAmount(0.2); - user.scene.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); + globalScene.currentBattle.moneyScattered += globalScene.getWaveMoneyAmount(0.2); + globalScene.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere")); return true; } } @@ -7510,7 +7511,7 @@ export class DestinyBondAttr extends MoveEffectAttr { * @returns true */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - user.scene.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`); + globalScene.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", { pokemonName: getPokemonNameWithAffix(user) })}`); user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id); return true; } @@ -7600,7 +7601,7 @@ export class AttackedByItemAttr extends MoveAttr { } const itemName = heldItems[0]?.type?.name ?? "item"; - target.scene.queueMessage(i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName })); + globalScene.queueMessage(i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName })); return true; }; @@ -7639,30 +7640,30 @@ export class AfterYouAttr extends MoveEffectAttr { * @returns true */ override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean { - user.scene.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:afterYou", { targetName: getPokemonNameWithAffix(target) })); //Will find next acting phase of the targeted pokémon, delete it and queue it next on successful delete. - const nextAttackPhase = target.scene.findPhase((phase) => phase.pokemon === target); - if (nextAttackPhase && target.scene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { - target.scene.prependToPhase(new MovePhase(target.scene, target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); + const nextAttackPhase = globalScene.findPhase((phase) => phase.pokemon === target); + if (nextAttackPhase && globalScene.tryRemovePhase((phase: MovePhase) => phase.pokemon === target)) { + globalScene.prependToPhase(new MovePhase(target, [ ...nextAttackPhase.targets ], nextAttackPhase.move), MovePhase); } return true; } } -const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY); +const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY); const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune(); -const failIfSingleBattle: MoveConditionFunc = (user, target, move) => user.scene.currentBattle.double; +const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScene.currentBattle.double; const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const cancelled = new Utils.BooleanHolder(false); - user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); + globalScene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled)); // Queue a message if an ability prevented usage of the move if (cancelled.value) { - user.scene.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); + globalScene.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); } return !cancelled.value; }; @@ -7671,10 +7672,10 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE); -const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.scene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined; +const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => globalScene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined; const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => { - const party: Pokemon[] = user.isPlayer() ? user.scene.getPlayerParty() : user.scene.getEnemyParty(); + const party: Pokemon[] = user.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); return party.some(pokemon => pokemon.isActive() && !pokemon.isOnField()); }; @@ -7757,7 +7758,7 @@ export class FirstMoveCondition extends MoveCondition { export class UpperHandCondition extends MoveCondition { constructor() { super((user, target, move) => { - const targetCommand = user.scene.currentBattle.turnCommands[target.getBattlerIndex()]; + const targetCommand = globalScene.currentBattle.turnCommands[target.getBattlerIndex()]; return !!targetCommand && targetCommand.command === Command.FIGHT @@ -7816,13 +7817,13 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { return false; } const userTypes = user.getTypes(); - const validTypes = this.getTypeResistances(user.scene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types + const validTypes = this.getTypeResistances(globalScene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types if (!validTypes.length) { return false; } const type = validTypes[user.randSeedInt(validTypes.length)]; user.summonData.types = [ type ]; - user.scene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: Utils.toReadableString(Type[type]) })); + globalScene.queueMessage(i18next.t("battle:transformedIntoType", { pokemonName: getPokemonNameWithAffix(user), type: Utils.toReadableString(Type[type]) })); user.updateInfo(); return true; @@ -7881,7 +7882,7 @@ export class ExposedMoveAttr extends AddBattlerTagAttr { return false; } - user.scene.queueMessage(i18next.t("moveTriggers:exposedMove", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("moveTriggers:exposedMove", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) })); return true; } @@ -8200,7 +8201,7 @@ export function initMoves() { .makesContact(false), new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1) @@ -8479,7 +8480,7 @@ export function initMoves() { .attr(ConfuseAttr), new SelfStatusMove(Moves.BELLY_DRUM, Type.NORMAL, -1, 10, -1, 0, 2) .attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => { - user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); + globalScene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); }), new AttackMove(Moves.SLUDGE_BOMB, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -8599,7 +8600,7 @@ export function initMoves() { new AttackMove(Moves.MAGNITUDE, Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, 0, 2) .attr(PreMoveMessageAttr, magnitudeMessageFunc) .attr(MagnitudePowerAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) .attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), @@ -8875,7 +8876,7 @@ export function initMoves() { .attr(FlinchAttr), new AttackMove(Moves.WEATHER_BALL, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 3) .attr(WeatherBallTypeAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN ].includes(user.scene.arena.weather?.weatherType!) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 2 : 1) // TODO: is this bang correct? + .attr(MovePowerMultiplierAttr, (user, target, move) => [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN ].includes(globalScene.arena.weather?.weatherType!) && !globalScene.arena.weather?.isEffectSuppressed() ? 2 : 1) // TODO: is this bang correct? .ballBombMove(), new StatusMove(Moves.AROMATHERAPY, Type.GRASS, -1, 5, -1, 0, 3) .attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), Abilities.SAP_SIPPER) @@ -9050,7 +9051,7 @@ export function initMoves() { new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4) - .attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL ? 2 : 1), + .attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === globalScene.currentBattle.turn) || globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.BALL ? 2 : 1), new AttackMove(Moves.ASSURANCE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4) .attr(MovePowerMultiplierAttr, (user, target, move) => target.turnData.damageTaken > 0 ? 2 : 1), new StatusMove(Moves.EMBARGO, Type.DARK, 100, 15, -1, 0, 4) @@ -9106,7 +9107,7 @@ export function initMoves() { new StatusMove(Moves.WORRY_SEED, Type.GRASS, 100, 10, -1, 0, 4) .attr(AbilityChangeAttr, Abilities.INSOMNIA), new AttackMove(Moves.SUCKER_PUNCH, Type.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4) - .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct? + .condition((user, target, move) => globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct? new StatusMove(Moves.TOXIC_SPIKES, Type.POISON, -1, 20, -1, 0, 4) .attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES) .target(MoveTarget.ENEMY_SIDE), @@ -9117,7 +9118,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4) .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true, 5) - .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), + .condition((user, target, move) => !globalScene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4) .attr(RecoilAttr, false, 0.33) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -9464,9 +9465,9 @@ export function initMoves() { .attr(CopyTypeAttr), new AttackMove(Moves.RETALIATE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 5, -1, 0, 5) .attr(MovePowerMultiplierAttr, (user, target, move) => { - const turn = user.scene.currentBattle.turn; - const lastPlayerFaint = user.scene.currentBattle.playerFaintsHistory[user.scene.currentBattle.playerFaintsHistory.length - 1]; - const lastEnemyFaint = user.scene.currentBattle.enemyFaintsHistory[user.scene.currentBattle.enemyFaintsHistory.length - 1]; + const turn = globalScene.currentBattle.turn; + const lastPlayerFaint = globalScene.currentBattle.playerFaintsHistory[globalScene.currentBattle.playerFaintsHistory.length - 1]; + const lastEnemyFaint = globalScene.currentBattle.enemyFaintsHistory[globalScene.currentBattle.enemyFaintsHistory.length - 1]; return ( (lastPlayerFaint !== undefined && turn - lastPlayerFaint.turn === 1 && user.isPlayer()) || (lastEnemyFaint !== undefined && turn - lastEnemyFaint.turn === 1 && !user.isPlayer()) @@ -9512,7 +9513,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.BULLDOZE, Type.GROUND, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 5) .attr(StatStageChangeAttr, [ Stat.SPD ], -1) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1) .makesContact(false) .target(MoveTarget.ALL_NEAR_OTHERS), new AttackMove(Moves.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5) @@ -9633,7 +9634,7 @@ export function initMoves() { .target(MoveTarget.ALL) .condition((user, target, move) => { // If any fielded pokémon is grass-type and grounded. - return [ ...user.scene.getEnemyParty(), ...user.scene.getPlayerParty() ].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded()); + return [ ...globalScene.getEnemyParty(), ...globalScene.getPlayerParty() ].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded()); }) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, false, { condition: (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded() }), new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6) @@ -9954,7 +9955,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPD ], -1, true) .punchingMove(), new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7) - .attr(BoostHealAttr, 0.5, 2 / 3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY) + .attr(BoostHealAttr, 0.5, 2 / 3, true, false, (user, target, move) => globalScene.arena.terrain?.terrainType === TerrainType.GRASSY) .triageMove(), new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7), new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, -1, 0, 7) @@ -10007,7 +10008,7 @@ export function initMoves() { .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(AddBattlerTagAttr, BattlerTagType.BURNED_UP, true, false) .attr(RemoveTypeAttr, Type.FIRE, (user) => { - user.scene.queueMessage(i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:burnedItselfOut", { pokemonName: getPokemonNameWithAffix(user) })); }), new StatusMove(Moves.SPEED_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 7) .attr(SwapStatAttr, Stat.SPD) @@ -10043,7 +10044,7 @@ export function initMoves() { new AttackMove(Moves.BRUTAL_SWING, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 7) .target(MoveTarget.ALL_NEAR_OTHERS), new StatusMove(Moves.AURORA_VEIL, Type.ICE, -1, 20, -1, 0, 7) - .condition((user, target, move) => (user.scene.arena.weather?.weatherType === WeatherType.HAIL || user.scene.arena.weather?.weatherType === WeatherType.SNOW) && !user.scene.arena.weather?.isEffectSuppressed(user.scene)) + .condition((user, target, move) => (globalScene.arena.weather?.weatherType === WeatherType.HAIL || globalScene.arena.weather?.weatherType === WeatherType.SNOW) && !globalScene.arena.weather?.isEffectSuppressed()) .attr(AddArenaTagAttr, ArenaTagType.AURORA_VEIL, 5, true) .target(MoveTarget.USER_SIDE), /* Unused */ @@ -10196,10 +10197,10 @@ export function initMoves() { new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, target, move) => { // Move is only stronger against overleveled foes. - if (target.level > target.scene.getMaxExpLevel()) { + if (target.level > globalScene.getMaxExpLevel()) { const dynamaxCannonPercentMarginBeforeFullDamage = 0.05; // How much % above MaxExpLevel of wave will the target need to be to take full damage. // The move's power scales as the margin is approached, reaching double power when it does or goes over it. - return 1 + Math.min(1, (target.level - target.scene.getMaxExpLevel()) / (target.scene.getMaxExpLevel() * dynamaxCannonPercentMarginBeforeFullDamage)); + return 1 + Math.min(1, (target.level - globalScene.getMaxExpLevel()) / (globalScene.getMaxExpLevel() * dynamaxCannonPercentMarginBeforeFullDamage)); } else { return 1; } @@ -10217,7 +10218,7 @@ export function initMoves() { .attr(EatBerryAttr) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .condition((user) => { - const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); + const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); return userBerries.length > 0; }) .edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki @@ -10359,7 +10360,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1), new AttackMove(Moves.GRAV_APPLE, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8) .attr(StatStageChangeAttr, [ Stat.DEF ], -1) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1) .makesContact(false), new AttackMove(Moves.SPIRIT_BREAK, Type.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, 100, 0, 8) .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), @@ -10381,11 +10382,11 @@ export function initMoves() { new AttackMove(Moves.STEEL_BEAM, Type.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, 0, 8) .attr(HalfSacrificialAttr), new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1) - .attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER), + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1) + .attr(VariableTargetAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER), new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8) .attr(ClearTerrainAttr) - .condition((user, target, move) => !!user.scene.arena.terrain), + .condition((user, target, move) => !!globalScene.arena.terrain), new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true, { lastHitOnly: true }) .attr(StatStageChangeAttr, [ Stat.DEF ], -1, true, { lastHitOnly: true }) @@ -10402,16 +10403,16 @@ export function initMoves() { new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(SacrificialAttr) .target(MoveTarget.ALL_NEAR_OTHERS) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1) .condition(failIfDampCondition) .makesContact(false), new AttackMove(Moves.GRASSY_GLIDE, Type.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8) - .attr(IncrementMovePriorityAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && user.isGrounded()), + .attr(IncrementMovePriorityAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && user.isGrounded()), new AttackMove(Moves.RISING_VOLTAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 8) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && target.isGrounded() ? 2 : 1), + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && target.isGrounded() ? 2 : 1), new AttackMove(Moves.TERRAIN_PULSE, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 8) .attr(TerrainPulseTypeAttr) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1) .pulseMove(), new AttackMove(Moves.SKITTER_SMACK, Type.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8) .attr(StatStageChangeAttr, [ Stat.SPATK ], -1), @@ -10671,7 +10672,7 @@ export function initMoves() { .recklessMove(), new AttackMove(Moves.LAST_RESPECTS, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) .partial() // Counter resets every wave instead of on arena reset - .attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 100)) + .attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? globalScene.currentBattle.playerFaints : globalScene.currentBattle.enemyFaints, 100)) .makesContact(false), new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), @@ -10695,7 +10696,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_GET_HIT, true, false, 0, 0, true) .attr(AddBattlerTagAttr, BattlerTagType.RECEIVE_DOUBLE_DAMAGE, true, false, 0, 0, true) .condition((user, target, move) => { - return !(target.getTag(BattlerTagType.PROTECTED)?.tagType === "PROTECTED" || target.scene.arena.getTag(ArenaTagType.MAT_BLOCK)?.tagType === "MAT_BLOCK"); + return !(target.getTag(BattlerTagType.PROTECTED)?.tagType === "PROTECTED" || globalScene.arena.getTag(ArenaTagType.MAT_BLOCK)?.tagType === "MAT_BLOCK"); }), new StatusMove(Moves.REVIVAL_BLESSING, Type.NORMAL, -1, 1, -1, 0, 9) .triageMove() @@ -10746,11 +10747,11 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPATK ], -1, true, { firstTargetOnly: true }) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9) - .attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1) + .attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1) .slicingMove(), new AttackMove(Moves.HYDRO_STEAM, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9) .attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY) - .attr(MovePowerMultiplierAttr, (user, target, move) => [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(user.scene.arena.weather?.weatherType!) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 1.5 : 1), // TODO: is this bang correct? + .attr(MovePowerMultiplierAttr, (user, target, move) => [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(globalScene.arena.weather?.weatherType!) && !globalScene.arena.weather?.isEffectSuppressed() ? 1.5 : 1), // TODO: is this bang correct? new AttackMove(Moves.RUINATION, Type.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9) .attr(TargetHalfHpDamageAttr), new AttackMove(Moves.COLLISION_COURSE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) @@ -10799,7 +10800,7 @@ export function initMoves() { }) .attr(AddBattlerTagAttr, BattlerTagType.DOUBLE_SHOCKED, true, false) .attr(RemoveTypeAttr, Type.ELECTRIC, (user) => { - user.scene.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) })); + globalScene.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", { pokemonName: getPokemonNameWithAffix(user) })); }), new AttackMove(Moves.GIGATON_HAMMER, Type.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9) .makesContact(false) @@ -10866,7 +10867,7 @@ export function initMoves() { .attr(ProtectAttr, BattlerTagType.BURNING_BULWARK) .condition(failIfLastCondition), new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9) - .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct? + .condition((user, target, move) => globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[globalScene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct? new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9) .slicingMove() .ignoresProtect(), diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index f0155b4f2a4..407644ee939 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -1,15 +1,17 @@ -import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { globalScene } from "#app/global-scene"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs, } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { randSeedInt } from "#app/utils"; import i18next from "i18next"; -import { IEggOptions } from "#app/data/egg"; +import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; @@ -36,8 +38,8 @@ export const ATrainersTestEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Randomly pick from 1 of the 5 stat trainers to spawn let trainerType: TrainerType; @@ -138,23 +140,22 @@ export const ATrainersTestEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip` }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Battle the stat trainer for an Egg and great rewards const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - await transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(); const eggOptions: IEggOptions = { - scene, pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, tier: EggTier.EPIC }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); - await initBattleWithEnemyConfig(scene, config); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); + await initBattleWithEnemyConfig(config); } ) .withSimpleOption( @@ -162,21 +163,20 @@ export const ATrainersTestEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip` }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Full heal party - scene.unshiftPhase(new PartyHealPhase(scene, true)); + globalScene.unshiftPhase(new PartyHealPhase(true)); const eggOptions: IEggOptions = { - scene, pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, tier: EggTier.RARE }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`)); - setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); + leaveEncounterWithoutBattle(); } ) .withOutroDialogue([ diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 6b0f239d28d..1499d953941 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -1,10 +1,14 @@ -import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; -import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -19,8 +23,8 @@ import { BattlerIndex } from "#app/battle"; import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#app/data/trainer-config"; import { PokeballType } from "#enums/pokeball"; -import HeldModifierConfig from "#app/interfaces/held-modifier-config"; -import { BerryType } from "#enums/berry-type"; +import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import type { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -170,18 +174,18 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; - scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); - scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3"); + globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); + globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3"); // Get all player berry items, remove from party, and store reference - const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; // Sort berries by party member ID to more easily re-add later if necessary const berryItemsMap = new Map(); - scene.getPlayerParty().forEach(pokemon => { + globalScene.getPlayerParty().forEach(pokemon => { const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id); if (pokemonBerries?.length > 0) { berryItemsMap.set(pokemon.id, pokemonBerries); @@ -196,7 +200,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = // Can't define stack count on a ModifierType, have to just create separate instances for each stack // Overflow berries will be "lost" on the boss, but it's un-catchable anyway for (let i = 0; i < berryMod.stackCount; i++) { - const modifierType = generateModifierType(scene, modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType; + const modifierType = generateModifierType(modifierTypes.BERRY, [ berryMod.berryType ]) as PokemonHeldItemModifierType; bossModifierConfigs.push({ modifier: modifierType }); } }); @@ -204,7 +208,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = // Do NOT remove the real berries yet or else it will be persisted in the session data // SpDef buff below wave 50, +1 to all stats otherwise - const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [ Stat.SPDEF ] : [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; @@ -221,8 +225,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = modifierConfigs: bossModifierConfigs, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); + queueEncounterMessage(`${namespace}:option.1.boss_enraged`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); } } ], @@ -233,18 +237,18 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = return true; }) - .withOnVisualsStart((scene: BattleScene) => { - doGreedentSpriteSteal(scene); - doBerrySpritePile(scene); + .withOnVisualsStart(() => { + doGreedentSpriteSteal(); + doBerrySpritePile(); // Remove the berries from the party // Session has been safely saved at this point, so data won't be lost - const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; berryItems.forEach(berryMod => { - scene.removeModifier(berryMod); + globalScene.removeModifier(berryMod); }); - scene.updateModifiers(true); + globalScene.updateModifiers(true); return true; }) @@ -260,26 +264,26 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; // Provides 1x Reviver Seed to each party member at end of battle - const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); + const revSeed = generateModifierType(modifierTypes.REVIVER_SEED); encounter.setDialogueToken("foodReward", revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name")); const givePartyPokemonReviverSeeds = () => { - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); party.forEach(p => { const heldItems = p.getHeldItems(); if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) { const seedModifier = revSeed.newModifier(p); - scene.addModifier(seedModifier, false, false, false, true); + globalScene.addModifier(seedModifier, false, false, false, true); } }); - queueEncounterMessage(scene, `${namespace}:option.1.food_stash`); + queueEncounterMessage(`${namespace}:option.1.food_stash`); }; - setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds); + setEncounterRewards({ fillRemaining: true }, undefined, givePartyPokemonReviverSeeds); encounter.startOfBattleEffects.push({ sourceBattlerIndex: BattlerIndex.ENEMY, targets: [ BattlerIndex.ENEMY ], @@ -287,8 +291,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = ignorePp: true }); - await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await transitionMysteryEncounterIntroVisuals(true, true, 500); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); }) .build() ) @@ -304,12 +308,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const berryMap = encounter.misc.berryItemsMap; // Returns 2/5 of the berries stolen to each Pokemon - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); party.forEach(pokemon => { const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id); const berryTypesAsArray: BerryType[] = []; @@ -322,15 +326,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = Phaser.Math.RND.shuffle(berryTypesAsArray); const randBerryType = berryTypesAsArray.pop(); - const berryModType = generateModifierType(scene, modifierTypes.BERRY, [ randBerryType ]) as BerryModifierType; - applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType); + const berryModType = generateModifierType(modifierTypes.BERRY, [ randBerryType ]) as BerryModifierType; + applyModifierTypeToPlayerPokemon(pokemon, berryModType); } } }); - await scene.updateModifiers(true); + await globalScene.updateModifiers(true); - await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); - leaveEncounterWithoutBattle(scene, true); + await transitionMysteryEncounterIntroVisuals(true, true, 500); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -346,36 +350,36 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Animate berries being eaten - doGreedentEatBerries(scene); - doBerrySpritePile(scene, true); + doGreedentEatBerries(); + doBerrySpritePile(true); return true; }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Let it have the food // Greedent joins the team, level equal to 2 below highest party member (shiny locked) - const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2; - const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true); + const level = getHighestLevelPlayerPokemon(false, true).level - 2; + const greedent = new EnemyPokemon(getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true); greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ]; greedent.passive = true; - await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); - await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false); - leaveEncounterWithoutBattle(scene, true); + await transitionMysteryEncounterIntroVisuals(true, true, 500); + await catchPokemon(greedent, null, PokeballType.POKEBALL, false); + leaveEncounterWithoutBattle(true); }) .build() ) .build(); -function doGreedentSpriteSteal(scene: BattleScene) { +function doGreedentSpriteSteal() { const shakeDelay = 50; const slideDelay = 500; - const greedentSprites = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1); + const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1); - scene.playSound("battle_anims/Follow Me"); - scene.tweens.chain({ + globalScene.playSound("battle_anims/Follow Me"); + globalScene.tweens.chain({ targets: greedentSprites, tweens: [ { // Slide Greedent diagonally @@ -445,10 +449,10 @@ function doGreedentSpriteSteal(scene: BattleScene) { }); } -function doGreedentEatBerries(scene: BattleScene) { - const greedentSprites = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1); +function doGreedentEatBerries() { + const greedentSprites = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1); let index = 1; - scene.tweens.add({ + globalScene.tweens.add({ targets: greedentSprites, duration: 150, ease: "Cubic.easeOut", @@ -456,11 +460,11 @@ function doGreedentEatBerries(scene: BattleScene) { y: "-=8", loop: 5, onStart: () => { - scene.playSound("battle_anims/PRSFX- Bug Bite"); + globalScene.playSound("battle_anims/PRSFX- Bug Bite"); }, onLoop: () => { if (index % 2 === 0) { - scene.playSound("battle_anims/PRSFX- Bug Bite"); + globalScene.playSound("battle_anims/PRSFX- Bug Bite"); } index++; } @@ -468,17 +472,15 @@ function doGreedentEatBerries(scene: BattleScene) { } /** - * - * @param scene * @param isEat Default false. Will "create" pile when false, and remove pile when true. */ -function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) { +function doBerrySpritePile(isEat: boolean = false) { const berryAddDelay = 150; let animationOrder = [ "starf", "sitrus", "lansat", "salac", "apicot", "enigma", "liechi", "ganlon", "lum", "petaya", "leppa" ]; if (isEat) { animationOrder = animationOrder.reverse(); } - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; animationOrder.forEach((berry, i) => { const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry)); let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite; @@ -487,7 +489,7 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) { sprite = sprites[0]; tintSprite = sprites[1]; } - scene.time.delayedCall(berryAddDelay * i + 400, () => { + globalScene.time.delayedCall(berryAddDelay * i + 400, () => { if (sprite) { sprite.setVisible(!isEat); } @@ -497,20 +499,20 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) { // Animate Petaya berry falling off the pile if (berry === "petaya" && sprite && tintSprite && !isEat) { - scene.time.delayedCall(200, () => { - doBerryBounce(scene, [ sprite, tintSprite ], 30, 500); + globalScene.time.delayedCall(200, () => { + doBerryBounce([ sprite, tintSprite ], 30, 500); }); } }); }); } -function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) { +function doBerryBounce(berrySprites: Phaser.GameObjects.Sprite[], yd: number, baseBounceDuration: number) { let bouncePower = 1; let bounceYOffset = yd; const doBounce = () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: berrySprites, y: "+=" + bounceYOffset, x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, @@ -522,7 +524,7 @@ function doBerryBounce(scene: BattleScene, berrySprites: Phaser.GameObjects.Spri if (bouncePower) { bounceYOffset = bounceYOffset * bouncePower; - scene.tweens.add({ + globalScene.tweens.add({ targets: berrySprites, y: "-=" + bounceYOffset, x: { value: "+=" + (bouncePower * bouncePower * 10), ease: "Linear" }, diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 1e20b73e351..671eb3d3ab8 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -2,8 +2,9 @@ import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, upd import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -69,14 +70,14 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; - const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; + const pokemon = getHighestStatTotalPlayerPokemon(true, true); const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(); const starterValue: number = speciesStarterCosts[baseSpecies] ?? 1; const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER); - const price = scene.getWaveMoneyAmount(multiplier); + const price = globalScene.getWaveMoneyAmount(multiplier); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("price", price.toString()); @@ -89,7 +90,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = // If player meets the combo OR requirements for option 2, populate the token const opt2Req = encounter.options[1].primaryPokemonRequirements[0]; - if (opt2Req.meetsRequirement(scene)) { + if (opt2Req.meetsRequirement()) { const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"]; const moveToken = encounter.dialogueTokens["option2PrimaryMove"]; if (abilityToken) { @@ -99,7 +100,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = } } - const shinyCharm = generateModifierType(scene, modifierTypes.SHINY_CHARM); + const shinyCharm = generateModifierType(modifierTypes.SHINY_CHARM); encounter.setDialogueToken("itemName", shinyCharm?.name ?? i18next.t("modifierType:ModifierType.SHINY_CHARM.name")); encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName()); @@ -118,17 +119,17 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Update money and remove pokemon from party - updatePlayerMoney(scene, encounter.misc.price); - scene.removePokemonFromPlayerParty(encounter.misc.pokemon); + updatePlayerMoney(encounter.misc.price); + globalScene.removePokemonFromPlayerParty(encounter.misc.pokemon); return true; }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Give the player a Shiny Charm - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM)); - leaveEncounterWithoutBattle(scene, true); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.SHINY_CHARM)); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -152,15 +153,15 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Extort the rich kid for money - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; // Update money and remove pokemon from party - updatePlayerMoney(scene, encounter.misc.price); + updatePlayerMoney(encounter.misc.price); - setEncounterExp(scene, encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true); + setEncounterExp(encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -175,9 +176,9 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index eca358e51f3..20179f4c618 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,23 +1,32 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import type { + EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - EnemyPartyConfig, generateModifierType, generateModifierTypeOption, + generateModifierType, + generateModifierTypeOption, initBattleWithEnemyConfig, - leaveEncounterWithoutBattle, setEncounterExp, + leaveEncounterWithoutBattle, + setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; +import type { BerryModifierType, + ModifierTypeOption } from "#app/modifier/modifier-type"; +import { getPartyLuckValue, ModifierPoolType, - ModifierTypeOption, modifierTypes, + modifierTypes, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; import { randSeedInt, randSeedItem } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -31,7 +40,8 @@ import { BerryType } from "#enums/berry-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/berriesAbound"; @@ -54,20 +64,20 @@ export const BerriesAboundEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon - const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); + const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); let bossSpecies: PokemonSpecies; - if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode); + if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { + const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); + const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); bossSpecies = getPokemonSpecies( levelSpecies ); } else { - bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true); + bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); } - const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); + const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); const config: EnemyPartyConfig = { pokemonConfigs: [{ @@ -82,10 +92,10 @@ export const BerriesAboundEncounter: MysteryEncounter = // Calculate the number of extra berries that player receives // 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7 const numBerries = - scene.currentBattle.waveIndex > 160 ? 7 - : scene.currentBattle.waveIndex > 120 ? 5 - : scene.currentBattle.waveIndex > 40 ? 4 : 2; - regenerateModifierPoolThresholds(scene.getPlayerParty(), ModifierPoolType.PLAYER, 0); + globalScene.currentBattle.waveIndex > 160 ? 7 + : globalScene.currentBattle.waveIndex > 120 ? 5 + : globalScene.currentBattle.waveIndex > 40 ? 4 : 2; + regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); encounter.misc = { numBerries }; const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon); @@ -113,7 +123,7 @@ export const BerriesAboundEncounter: MysteryEncounter = ]; // Get fastest party pokemon for option 2 - const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true, false); + const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false); encounter.misc.fastestPokemon = fastestPokemon; encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD); encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender()); @@ -134,34 +144,34 @@ export const BerriesAboundEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const numBerries = encounter.misc.numBerries; const doBerryRewards = () => { const berryText = i18next.t(`${namespace}:berries`); - scene.playSound("item_fanfare"); - queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); + globalScene.playSound("item_fanfare"); + queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); // Generate a random berry and give it to the first Pokemon with room for it for (let i = 0; i < numBerries; i++) { - tryGiveBerry(scene); + tryGiveBerry(); } }; const shopOptions: ModifierTypeOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateModifierTypeOption(scene, modifierTypes.BERRY); + const mod = generateModifierTypeOption(modifierTypes.BERRY); if (mod) { shopOptions.push(mod); } } - setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); - await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); + setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); + await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); } ) .withOption( @@ -171,9 +181,9 @@ export const BerriesAboundEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip` }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick race for berries - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon; const enemySpeed: number = encounter.misc.enemySpeed; const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1); @@ -182,7 +192,7 @@ export const BerriesAboundEncounter: MysteryEncounter = const shopOptions: ModifierTypeOption[] = []; for (let i = 0; i < 5; i++) { // Generate shop berries - const mod = generateModifierTypeOption(scene, modifierTypes.BERRY); + const mod = generateModifierTypeOption(modifierTypes.BERRY); if (mod) { shopOptions.push(mod); } @@ -193,29 +203,29 @@ export const BerriesAboundEncounter: MysteryEncounter = const doBerryRewards = () => { const berryText = i18next.t(`${namespace}:berries`); - scene.playSound("item_fanfare"); - queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); + globalScene.playSound("item_fanfare"); + queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerries })); // Generate a random berry and give it to the first Pokemon with room for it for (let i = 0; i < numBerries; i++) { - tryGiveBerry(scene); + tryGiveBerry(); } }; // Defense/Spd buffs below wave 50, +1 to all stats otherwise - const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [ Stat.DEF, Stat.SPDEF, Stat.SPD ] : [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; - const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; + const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ]; config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.2.boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); + queueEncounterMessage(`${namespace}:option.2.boss_enraged`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); }; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); - await showEncounterText(scene, `${namespace}:option.2.selected_bad`); - await initBattleWithEnemyConfig(scene, config); + setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); + await showEncounterText(`${namespace}:option.2.selected_bad`); + await initBattleWithEnemyConfig(config); return; } else { // Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2 @@ -224,19 +234,19 @@ export const BerriesAboundEncounter: MysteryEncounter = const doFasterBerryRewards = () => { const berryText = i18next.t(`${namespace}:berries`); - scene.playSound("item_fanfare"); - queueEncounterMessage(scene, i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed })); + globalScene.playSound("item_fanfare"); + queueEncounterMessage(i18next.t("battle:rewardGainCount", { modifierName: berryText, count: numBerriesGrabbed })); // Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first) for (let i = 0; i < numBerriesGrabbed; i++) { - tryGiveBerry(scene, fastestPokemon); + tryGiveBerry(fastestPokemon); } }; - setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards); - await showEncounterText(scene, `${namespace}:option.2.selected`); - leaveEncounterWithoutBattle(scene); + setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); + setEncounterRewards({ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards); + await showEncounterText(`${namespace}:option.2.selected`); + leaveEncounterWithoutBattle(); } }) .build() @@ -251,38 +261,38 @@ export const BerriesAboundEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) .build(); -function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { +function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) { const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; - const berry = generateModifierType(scene, modifierTypes.BERRY, [ berryType ]) as BerryModifierType; + const berry = generateModifierType(modifierTypes.BERRY, [ berryType ]) as BerryModifierType; - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); // Will try to apply to prioritized pokemon first, then do normal application method if it fails if (prioritizedPokemon) { - const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier + const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier && m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { - applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry); + if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { + applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry); return; } } // Iterate over the party until berry was successfully given for (const pokemon of party) { - const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier + const heldBerriesOfType = globalScene.findModifier(m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; - if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { - applyModifierTypeToPlayerPokemon(scene, pokemon, berry); + if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) { + applyModifierTypeToPlayerPokemon(pokemon, berry); return; } } diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index ecd6972902b..209cc7a7ec5 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -1,5 +1,7 @@ +import type { + EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - EnemyPartyConfig, generateModifierType, + generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, @@ -17,17 +19,20 @@ import { } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; -import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { Moves } from "#enums/moves"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { @@ -37,14 +42,17 @@ import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { Type } from "#enums/type"; -import { AttackTypeBoosterModifierType, ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; +import type { AttackTypeBoosterModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import type { + PokemonHeldItemModifier +} from "#app/modifier/modifier"; import { AttackTypeBoosterModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, GigantamaxAccessModifier, - MegaEvolutionAccessModifier, - PokemonHeldItemModifier + MegaEvolutionAccessModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; @@ -214,12 +222,12 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculates what trainers are available for battle in the encounter // Bug type superfan trainer config - const config = getTrainerConfigForWave(scene.currentBattle.waveIndex); + const config = getTrainerConfigForWave(globalScene.currentBattle.waveIndex); const spriteKey = config.getSpriteKey(); encounter.enemyPartyConfigs.push({ trainerConfig: config, @@ -227,7 +235,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }); let beedrillKeys: { spriteKey: string, fileRoot: string }, butterfreeKeys: { spriteKey: string, fileRoot: string }; - if (scene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) { + if (globalScene.currentBattle.waveIndex < WAVE_LEVEL_BREAKPOINTS[3]) { beedrillKeys = getSpriteKeysFromSpecies(Species.BEEDRILL, false); butterfreeKeys = getSpriteKeysFromSpecies(Species.BUTTERFREE, false); } else { @@ -270,9 +278,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = ]; const requiredItems = [ - generateModifierType(scene, modifierTypes.QUICK_CLAW), - generateModifierType(scene, modifierTypes.GRIP_CLAW), - generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.BUG ]), + generateModifierType(modifierTypes.QUICK_CLAW), + generateModifierType(modifierTypes.GRIP_CLAW), + generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.BUG ]), ]; const requiredItemString = requiredItems.map(m => m?.name ?? "unknown").join("/"); @@ -295,9 +303,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Select battle the bug trainer - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; // Init the moves available for tutor @@ -313,9 +321,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = // Assigns callback that teaches move before continuing to rewards encounter.onRewards = doBugTypeMoveTutor; - setEncounterRewards(scene, { fillRemaining: true }); - await transitionMysteryEncounterIntroVisuals(scene, true, true); - await initBattleWithEnemyConfig(scene, config); + setEncounterRewards({ fillRemaining: true }); + await transitionMysteryEncounterIntroVisuals(true, true); + await initBattleWithEnemyConfig(config); } ) .withOption(MysteryEncounterOptionBuilder @@ -326,17 +334,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.2.tooltip`, disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip` }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Player shows off their bug types - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; // Player gets different rewards depending on the number of bug types they have - const numBugTypes = scene.getPlayerParty().filter(p => p.isOfType(Type.BUG, true)).length; + const numBugTypes = globalScene.getPlayerParty().filter(p => p.isOfType(Type.BUG, true)).length; const numBugTypesText = i18next.t(`${namespace}:numBugTypes`, { count: numBugTypes }); encounter.setDialogueToken("numBugTypes", numBugTypesText); if (numBugTypes < 2) { - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL ], fillRemaining: false }); encounter.selectedOption!.dialogue!.selected = [ { speaker: `${namespace}:speaker`, @@ -344,7 +352,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ]; } else if (numBugTypes < 4) { - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL ], fillRemaining: false }); encounter.selectedOption!.dialogue!.selected = [ { speaker: `${namespace}:speaker`, @@ -352,7 +360,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ]; } else if (numBugTypes < 6) { - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.GRIP_CLAW, modifierTypes.MAX_LURE, modifierTypes.ROGUE_BALL ], fillRemaining: false }); encounter.selectedOption!.dialogue!.selected = [ { speaker: `${namespace}:speaker`, @@ -362,28 +370,28 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = } else { // If the player has any evolution/form change items that are valid for their party, // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball - const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ]; + const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(modifierTypes.MASTER_BALL)! ]; const specialOptions: ModifierTypeOption[] = []; - if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { - modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!); + if (!globalScene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(modifierTypes.MEGA_BRACELET)!); } - if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) { - modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!); + if (!globalScene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(modifierTypes.DYNAMAX_BAND)!); } - const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM); + const nonRareEvolutionModifier = generateModifierTypeOption(modifierTypes.EVOLUTION_ITEM); if (nonRareEvolutionModifier) { specialOptions.push(nonRareEvolutionModifier); } - const rareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.RARE_EVOLUTION_ITEM); + const rareEvolutionModifier = generateModifierTypeOption(modifierTypes.RARE_EVOLUTION_ITEM); if (rareEvolutionModifier) { specialOptions.push(rareEvolutionModifier); } - const formChangeModifier = generateModifierTypeOption(scene, modifierTypes.FORM_CHANGE_ITEM); + const formChangeModifier = generateModifierTypeOption(modifierTypes.FORM_CHANGE_ITEM); if (formChangeModifier) { specialOptions.push(formChangeModifier); } - const rareFormChangeModifier = generateModifierTypeOption(scene, modifierTypes.RARE_FORM_CHANGE_ITEM); + const rareFormChangeModifier = generateModifierTypeOption(modifierTypes.RARE_FORM_CHANGE_ITEM); if (rareFormChangeModifier) { specialOptions.push(rareFormChangeModifier); } @@ -391,7 +399,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = modifierOptions.push(specialOptions[randSeedInt(specialOptions.length)]); } - setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: modifierOptions, fillRemaining: false }); encounter.selectedOption!.dialogue!.selected = [ { speaker: `${namespace}:speaker`, @@ -400,9 +408,9 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = ]; } }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Player shows off their bug types - leaveEncounterWithoutBattle(scene); + leaveEncounterWithoutBattle(); }) .build()) .withOption(MysteryEncounterOptionBuilder @@ -429,8 +437,8 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = ], secondOptionPrompt: `${namespace}:option.3.select_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones @@ -466,27 +474,27 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = (item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG); }); if (!hasValidItem) { - return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null; + return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; chosenPokemon.loseHeldItem(modifier, false); - scene.updateModifiers(true, true); + globalScene.updateModifiers(true, true); - const bugNet = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; + const bugNet = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET)!; bugNet.type.tier = ModifierTier.ROGUE; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false }); - leaveEncounterWithoutBattle(scene, true); + setEncounterRewards({ guaranteedModifierTypeOptions: [ bugNet ], guaranteedModifierTypeFuncs: [ modifierTypes.REVIVER_SEED ], fillRemaining: false }); + leaveEncounterWithoutBattle(true); }) .build()) .withOutroDialogue([ @@ -642,22 +650,22 @@ function getTrainerConfigForWave(waveIndex: number) { return config; } -function doBugTypeMoveTutor(scene: BattleScene): Promise { +function doBugTypeMoveTutor(): Promise { return new Promise(async resolve => { - const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; - await showEncounterDialogue(scene, `${namespace}:battle_won`, `${namespace}:speaker`); + const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions; + await showEncounterDialogue(`${namespace}:battle_won`, `${namespace}:speaker`); const overlayScale = 1; - const moveInfoOverlay = new MoveInfoOverlay(scene, { + const moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: false, scale: overlayScale, onSide: true, right: true, x: 1, y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1, - width: (scene.game.canvas.width / 6) - 2, + width: (globalScene.game.canvas.width / 6) - 2, }); - scene.ui.add(moveInfoOverlay); + globalScene.ui.add(moveInfoOverlay); const optionSelectItems = moveOptions.map((move: PokemonMove) => { const option: OptionSelectItem = { @@ -680,7 +688,7 @@ function doBugTypeMoveTutor(scene: BattleScene): Promise { moveInfoOverlay.setVisible(false); }; - const result = await selectOptionThenPokemon(scene, optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel); + const result = await selectOptionThenPokemon(optionSelectItems, `${namespace}:teach_move_prompt`, undefined, onHoverOverCancel); // let forceExit = !!result; if (!result) { moveInfoOverlay.active = false; @@ -691,7 +699,7 @@ function doBugTypeMoveTutor(scene: BattleScene): Promise { // Option select complete, handle if they are learning a move if (result && result.selectedOptionIndex < moveOptions.length) { - scene.unshiftPhase(new LearnMovePhase(scene, result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId)); + globalScene.unshiftPhase(new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId)); } // Complete battle and go to rewards diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 6bd6856604b..f6700bb3716 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,26 +1,30 @@ -import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; import { ModifierTier } from "#app/modifier/modifier-tier"; -import { ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { ModifierPoolType, modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Abilities } from "#enums/abilities"; import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { Type } from "#enums/type"; +import type { Type } from "#enums/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { randSeedInt, randSeedShuffle } from "#app/utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { Ability } from "#app/data/ability"; import { BerryModifier } from "#app/modifier/modifier"; import { BerryType } from "#enums/berry-type"; @@ -105,8 +109,8 @@ export const ClowningAroundEncounter: MysteryEncounter = speaker: `${namespace}:speaker` }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const clownTrainerType = TrainerType.HARLEQUIN; const clownConfig = trainerConfigs[clownTrainerType].clone(); @@ -142,7 +146,7 @@ export const ClowningAroundEncounter: MysteryEncounter = }); // Load animations/sfx for start of fight moves - loadCustomMovesForEncounter(scene, [ Moves.ROLE_PLAY, Moves.TAUNT ]); + loadCustomMovesForEncounter([ Moves.ROLE_PLAY, Moves.TAUNT ]); encounter.setDialogueToken("blacephalonName", getPokemonSpecies(Species.BLACEPHALON).getName()); @@ -165,12 +169,12 @@ export const ClowningAroundEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn battle const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - setEncounterRewards(scene, { fillRemaining: true }); + setEncounterRewards({ fillRemaining: true }); // TODO: when Magic Room and Wonder Room are implemented, add those to start of battle encounter.startOfBattleEffects.push( @@ -193,28 +197,28 @@ export const ClowningAroundEncounter: MysteryEncounter = ignorePp: true }); - await transitionMysteryEncounterIntroVisuals(scene); - await initBattleWithEnemyConfig(scene, config); + await transitionMysteryEncounterIntroVisuals(); + await initBattleWithEnemyConfig(config); }) - .withPostOptionPhase(async (scene: BattleScene): Promise => { + .withPostOptionPhase(async (): Promise => { // After the battle, offer the player the opportunity to permanently swap ability - const abilityWasSwapped = await handleSwapAbility(scene); + const abilityWasSwapped = await handleSwapAbility(); if (abilityWasSwapped) { - await showEncounterText(scene, `${namespace}:option.1.ability_gained`); + await showEncounterText(`${namespace}:option.1.ability_gained`); } // Play animations once ability swap is complete // Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals - scene.tweens.add({ - targets: scene.currentBattle.trainer, + globalScene.tweens.add({ + targets: globalScene.currentBattle.trainer, x: "+=16", y: "-=16", alpha: 0, ease: "Sine.easeInOut", duration: 250 }); - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); + background.playWithoutTargets(230, 40, 2); return true; }) .build() @@ -239,13 +243,13 @@ export const ClowningAroundEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Swap player's items on pokemon with the most items // Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items // So Vitamins, form change items, etc. are not included - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); let mostHeldItemsPokemon = party[0]; let count = mostHeldItemsPokemon.getHeldItems() .filter(m => m.isTransferable && !(m instanceof BerryModifier)) @@ -270,10 +274,10 @@ export const ClowningAroundEncounter: MysteryEncounter = items.filter(m => m instanceof BerryModifier) .forEach(m => { numBerries += m.stackCount; - scene.removeModifier(m); + globalScene.removeModifier(m); }); - generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries"); + generateItemsOfTier(mostHeldItemsPokemon, numBerries, "Berries"); // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) // For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier @@ -286,24 +290,24 @@ export const ClowningAroundEncounter: MysteryEncounter = const tier = type.tier ?? ModifierTier.ULTRA; if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) { numRogue += m.stackCount; - scene.removeModifier(m); + globalScene.removeModifier(m); } else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) { numUltra += m.stackCount; - scene.removeModifier(m); + globalScene.removeModifier(m); } }); - generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); - generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); + generateItemsOfTier(mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA); + generateItemsOfTier(mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE); }) - .withOptionPhase(async (scene: BattleScene) => { - leaveEncounterWithoutBattle(scene, true); + .withOptionPhase(async () => { + leaveEncounterWithoutBattle(true); }) - .withPostOptionPhase(async (scene: BattleScene) => { + .withPostOptionPhase(async () => { // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene, true, true, 200); + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); + background.playWithoutTargets(230, 40, 2); + await transitionMysteryEncounterIntroVisuals(true, true, 200); }) .build() ) @@ -327,10 +331,10 @@ export const ClowningAroundEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Randomize the second type of all player's pokemon // If the pokemon does not normally have a second type, it will gain 1 - for (const pokemon of scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { const originalTypes = pokemon.getTypes(false, false, true); // If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type @@ -367,14 +371,14 @@ export const ClowningAroundEncounter: MysteryEncounter = } } }) - .withOptionPhase(async (scene: BattleScene) => { - leaveEncounterWithoutBattle(scene, true); + .withOptionPhase(async () => { + leaveEncounterWithoutBattle(true); }) - .withPostOptionPhase(async (scene: BattleScene) => { + .withPostOptionPhase(async () => { // Play animations - const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 230, 40, 2); - await transitionMysteryEncounterIntroVisuals(scene, true, true, 200); + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); + background.playWithoutTargets(230, 40, 2); + await transitionMysteryEncounterIntroVisuals(true, true, 200); }) .build() ) @@ -385,24 +389,24 @@ export const ClowningAroundEncounter: MysteryEncounter = ]) .build(); -async function handleSwapAbility(scene: BattleScene) { +async function handleSwapAbility() { return new Promise(async resolve => { - await showEncounterDialogue(scene, `${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`); - await showEncounterText(scene, `${namespace}:option.1.apply_ability_message`); + await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`); + await showEncounterText(`${namespace}:option.1.apply_ability_message`); - scene.ui.setMode(Mode.MESSAGE).then(() => { - displayYesNoOptions(scene, resolve); + globalScene.ui.setMode(Mode.MESSAGE).then(() => { + displayYesNoOptions(resolve); }); }); } -function displayYesNoOptions(scene: BattleScene, resolve) { - showEncounterText(scene, `${namespace}:option.1.ability_prompt`, null, 500, false); +function displayYesNoOptions(resolve) { + showEncounterText(`${namespace}:option.1.ability_prompt`, null, 500, false); const fullOptions = [ { label: i18next.t("menu:yes"), handler: () => { - onYesAbilitySwap(scene, resolve); + onYesAbilitySwap(resolve); return true; } }, @@ -420,29 +424,29 @@ function displayYesNoOptions(scene: BattleScene, resolve) { maxOptions: 7, yOffset: 0 }; - scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); + globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); } -function onYesAbilitySwap(scene: BattleScene, resolve) { +function onYesAbilitySwap(resolve) { const onPokemonSelected = (pokemon: PlayerPokemon) => { // Do ability swap - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability); encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); - scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); + globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); }; const onPokemonNotSelected = () => { - scene.ui.setMode(Mode.MESSAGE).then(() => { - displayYesNoOptions(scene, resolve); + globalScene.ui.setMode(Mode.MESSAGE).then(() => { + displayYesNoOptions(resolve); }); }; - selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected); + selectPokemonForOption(onPokemonSelected, onPokemonNotSelected); } -function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") { +function generateItemsOfTier(pokemon: PlayerPokemon, numItems: number, tier: ModifierTier | "Berries") { // These pools have to be defined at runtime so that modifierTypes exist // Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon // This is to prevent "over-generating" a random item of a certain type during item swaps @@ -495,11 +499,11 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem const newItemType = pool[randIndex]; let newMod: PokemonHeldItemModifierType; if (tier === "Berries") { - newMod = generateModifierType(scene, modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType; + newMod = generateModifierType(modifierTypes.BERRY, [ newItemType[0] ]) as PokemonHeldItemModifierType; } else { - newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType; + newMod = generateModifierType(newItemType[0]) as PokemonHeldItemModifierType; } - applyModifierTypeToPlayerPokemon(scene, pokemon, newMod); + applyModifierTypeToPlayerPokemon(pokemon, newMod); // Decrement max stacks and remove from pool if at max newItemType[1]--; if (newItemType[1] <= 0) { diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 841aadd7c36..6dcac277525 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -1,22 +1,26 @@ import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { EncounterBattleAnim } from "#app/data/battle-anims"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { TrainerSlot } from "#app/data/trainer-config"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { modifierTypes } from "#app/modifier/modifier-type"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import PokemonData from "#app/system/pokemon-data"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; import { EncounterAnim } from "#enums/encounter-anims"; @@ -91,10 +95,10 @@ export const DancingLessonsEncounter: MysteryEncounter = .withAutoHideIntroVisuals(false) .withCatchAllowed(true) .withFleeAllowed(false) - .withOnVisualsStart((scene: BattleScene) => { - const oricorio = scene.getEnemyPokemon()!; - const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!); - danceAnim.play(scene, false, () => { + .withOnVisualsStart(() => { + const oricorio = globalScene.getEnemyPokemon()!; + const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, globalScene.getPlayerPokemon()!); + danceAnim.play(false, () => { if (oricorio.shiny) { oricorio.sparkle(); } @@ -110,12 +114,12 @@ export const DancingLessonsEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const species = getPokemonSpecies(Species.ORICORIO); - const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); - const enemyPokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, false); + const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); + const enemyPokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, false); if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) { if (enemyPokemon.moveset.length < 4) { enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE)); @@ -126,7 +130,7 @@ export const DancingLessonsEncounter: MysteryEncounter = // Set the form index based on the biome // Defaults to Baile style if somehow nothing matches - const currentBiome = scene.arena.biomeType; + const currentBiome = globalScene.arena.biomeType; if (BAILE_STYLE_BIOMES.includes(currentBiome)) { enemyPokemon.formIndex = 0; } else if (POM_POM_STYLE_BIOMES.includes(currentBiome)) { @@ -140,14 +144,14 @@ export const DancingLessonsEncounter: MysteryEncounter = } const oricorioData = new PokemonData(enemyPokemon); - const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); + const oricorio = globalScene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData); // Adds a real Pokemon sprite to the field (required for the animation) - scene.getEnemyParty().forEach(enemyPokemon => { - scene.field.remove(enemyPokemon, true); + globalScene.getEnemyParty().forEach(enemyPokemon => { + globalScene.field.remove(enemyPokemon, true); }); - scene.currentBattle.enemyParty = [ oricorio ]; - scene.field.add(oricorio); + globalScene.currentBattle.enemyParty = [ oricorio ]; + globalScene.field.add(oricorio); // Spawns on offscreen field oricorio.x -= 300; encounter.loadAssets.push(oricorio.loadAssets()); @@ -160,8 +164,8 @@ export const DancingLessonsEncounter: MysteryEncounter = // Gets +1 to all stats except SPD on battle start tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.1.boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1)); + queueEncounterMessage(`${namespace}:option.1.boss_enraged`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF ], 1)); } }], }; @@ -186,9 +190,9 @@ export const DancingLessonsEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.startOfBattleEffects.push({ sourceBattlerIndex: BattlerIndex.ENEMY, @@ -197,9 +201,9 @@ export const DancingLessonsEncounter: MysteryEncounter = ignorePp: true }); - await hideOricorioPokemon(scene); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true }); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await hideOricorioPokemon(); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.BATON ], fillRemaining: true }); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); }) .build() ) @@ -215,25 +219,25 @@ export const DancingLessonsEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Learn its Dance - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); - scene.unshiftPhase(new LearnMovePhase(scene, scene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE)); + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(pokemon), Moves.REVELATION_DANCE)); // Play animation again to "learn" the dance - const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon()); - danceAnim.play(scene); + const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, globalScene.getEnemyPokemon()!, globalScene.getPlayerPokemon()); + danceAnim.play(); }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Learn its Dance - await hideOricorioPokemon(scene); - leaveEncounterWithoutBattle(scene, true); + await hideOricorioPokemon(); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -252,9 +256,9 @@ export const DancingLessonsEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Open menu for selecting pokemon with a Dancing move - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for nature selection return pokemon.moveset @@ -281,20 +285,20 @@ export const DancingLessonsEncounter: MysteryEncounter = if (!pokemon.isAllowedInBattle()) { return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null; } - const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon); + const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon); if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null; + return getEncounterText(`${namespace}:invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Show the Oricorio a dance, and recruit it - const encounter = scene.currentBattle.mysteryEncounter!; - const oricorio = encounter.misc.oricorioData.toPokemon(scene); + const encounter = globalScene.currentBattle.mysteryEncounter!; + const oricorio = encounter.misc.oricorioData.toPokemon(); oricorio.passive = true; // Ensure the Oricorio's moveset gains the Dance move the player used @@ -307,18 +311,18 @@ export const DancingLessonsEncounter: MysteryEncounter = } } - await hideOricorioPokemon(scene); - await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false); - leaveEncounterWithoutBattle(scene, true); + await hideOricorioPokemon(); + await catchPokemon(oricorio, null, PokeballType.POKEBALL, false); + leaveEncounterWithoutBattle(true); }) .build() ) .build(); -function hideOricorioPokemon(scene: BattleScene) { +function hideOricorioPokemon() { return new Promise(resolve => { - const oricorioSprite = scene.getEnemyParty()[0]; - scene.tweens.add({ + const oricorioSprite = globalScene.getEnemyParty()[0]; + globalScene.tweens.add({ targets: oricorioSprite, x: "+=16", y: "-=16", @@ -326,7 +330,7 @@ function hideOricorioPokemon(scene: BattleScene) { ease: "Sine.easeInOut", duration: 750, onComplete: () => { - scene.field.remove(oricorioSprite, true); + globalScene.field.remove(oricorioSprite, true); resolve(); } }); diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 05d6501f256..3b6ab8b0c05 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -1,18 +1,21 @@ -import { Type } from "#enums/type"; +import type { Type } from "#enums/type"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; +import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; import { getRandomPlayerPokemon, getRandomSpeciesByStarterCost } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; -import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Challenges } from "#enums/challenges"; @@ -138,16 +141,16 @@ export const DarkDealEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens // Will never return last battle able mon and instead pick fainted/unable to battle - const removedPokemon = getRandomPlayerPokemon(scene, true, false, true); + const removedPokemon = getRandomPlayerPokemon(true, false, true); // Get all the pokemon's held items const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); - scene.removePokemonFromPlayerParty(removedPokemon); + globalScene.removePokemonFromPlayerParty(removedPokemon); - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender()); // Store removed pokemon types @@ -156,16 +159,16 @@ export const DarkDealEncounter: MysteryEncounter = modifiers }; }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Give the player 5 Rogue Balls - const encounter = scene.currentBattle.mysteryEncounter!; - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); + const encounter = globalScene.currentBattle.mysteryEncounter!; + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.ROGUE_BALL)); // Start encounter with random legendary (7-10 starter strength) that has level additive // If this is a mono-type challenge, always ensure the required type is filtered for let bossTypes: Type[] = encounter.misc.removedTypes; - const singleTypeChallenges = scene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE); - if (scene.gameMode.isChallenge && singleTypeChallenges.length > 0) { + const singleTypeChallenges = globalScene.gameMode.challenges.filter(c => c.value && c.id === Challenges.SINGLE_TYPE); + if (globalScene.gameMode.isChallenge && singleTypeChallenges.length > 0) { bossTypes = singleTypeChallenges.map(c => (c.value - 1) as Type); } @@ -191,7 +194,7 @@ export const DarkDealEncounter: MysteryEncounter = const config: EnemyPartyConfig = { pokemonConfigs: [ pokemonConfig ], }; - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -206,9 +209,9 @@ export const DarkDealEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 99668c76143..d710dffab8c 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -1,18 +1,22 @@ -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PreserveBerryModifier } from "#app/modifier/modifier"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; +import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PreserveBerryModifier } from "#app/modifier/modifier"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import i18next from "#app/plugins/i18n"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { randSeedItem } from "#app/utils"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -36,19 +40,19 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; -const doEventReward = (scene: BattleScene) => { - const event_buff = scene.eventManager.activeEvent()?.delibirdyBuff ?? []; +const doEventReward = () => { + const event_buff = globalScene.eventManager.activeEvent()?.delibirdyBuff ?? []; if (event_buff.length > 0) { const candidates = event_buff.filter((c => { - const mtype = generateModifierType(scene, modifierTypes[c]); - const existingCharm = scene.findModifier(m => m.type.id === mtype?.id); - return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount(scene)); + const mtype = generateModifierType(modifierTypes[c]); + const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id); + return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount()); })); if (candidates.length > 0) { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes[randSeedItem(candidates)])); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes[randSeedItem(candidates)])); } else { // At max stacks, give a Voucher instead - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.VOUCHER)); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.VOUCHER)); } } }; @@ -114,15 +118,15 @@ export const DelibirdyEncounter: MysteryEncounter = text: `${namespace}:outro`, } ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName()); - scene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3"); + globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3"); return true; }) - .withOnVisualsStart((scene: BattleScene) => { - scene.fadeAndSwitchBgm("mystery_encounter_delibirdy"); + .withOnVisualsStart(() => { + globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy"); return true; }) .withOption( @@ -138,29 +142,29 @@ export const DelibirdyEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; - updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false); + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; + updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false); return true; }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Give the player an Amulet Coin // Check if the player has max stacks of that item already - const existing = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; + const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; - if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { + if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell); - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); - doEventReward(scene); + const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); + doEventReward(); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN)); - doEventReward(scene); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.AMULET_COIN)); + doEventReward(); } - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -178,8 +182,8 @@ export const DelibirdyEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones const validItems = pokemon.getHeldItems().filter((it) => { @@ -205,57 +209,57 @@ export const DelibirdyEncounter: MysteryEncounter = const selectableFilter = (pokemon: Pokemon) => { // If pokemon has valid item, it can be selected - const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon); + const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon); if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null; + return getEncounterText(`${namespace}:invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed if (modifier instanceof BerryModifier) { // Check if the player has max stacks of that Candy Jar already - const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier; + const existing = globalScene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier; - if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { + if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell); - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); - doEventReward(scene); + const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); + doEventReward(); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR)); - doEventReward(scene); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.CANDY_JAR)); + doEventReward(); } } else { // Check if the player has max stacks of that Berry Pouch already - const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; + const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; - if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { + if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerPokemon()!, shellBell); - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); - doEventReward(scene); + const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); + doEventReward(); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); - doEventReward(scene); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.BERRY_POUCH)); + doEventReward(); } } chosenPokemon.loseHeldItem(modifier, false); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -273,8 +277,8 @@ export const DelibirdyEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones const validItems = pokemon.getHeldItems().filter((it) => { @@ -300,39 +304,39 @@ export const DelibirdyEncounter: MysteryEncounter = const selectableFilter = (pokemon: Pokemon) => { // If pokemon has valid item, it can be selected - const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon); + const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon); if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null; + return getEncounterText(`${namespace}:invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check if the player has max stacks of Healing Charm already - const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; + const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; - if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { + if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - await applyModifierTypeToPlayerPokemon(scene, scene.getPlayerParty()[0], shellBell); - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); - doEventReward(scene); + const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); + doEventReward(); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); - doEventReward(scene); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierTypes.HEALING_CHARM)); + doEventReward(); } chosenPokemon.loseHeldItem(modifier, false); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 10034d19263..b2bc13ca744 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -2,12 +2,13 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type"; +import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder, } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -60,7 +61,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`, }, - async (scene: BattleScene) => { + async () => { // Choose TMs const modifiers: ModifierTypeFunc[] = []; let i = 0; @@ -77,8 +78,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = i++; } - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); + leaveEncounterWithoutBattle(); } ) .withSimpleOption( @@ -86,7 +87,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, }, - async (scene: BattleScene) => { + async () => { // Choose Vitamins const modifiers: ModifierTypeFunc[] = []; let i = 0; @@ -101,8 +102,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = i++; } - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); + leaveEncounterWithoutBattle(); } ) .withSimpleOption( @@ -110,7 +111,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, }, - async (scene: BattleScene) => { + async () => { // Choose X Items const modifiers: ModifierTypeFunc[] = []; let i = 0; @@ -125,8 +126,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = i++; } - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); + leaveEncounterWithoutBattle(); } ) .withSimpleOption( @@ -134,7 +135,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.4.label`, buttonTooltip: `${namespace}:option.4.tooltip`, }, - async (scene: BattleScene) => { + async () => { // Choose Pokeballs const modifiers: ModifierTypeFunc[] = []; let i = 0; @@ -153,8 +154,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = i++; } - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ guaranteedModifierTypeFuncs: modifiers, fillRemaining: false, }); + leaveEncounterWithoutBattle(); } ) .withOutroDialogue([ diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 1c26df0cf71..8bb5c68eec0 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -1,12 +1,13 @@ import { MoveCategory } from "#app/data/move"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { Stat } from "#enums/stat"; @@ -64,8 +65,8 @@ export const FieldTripEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.1.tooltip`, secondOptionPrompt: `${namespace}:second_option_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for Pokemon move valid for this option return pokemon.moveset.map((move: PokemonMove) => { @@ -74,7 +75,7 @@ export const FieldTripEncounter: MysteryEncounter = handler: () => { // Pokemon and move selected encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:physical`)); - pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.PHYSICAL); + pokemonAndMoveChosen(pokemon, move, MoveCategory.PHYSICAL); return true; }, }; @@ -82,23 +83,23 @@ export const FieldTripEncounter: MysteryEncounter = }); }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!, - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!, - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, - generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!, - generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ATK ])!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.DEF ])!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, + generateModifierTypeOption(modifierTypes.DIRE_HIT)!, + generateModifierTypeOption(modifierTypes.RARER_CANDY)!, ]; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); } - leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + leaveEncounterWithoutBattle(!encounter.misc.correctMove); }) .build() ) @@ -110,8 +111,8 @@ export const FieldTripEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.2.tooltip`, secondOptionPrompt: `${namespace}:second_option_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for Pokemon move valid for this option return pokemon.moveset.map((move: PokemonMove) => { @@ -120,7 +121,7 @@ export const FieldTripEncounter: MysteryEncounter = handler: () => { // Pokemon and move selected encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:special`)); - pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.SPECIAL); + pokemonAndMoveChosen(pokemon, move, MoveCategory.SPECIAL); return true; }, }; @@ -128,23 +129,23 @@ export const FieldTripEncounter: MysteryEncounter = }); }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!, - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!, - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, - generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!, - generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPATK ])!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPDEF ])!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, + generateModifierTypeOption(modifierTypes.DIRE_HIT)!, + generateModifierTypeOption(modifierTypes.RARER_CANDY)!, ]; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); } - leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + leaveEncounterWithoutBattle(!encounter.misc.correctMove); }) .build() ) @@ -156,8 +157,8 @@ export const FieldTripEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.3.tooltip`, secondOptionPrompt: `${namespace}:second_option_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for Pokemon move valid for this option return pokemon.moveset.map((move: PokemonMove) => { @@ -166,7 +167,7 @@ export const FieldTripEncounter: MysteryEncounter = handler: () => { // Pokemon and move selected encounter.setDialogueToken("moveCategory", i18next.t(`${namespace}:status`)); - pokemonAndMoveChosen(scene, pokemon, move, MoveCategory.STATUS); + pokemonAndMoveChosen(pokemon, move, MoveCategory.STATUS); return true; }, }; @@ -174,30 +175,30 @@ export const FieldTripEncounter: MysteryEncounter = }); }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.misc.correctMove) { const modifiers = [ - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!, - generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, - generateModifierTypeOption(scene, modifierTypes.GREAT_BALL)!, - generateModifierTypeOption(scene, modifierTypes.IV_SCANNER)!, - generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.ACC ])!, + generateModifierTypeOption(modifierTypes.TEMP_STAT_STAGE_BOOSTER, [ Stat.SPD ])!, + generateModifierTypeOption(modifierTypes.GREAT_BALL)!, + generateModifierTypeOption(modifierTypes.IV_SCANNER)!, + generateModifierTypeOption(modifierTypes.RARER_CANDY)!, ]; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); } - leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + leaveEncounterWithoutBattle(!encounter.misc.correctMove); }) .build() ) .build(); -function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) { - const encounter = scene.currentBattle.mysteryEncounter!; +function pokemonAndMoveChosen(pokemon: PlayerPokemon, move: PokemonMove, correctMoveCategory: MoveCategory) { + const encounter = globalScene.currentBattle.mysteryEncounter!; const correctMove = move.getMove().category === correctMoveCategory; encounter.setDialogueToken("pokeName", pokemon.getNameToRender()); encounter.setDialogueToken("move", move.getName()); @@ -214,7 +215,7 @@ function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move: text: `${namespace}:incorrect_exp`, }, ]; - setEncounterExp(scene, scene.getPlayerParty().map((p) => p.id), 50); + setEncounterExp(globalScene.getPlayerParty().map((p) => p.id), 50); } else { encounter.selectedOption!.dialogue!.selected = [ { @@ -228,7 +229,7 @@ function pokemonAndMoveChosen(scene: BattleScene, pokemon: PlayerPokemon, move: text: `${namespace}:correct_exp`, }, ]; - setEncounterExp(scene, [ pokemon.id ], 100); + setEncounterExp([ pokemon.id ], 100); } encounter.misc = { correctMove: correctMove, diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index bbc979e844e..c52540584b3 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -1,16 +1,20 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; import { Type } from "#enums/type"; import { BattlerIndex } from "#app/battle"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#enums/weather-type"; @@ -58,8 +62,8 @@ export const FieryFalloutEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mons const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA); @@ -71,7 +75,7 @@ export const FieryFalloutEncounter: MysteryEncounter = gender: Gender.MALE, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); } }, { @@ -80,7 +84,7 @@ export const FieryFalloutEncounter: MysteryEncounter = gender: Gender.FEMALE, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.SPDEF, Stat.SPD ], 1)); } } ], @@ -113,25 +117,25 @@ export const FieryFalloutEncounter: MysteryEncounter = ]; // Load animations/sfx for Volcarona moves - loadCustomMovesForEncounter(scene, [ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]); + loadCustomMovesForEncounter([ Moves.FIRE_SPIN, Moves.QUIVER_DANCE ]); - scene.arena.trySetWeather(WeatherType.SUNNY, true); + globalScene.arena.trySetWeather(WeatherType.SUNNY, true); encounter.setDialogueToken("volcaronaName", getPokemonSpecies(Species.VOLCARONA).getName()); return true; }) - .withOnVisualsStart((scene: BattleScene) => { + .withOnVisualsStart(() => { // Play animations - const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - background.playWithoutTargets(scene, 200, 70, 2, 3); - const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon()!, scene.getPlayerPokemon()); - animation.playWithoutTargets(scene, 80, 100, 2); - scene.time.delayedCall(600, () => { - animation.playWithoutTargets(scene, -20, 100, 2); + const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); + background.playWithoutTargets(200, 70, 2, 3); + const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, globalScene.getPlayerPokemon()!, globalScene.getPlayerPokemon()); + animation.playWithoutTargets(80, 100, 2); + globalScene.time.delayedCall(600, () => { + animation.playWithoutTargets(-20, 100, 2); }); - scene.time.delayedCall(1200, () => { - animation.playWithoutTargets(scene, 140, 150, 2); + globalScene.time.delayedCall(1200, () => { + animation.playWithoutTargets(140, 150, 2); }); return true; @@ -150,10 +154,10 @@ export const FieryFalloutEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem(scene)); + const encounter = globalScene.currentBattle.mysteryEncounter!; + setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem()); encounter.startOfBattleEffects.push( { @@ -168,7 +172,7 @@ export const FieryFalloutEncounter: MysteryEncounter = move: new PokemonMove(Moves.FIRE_SPIN), ignorePp: true }); - await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); + await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); } ) .withSimpleOption( @@ -181,15 +185,15 @@ export const FieryFalloutEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Damage non-fire types and burn 1 random non-fire type member + give it Heatproof - const encounter = scene.currentBattle.mysteryEncounter!; - const nonFireTypes = scene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE)); + const encounter = globalScene.currentBattle.mysteryEncounter!; + const nonFireTypes = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE)); for (const pkm of nonFireTypes) { const percentage = DAMAGE_PERCENTAGE / 100; const damage = Math.floor(pkm.getMaxHp() * percentage); - applyDamageToPokemon(scene, pkm, damage); + applyDamageToPokemon(pkm, damage); } // Burn random member @@ -201,7 +205,7 @@ export const FieryFalloutEncounter: MysteryEncounter = // Burn applied encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name); - queueEncounterMessage(scene, `${namespace}:option.2.target_burned`); + queueEncounterMessage(`${namespace}:option.2.target_burned`); // Also permanently change the burned Pokemon's ability to Heatproof applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF); @@ -209,7 +213,7 @@ export const FieryFalloutEncounter: MysteryEncounter = } // No rewards - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); } ) .withOption( @@ -231,44 +235,44 @@ export const FieryFalloutEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Do NOT await this, to prevent player from repeatedly pressing options - transitionMysteryEncounterIntroVisuals(scene, false, false, 2000); + transitionMysteryEncounterIntroVisuals(false, false, 2000); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Fire types help calm the Volcarona - const encounter = scene.currentBattle.mysteryEncounter!; - await transitionMysteryEncounterIntroVisuals(scene); - setEncounterRewards(scene, + const encounter = globalScene.currentBattle.mysteryEncounter!; + await transitionMysteryEncounterIntroVisuals(); + setEncounterRewards( { fillRemaining: true }, undefined, () => { - giveLeadPokemonAttackTypeBoostItem(scene); + giveLeadPokemonAttackTypeBoostItem(); }); const primary = encounter.options[2].primaryPokemon!; - setEncounterExp(scene, [ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); - leaveEncounterWithoutBattle(scene); + setEncounterExp([ primary.id ], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); + leaveEncounterWithoutBattle(); }) .build() ) .build(); -function giveLeadPokemonAttackTypeBoostItem(scene: BattleScene) { +function giveLeadPokemonAttackTypeBoostItem() { // Give first party pokemon attack type boost item for free at end of battle - const leadPokemon = scene.getPlayerParty()?.[0]; + const leadPokemon = globalScene.getPlayerParty()?.[0]; if (leadPokemon) { // Generate type booster held item, default to Charcoal if item fails to generate - let boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; + let boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; if (!boosterModifierType) { - boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; + boosterModifierType = generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FIRE ]) as AttackTypeBoosterModifierType; } - applyModifierTypeToPlayerPokemon(scene, leadPokemon, boosterModifierType); + applyModifierTypeToPlayerPokemon(leadPokemon, boosterModifierType); - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("itemName", boosterModifierType.name); encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); - queueEncounterMessage(scene, `${namespace}:found_item`); + queueEncounterMessage(`${namespace}:found_item`); } } diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index e238fd51e66..fc6f163010b 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -1,23 +1,28 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import type { + EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - EnemyPartyConfig, initBattleWithEnemyConfig, - leaveEncounterWithoutBattle, setEncounterExp, + leaveEncounterWithoutBattle, + setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import Pokemon, { EnemyPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; import { ModifierTier } from "#app/modifier/modifier-tier"; +import type { + ModifierTypeOption } from "#app/modifier/modifier-type"; import { getPartyLuckValue, getPlayerModifierTypeOptions, ModifierPoolType, - ModifierTypeOption, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -29,7 +34,8 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun import { randSeedInt, randSeedItem } from "#app/utils"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fightOrFlight"; @@ -52,20 +58,20 @@ export const FightOrFlightEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon - const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); + const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); let bossSpecies: PokemonSpecies; - if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode); + if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { + const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); + const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); bossSpecies = getPokemonSpecies( levelSpecies ); } else { - bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true); + bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); } - const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); + const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender()); const config: EnemyPartyConfig = { pokemonConfigs: [{ @@ -75,10 +81,10 @@ export const FightOrFlightEncounter: MysteryEncounter = isBoss: true, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`); + queueEncounterMessage(`${namespace}:option.1.stat_boost`); // Randomly boost 1 stat 2 stages // Cannot boost Spd, Acc, or Evasion - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2)); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ randSeedInt(4, 1) ], 2)); } }], }; @@ -87,18 +93,18 @@ export const FightOrFlightEncounter: MysteryEncounter = // Calculate item // Waves 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER const tier = - scene.currentBattle.waveIndex > 160 + globalScene.currentBattle.waveIndex > 160 ? ModifierTier.MASTER - : scene.currentBattle.waveIndex > 120 + : globalScene.currentBattle.waveIndex > 120 ? ModifierTier.ROGUE - : scene.currentBattle.waveIndex > 40 + : globalScene.currentBattle.waveIndex > 40 ? ModifierTier.ULTRA : ModifierTier.GREAT; - regenerateModifierPoolThresholds(scene.getPlayerParty(), ModifierPoolType.PLAYER, 0); + regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0); let item: ModifierTypeOption | null = null; // TMs and Candy Jar excluded from possible rewards as they're too swingy in value for a singular item reward while (!item || item.type.id.includes("TM_") || item.type.id === "CANDY_JAR") { - item = getPlayerModifierTypeOptions(1, scene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0]; + item = getPlayerModifierTypeOptions(1, globalScene.getPlayerParty(), [], { guaranteedModifierTiers: [ tier ], allowLuckUpgrades: false })[0]; } encounter.setDialogueToken("itemName", item.type.name); encounter.misc = item; @@ -144,12 +150,12 @@ export const FightOrFlightEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle // Pokemon will randomly boost 1 stat by 2 stages - const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); - await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); + const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; + setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); + await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); } ) .withOption( @@ -166,16 +172,16 @@ export const FightOrFlightEncounter: MysteryEncounter = } ] }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick steal - const encounter = scene.currentBattle.mysteryEncounter!; - const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); + const encounter = globalScene.currentBattle.mysteryEncounter!; + const item = globalScene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption; + setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); // Use primaryPokemon to execute the thievery const primaryPokemon = encounter.options[1].primaryPokemon!; - setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); - leaveEncounterWithoutBattle(scene); + setEncounterExp(primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp); + leaveEncounterWithoutBattle(); }) .build() ) @@ -189,9 +195,9 @@ export const FightOrFlightEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 84c3e56a836..4556c3ab6a0 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -1,10 +1,13 @@ import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#app/data/trainer-config"; -import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -80,14 +83,14 @@ export const FunAndGamesEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; - scene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3"); + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; + globalScene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3"); encounter.setDialogueToken("wobbuffetName", getPokemonSpecies(Species.WOBBUFFET).getName()); return true; }) - .withOnVisualsStart((scene: BattleScene) => { - scene.fadeAndSwitchBgm("mystery_encounter_fun_and_games"); + .withOnVisualsStart(() => { + globalScene.fadeAndSwitchBgm("mystery_encounter_fun_and_games"); return true; }) .withOption(MysteryEncounterOptionBuilder @@ -102,9 +105,9 @@ export const FunAndGamesEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Select Pokemon for minigame - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.misc = { playerPokemon: pokemon, @@ -113,28 +116,28 @@ export const FunAndGamesEncounter: MysteryEncounter = // Only Pokemon that are not KOed/legal can be selected const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Start minigame - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.misc.turnsRemaining = 3; // Update money const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney; - updatePlayerMoney(scene, -moneyCost, true, false); - await showEncounterText(scene, i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost })); + updatePlayerMoney(-moneyCost, true, false); + await showEncounterText(i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost })); // Handlers for battle events encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase encounter.doContinueEncounter = handleLoseMinigame; // triggered during MysteryEncounterRewardsPhase, post VictoryPhase if the player KOs Wobbuffet - hideShowmanIntroSprite(scene); - await summonPlayerPokemon(scene); - await showWobbuffetHealthBar(scene); + hideShowmanIntroSprite(); + await summonPlayerPokemon(); + await showWobbuffetHealthBar(); return true; }) @@ -150,22 +153,22 @@ export const FunAndGamesEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - await transitionMysteryEncounterIntroVisuals(scene, true, true); - leaveEncounterWithoutBattle(scene, true); + await transitionMysteryEncounterIntroVisuals(true, true); + leaveEncounterWithoutBattle(true); return true; } ) .build(); -async function summonPlayerPokemon(scene: BattleScene) { +async function summonPlayerPokemon() { return new Promise(async resolve => { - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const playerPokemon = encounter.misc.playerPokemon; // Swaps the chosen Pokemon and the first player's lead Pokemon in the party - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); const chosenIndex = party.indexOf(playerPokemon); if (chosenIndex !== 0) { const leadPokemon = party[0]; @@ -175,36 +178,36 @@ async function summonPlayerPokemon(scene: BattleScene) { // Do trainer summon animation let playerAnimationPromise: Promise | undefined; - scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) })); - scene.pbTray.hide(); - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - scene.time.delayedCall(562, () => { - scene.trainer.setFrame("2"); - scene.time.delayedCall(64, () => { - scene.trainer.setFrame("3"); + globalScene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) })); + globalScene.pbTray.hide(); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + globalScene.time.delayedCall(562, () => { + globalScene.trainer.setFrame("2"); + globalScene.time.delayedCall(64, () => { + globalScene.trainer.setFrame("3"); }); }); - scene.tweens.add({ - targets: scene.trainer, + globalScene.tweens.add({ + targets: globalScene.trainer, x: -36, duration: 1000, - onComplete: () => scene.trainer.setVisible(false) + onComplete: () => globalScene.trainer.setVisible(false) }); - scene.time.delayedCall(750, () => { - playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon); + globalScene.time.delayedCall(750, () => { + playerAnimationPromise = summonPlayerPokemonAnimation(playerPokemon); }); // Also loads Wobbuffet data (cannot be shiny) const enemySpecies = getPokemonSpecies(Species.WOBBUFFET); - scene.currentBattle.enemyParty = []; - const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true); + globalScene.currentBattle.enemyParty = []; + const wobbuffet = globalScene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true); wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ]; wobbuffet.setNature(Nature.MILD); wobbuffet.setAlpha(0); wobbuffet.setVisible(false); wobbuffet.calculateStats(); - scene.currentBattle.enemyParty[0] = wobbuffet; - scene.gameData.setPokemonSeen(wobbuffet, true); + globalScene.currentBattle.enemyParty[0] = wobbuffet; + globalScene.gameData.setPokemonSeen(wobbuffet, true); await wobbuffet.loadAssets(); const id = setInterval(checkPlayerAnimationPromise, 500); async function checkPlayerAnimationPromise() { @@ -217,37 +220,37 @@ async function summonPlayerPokemon(scene: BattleScene) { }); } -function handleLoseMinigame(scene: BattleScene) { +function handleLoseMinigame() { return new Promise(async resolve => { // Check Wobbuffet is still alive - const wobbuffet = scene.getEnemyPokemon(); + const wobbuffet = globalScene.getEnemyPokemon(); if (!wobbuffet || wobbuffet.isFainted(true) || wobbuffet.hp === 0) { // Player loses // End the battle if (wobbuffet) { wobbuffet.hideInfo(); - scene.field.remove(wobbuffet); + globalScene.field.remove(wobbuffet); } - transitionMysteryEncounterIntroVisuals(scene, true, true); - scene.currentBattle.enemyParty = []; - scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; - leaveEncounterWithoutBattle(scene, true); - await showEncounterText(scene, `${namespace}:ko`); - const reviveCost = scene.getWaveMoneyAmount(1.5); - updatePlayerMoney(scene, -reviveCost, true, false); + transitionMysteryEncounterIntroVisuals(true, true); + globalScene.currentBattle.enemyParty = []; + globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; + leaveEncounterWithoutBattle(true); + await showEncounterText(`${namespace}:ko`); + const reviveCost = globalScene.getWaveMoneyAmount(1.5); + updatePlayerMoney(-reviveCost, true, false); } resolve(); }); } -function handleNextTurn(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +function handleNextTurn() { + const encounter = globalScene.currentBattle.mysteryEncounter!; - const wobbuffet = scene.getEnemyPokemon(); + const wobbuffet = globalScene.getEnemyPokemon(); if (!wobbuffet) { // Should never be triggered, just handling the edge case - handleLoseMinigame(scene); + handleLoseMinigame(); return true; } if (encounter.misc.turnsRemaining <= 0) { @@ -257,15 +260,15 @@ function handleNextTurn(scene: BattleScene) { let isHealPhase = false; if (healthRatio < 0.03) { // Grand prize - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MULTI_LENS ], fillRemaining: false }); resultMessageKey = `${namespace}:best_result`; } else if (healthRatio < 0.15) { // 2nd prize - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SCOPE_LENS ], fillRemaining: false }); resultMessageKey = `${namespace}:great_result`; } else if (healthRatio < 0.33) { // 3rd prize - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.WIDE_LENS ], fillRemaining: false }); resultMessageKey = `${namespace}:good_result`; } else { // No prize @@ -275,22 +278,22 @@ function handleNextTurn(scene: BattleScene) { // End the battle wobbuffet.hideInfo(); - scene.field.remove(wobbuffet); - scene.currentBattle.enemyParty = []; - scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; - leaveEncounterWithoutBattle(scene, isHealPhase); + globalScene.field.remove(wobbuffet); + globalScene.currentBattle.enemyParty = []; + globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined; + leaveEncounterWithoutBattle(isHealPhase); // Must end the TurnInit phase prematurely so battle phases aren't added to queue - queueEncounterMessage(scene, `${namespace}:end_game`); - queueEncounterMessage(scene, resultMessageKey); + queueEncounterMessage(`${namespace}:end_game`); + queueEncounterMessage(resultMessageKey); // Skip remainder of TurnInitPhase return true; } else { if (encounter.misc.turnsRemaining < 3) { // Display charging messages on turns that aren't the initial turn - queueEncounterMessage(scene, `${namespace}:charging_continue`); + queueEncounterMessage(`${namespace}:charging_continue`); } - queueEncounterMessage(scene, `${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`); + queueEncounterMessage(`${namespace}:turn_remaining_${encounter.misc.turnsRemaining}`); encounter.misc.turnsRemaining--; } @@ -298,33 +301,33 @@ function handleNextTurn(scene: BattleScene) { return false; } -async function showWobbuffetHealthBar(scene: BattleScene) { - const wobbuffet = scene.getEnemyPokemon()!; +async function showWobbuffetHealthBar() { + const wobbuffet = globalScene.getEnemyPokemon()!; - scene.add.existing(wobbuffet); - scene.field.add(wobbuffet); + globalScene.add.existing(wobbuffet); + globalScene.field.add(wobbuffet); - const playerPokemon = scene.getPlayerPokemon() as Pokemon; + const playerPokemon = globalScene.getPlayerPokemon() as Pokemon; if (playerPokemon?.isOnField()) { - scene.field.moveBelow(wobbuffet, playerPokemon); + globalScene.field.moveBelow(wobbuffet, playerPokemon); } // Show health bar and trigger cry wobbuffet.showInfo(); - scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { wobbuffet.cry(); }); wobbuffet.resetSummonData(); // Track the HP change across turns - scene.currentBattle.mysteryEncounter!.misc.wobbuffetHealth = wobbuffet.hp; + globalScene.currentBattle.mysteryEncounter!.misc.wobbuffetHealth = wobbuffet.hp; } -function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon): Promise { +function summonPlayerPokemonAnimation(pokemon: PlayerPokemon): Promise { return new Promise(resolve => { - const pokeball = scene.addFieldSprite(36, 80, "pb", getPokeballAtlasKey(pokemon.pokeball)); + const pokeball = globalScene.addFieldSprite(36, 80, "pb", getPokeballAtlasKey(pokemon.pokeball)); pokeball.setVisible(false); pokeball.setOrigin(0.5, 0.625); - scene.field.add(pokeball); + globalScene.field.add(pokeball); pokemon.setFieldPosition(FieldPosition.CENTER, 0); @@ -332,32 +335,32 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon pokeball.setVisible(true); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 650, x: 100 + fpOffset[0] }); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 150, ease: "Cubic.easeOut", y: 70 + fpOffset[1], onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 500, ease: "Cubic.easeIn", angle: 1440, y: 132 + fpOffset[1], onComplete: () => { - scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokeball.destroy(); - scene.add.existing(pokemon); - scene.field.add(pokemon); - addPokeballOpenParticles(scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); - scene.updateModifiers(true); - scene.updateFieldScale(); + globalScene.add.existing(pokemon); + globalScene.field.add(pokemon); + addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball); + globalScene.updateModifiers(true); + globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); pokemon.setVisible(true); @@ -365,8 +368,8 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon pokemon.setScale(0.5); pokemon.tint(getPokeballTintColor(pokemon.pokeball)); pokemon.untint(250, "Sine.easeIn"); - scene.updateFieldScale(); - scene.tweens.add({ + globalScene.updateFieldScale(); + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeIn", @@ -375,15 +378,15 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); pokemon.getSprite().clearTint(); pokemon.resetSummonData(); - scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { if (pokemon.isShiny()) { - scene.unshiftPhase(new ShinySparklePhase(scene, pokemon.getBattlerIndex())); + globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); } pokemon.resetTurnData(); - scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); - scene.pushPhase(new PostSummonPhase(scene, pokemon.getBattlerIndex())); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + globalScene.pushPhase(new PostSummonPhase(pokemon.getBattlerIndex())); resolve(); }); } @@ -395,13 +398,13 @@ function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon }); } -function hideShowmanIntroSprite(scene: BattleScene) { - const carnivalGame = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(0)[0]; - const wobbuffet = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1)[0]; - const showMan = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(2)[0]; +function hideShowmanIntroSprite() { + const carnivalGame = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(0)[0]; + const wobbuffet = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1)[0]; + const showMan = globalScene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(2)[0]; // Hide the showman - scene.tweens.add({ + globalScene.tweens.add({ targets: showMan, x: "+=16", y: "-=16", @@ -411,7 +414,7 @@ function hideShowmanIntroSprite(scene: BattleScene) { }); // Slide the Wobbuffet and Game over slightly - scene.tweens.add({ + globalScene.tweens.add({ targets: [ wobbuffet, carnivalGame ], x: "+=16", ease: "Sine.easeInOut", diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index b7376c2bfd2..b50ced69918 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -2,20 +2,26 @@ import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterReward import { TrainerSlot, } from "#app/data/trainer-config"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { MusicPreference } from "#app/system/settings/settings"; -import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; +import { getPlayerModifierTypeOptions, ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { Species } from "#enums/species"; -import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { getTypeRgb } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import PokemonData from "#app/system/pokemon-data"; import i18next from "i18next"; import { Gender, getGenderSymbol } from "#app/data/gender"; @@ -102,24 +108,24 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Load bgm let bgmKey: string; - if (scene.musicPreference === MusicPreference.GENFIVE) { + if (globalScene.musicPreference === MusicPreference.GENFIVE) { bgmKey = "mystery_encounter_gen_5_gts"; - scene.loadBgm(bgmKey, `${bgmKey}.mp3`); + globalScene.loadBgm(bgmKey, `${bgmKey}.mp3`); } else { // Mixed option bgmKey = "mystery_encounter_gen_6_gts"; - scene.loadBgm(bgmKey, `${bgmKey}.mp3`); + globalScene.loadBgm(bgmKey, `${bgmKey}.mp3`); } // Load possible trade options // Maps current party member's id to 3 EnemyPokemon objects // None of the trade options can be the same species - const tradeOptionsMap: Map = getPokemonTradeOptions(scene); + const tradeOptionsMap: Map = getPokemonTradeOptions(); encounter.misc = { tradeOptionsMap, bgmKey @@ -127,8 +133,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = return true; }) - .withOnVisualsStart((scene: BattleScene) => { - scene.fadeAndSwitchBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey); + .withOnVisualsStart(() => { + globalScene.fadeAndSwitchBgm(globalScene.currentBattle.mysteryEncounter!.misc.bgmKey); return true; }) .withOption( @@ -140,8 +146,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.1.tooltip`, secondOptionPrompt: `${namespace}:option.1.trade_options_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get the trade species options for the selected pokemon const tradeOptionsMap: Map = encounter.misc.tradeOptionsMap; @@ -165,17 +171,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[tradePokemon.formIndex].formName : null; const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); - showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false); + showEncounterText(`${line1}\n${line2}`, 0, 0, false); }, }; return option; }); }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); @@ -185,32 +191,32 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = encounter.setDialogueToken("tradeTrainerName", traderName.trim()); // Remove the original party member from party - scene.removePokemonFromPlayerParty(tradedPokemon, false); + globalScene.removePokemonFromPlayerParty(tradedPokemon, false); // Set data properly, then generate the new Pokemon's assets receivedPokemonData.passive = tradedPokemon.passive; // Pokeball to Ultra ball, randomly receivedPokemonData.pokeball = randInt(4) as PokeballType; const dataSource = new PokemonData(receivedPokemonData); - const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource); - scene.getPlayerParty().push(newPlayerPokemon); + const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource); + globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); for (const mod of modifiers) { mod.pokemonId = newPlayerPokemon.id; - scene.addModifier(mod, true, false, false, true); + globalScene.addModifier(mod, true, false, false, true); } // Show the trade animation - await showTradeBackground(scene); - await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); - await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000); - scene.playBgm(encounter.misc.bgmKey); - await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); - await hideTradeBackground(scene); + await showTradeBackground(); + await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); + await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000); + globalScene.playBgm(encounter.misc.bgmKey); + await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon); + await hideTradeBackground(); tradedPokemon.destroy(); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -222,19 +228,19 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Randomly generate a Wonder Trade pokemon - const randomTradeOption = generateTradeOption(scene.getPlayerParty().map(p => p.species)); - const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); + const randomTradeOption = generateTradeOption(globalScene.getPlayerParty().map(p => p.species)); + const tradePokemon = new EnemyPokemon(randomTradeOption, pokemon.level, TrainerSlot.NONE, false); // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE); - if (scene.eventManager.isEventActive()) { - shinyThreshold.value *= scene.eventManager.getShinyMultiplier(); + if (globalScene.eventManager.isEventActive()) { + shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); } - scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); // Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms // Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that @@ -248,7 +254,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = if (tradePokemon.species.abilityHidden) { if (tradePokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new NumberHolder(64); - scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -281,10 +287,10 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = encounter.misc.receivedPokemon = tradePokemon; }; - return selectPokemonForOption(scene, onPokemonSelected); + return selectPokemonForOption(onPokemonSelected); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; const modifiers = tradedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); @@ -294,31 +300,31 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = encounter.setDialogueToken("tradeTrainerName", traderName.trim()); // Remove the original party member from party - scene.removePokemonFromPlayerParty(tradedPokemon, false); + globalScene.removePokemonFromPlayerParty(tradedPokemon, false); // Set data properly, then generate the new Pokemon's assets receivedPokemonData.passive = tradedPokemon.passive; receivedPokemonData.pokeball = randInt(4) as PokeballType; const dataSource = new PokemonData(receivedPokemonData); - const newPlayerPokemon = scene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource); - scene.getPlayerParty().push(newPlayerPokemon); + const newPlayerPokemon = globalScene.addPlayerPokemon(receivedPokemonData.species, receivedPokemonData.level, dataSource.abilityIndex, dataSource.formIndex, dataSource.gender, dataSource.shiny, dataSource.variant, dataSource.ivs, dataSource.nature, dataSource); + globalScene.getPlayerParty().push(newPlayerPokemon); await newPlayerPokemon.loadAssets(); for (const mod of modifiers) { mod.pokemonId = newPlayerPokemon.id; - scene.addModifier(mod, true, false, false, true); + globalScene.addModifier(mod, true, false, false, true); } // Show the trade animation - await showTradeBackground(scene); - await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon); - await showEncounterText(scene, `${namespace}:trade_received`, null, 0, true, 4000); - scene.playBgm(encounter.misc.bgmKey); - await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon); - await hideTradeBackground(scene); + await showTradeBackground(); + await doPokemonTradeSequence(tradedPokemon, newPlayerPokemon); + await showEncounterText(`${namespace}:trade_received`, null, 0, true, 4000); + globalScene.playBgm(encounter.misc.bgmKey); + await addPokemonDataToDexAndValidateAchievements(newPlayerPokemon); + await hideTradeBackground(); tradedPokemon.destroy(); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -330,8 +336,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = buttonTooltip: `${namespace}:option.3.tooltip`, secondOptionPrompt: `${namespace}:option.3.trade_options_prompt`, }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Get Pokemon held items and filter for valid ones const validItems = pokemon.getHeldItems().filter((it) => { @@ -359,18 +365,18 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = return it.isTransferable; }).length > 0; if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:option.3.invalid_selection`) ?? null; + return getEncounterText(`${namespace}:option.3.invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier as PokemonHeldItemModifier; - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon; // Check tier of the traded item, the received item will be one tier up @@ -397,16 +403,16 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = } encounter.setDialogueToken("itemName", item.type.name); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: [ item ], fillRemaining: false }); chosenPokemon.loseHeldItem(modifier, false); - await scene.updateModifiers(true, true); + await globalScene.updateModifiers(true, true); // Generate a trainer name const traderName = generateRandomTraderName(); encounter.setDialogueToken("tradeTrainerName", traderName.trim()); - await showEncounterText(scene, `${namespace}:item_trade_selected`); - leaveEncounterWithoutBattle(scene); + await showEncounterText(`${namespace}:item_trade_selected`); + leaveEncounterWithoutBattle(); }) .build() ) @@ -420,26 +426,26 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) .build(); -function getPokemonTradeOptions(scene: BattleScene): Map { +function getPokemonTradeOptions(): Map { const tradeOptionsMap: Map = new Map(); // Starts by filtering out any current party members as valid resulting species - const alreadyUsedSpecies: PokemonSpecies[] = scene.getPlayerParty().map(p => p.species); + const alreadyUsedSpecies: PokemonSpecies[] = globalScene.getPlayerParty().map(p => p.species); - scene.getPlayerParty().forEach(pokemon => { + globalScene.getPlayerParty().forEach(pokemon => { // If the party member is legendary/mythical, the only trade options available are always pulled from generation-specific legendary trade pools if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) { const generation = pokemon.species.generation; const tradeOptions: EnemyPokemon[] = LEGENDARY_TRADE_POOLS[generation].map(s => { const pokemonSpecies = getPokemonSpecies(s); - return new EnemyPokemon(scene, pokemonSpecies, 5, TrainerSlot.NONE, false); + return new EnemyPokemon(pokemonSpecies, 5, TrainerSlot.NONE, false); }); tradeOptionsMap.set(pokemon.id, tradeOptions); } else { @@ -454,7 +460,7 @@ function getPokemonTradeOptions(scene: BattleScene): Map // Add trade options to map tradeOptionsMap.set(pokemon.id, tradeOptions.map(s => { - return new EnemyPokemon(scene, s, pokemon.level, TrainerSlot.NONE, false); + return new EnemyPokemon(s, pokemon.level, TrainerSlot.NONE, false); })); } }); @@ -497,28 +503,28 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: return newSpecies!; } -function showTradeBackground(scene: BattleScene) { +function showTradeBackground() { return new Promise(resolve => { - const tradeContainer = scene.add.container(0, -scene.game.canvas.height / 6); + const tradeContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); tradeContainer.setName("Trade Background"); - const flyByStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0); + const flyByStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0); flyByStaticBg.setName("Black Background"); flyByStaticBg.setOrigin(0, 0); flyByStaticBg.setVisible(false); tradeContainer.add(flyByStaticBg); - const tradeBaseBg = scene.add.image(0, 0, "default_bg"); + const tradeBaseBg = globalScene.add.image(0, 0, "default_bg"); tradeBaseBg.setName("Trade Background Image"); tradeBaseBg.setOrigin(0, 0); tradeContainer.add(tradeBaseBg); - scene.fieldUI.add(tradeContainer); - scene.fieldUI.bringToTop(tradeContainer); + globalScene.fieldUI.add(tradeContainer); + globalScene.fieldUI.bringToTop(tradeContainer); tradeContainer.setVisible(true); tradeContainer.alpha = 0; - scene.tweens.add({ + globalScene.tweens.add({ targets: tradeContainer, alpha: 1, duration: 500, @@ -530,17 +536,17 @@ function showTradeBackground(scene: BattleScene) { }); } -function hideTradeBackground(scene: BattleScene) { +function hideTradeBackground() { return new Promise(resolve => { - const transformationContainer = scene.fieldUI.getByName("Trade Background"); + const transformationContainer = globalScene.fieldUI.getByName("Trade Background"); - scene.tweens.add({ + globalScene.tweens.add({ targets: transformationContainer, alpha: 0, duration: 1000, ease: "Sine.easeInOut", onComplete: () => { - scene.fieldUI.remove(transformationContainer, true); + globalScene.fieldUI.remove(transformationContainer, true); resolve(); } }); @@ -549,13 +555,12 @@ function hideTradeBackground(scene: BattleScene) { /** * Initiates an "evolution-like" animation to transform a previousPokemon (presumably from the player's party) into a new one, not necessarily an evolution species. - * @param scene * @param tradedPokemon * @param receivedPokemon */ -function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon, receivedPokemon: PlayerPokemon) { +function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: PlayerPokemon) { return new Promise(resolve => { - const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; + const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image; let tradedPokemonSprite: Phaser.GameObjects.Sprite; @@ -564,8 +569,8 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon let receivedPokemonTintSprite: Phaser.GameObjects.Sprite; const getPokemonSprite = () => { - const ret = scene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub"); - ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + const ret = globalScene.addPokemonSprite(tradedPokemon, tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pkmn__sub"); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); return ret; }; @@ -589,7 +594,7 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", tradedPokemon.shiny); @@ -610,7 +615,7 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); sprite.setPipelineData("shiny", receivedPokemon.shiny); @@ -625,45 +630,45 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon // Traded pokemon pokeball const tradedPbAtlasKey = getPokeballAtlasKey(tradedPokemon.pokeball); - const tradedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey); + const tradedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", tradedPbAtlasKey); tradedPokeball.setVisible(false); tradeContainer.add(tradedPokeball); // Received pokemon pokeball const receivedPbAtlasKey = getPokeballAtlasKey(receivedPokemon.pokeball); - const receivedPokeball: Phaser.GameObjects.Sprite = scene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey); + const receivedPokeball: Phaser.GameObjects.Sprite = globalScene.add.sprite(tradeBaseBg.displayWidth / 2, tradeBaseBg.displayHeight / 2, "pb", receivedPbAtlasKey); receivedPokeball.setVisible(false); tradeContainer.add(receivedPokeball); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokemonSprite, alpha: 1, ease: "Cubic.easeInOut", duration: 500, onComplete: async () => { - scene.fadeOutBgm(1000, false); - await showEncounterText(scene, `${namespace}:pokemon_trade_selected`); + globalScene.fadeOutBgm(1000, false); + await showEncounterText(`${namespace}:pokemon_trade_selected`); tradedPokemon.cry(); - scene.playBgm("evolution"); - await showEncounterText(scene, `${namespace}:pokemon_trade_goodbye`); + globalScene.playBgm("evolution"); + await showEncounterText(`${namespace}:pokemon_trade_goodbye`); tradedPokeball.setAlpha(0); tradedPokeball.setVisible(true); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokeball, alpha: 1, ease: "Cubic.easeInOut", duration: 250, onComplete: () => { tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`); - scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_open`)); - scene.playSound("se/pb_rel"); + globalScene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_open`)); + globalScene.playSound("se/pb_rel"); tradedPokemonTintSprite.setVisible(true); // TODO: need to add particles to fieldUI instead of field - // addPokeballOpenParticles(scene, tradedPokemon.x, tradedPokemon.y, tradedPokemon.pokeball); + // addPokeballOpenParticles(tradedPokemon.x, tradedPokemon.y, tradedPokemon.pokeball); - scene.tweens.add({ + globalScene.tweens.add({ targets: [ tradedPokemonTintSprite, tradedPokemonSprite ], duration: 500, ease: "Sine.easeIn", @@ -672,30 +677,30 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon tradedPokemonSprite.setVisible(false); tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}_opening`); tradedPokemonTintSprite.setVisible(false); - scene.playSound("se/pb_catch"); - scene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}`)); + globalScene.playSound("se/pb_catch"); + globalScene.time.delayedCall(17, () => tradedPokeball.setTexture("pb", `${tradedPbAtlasKey}`)); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokeball, y: "+=10", duration: 200, delay: 250, ease: "Cubic.easeIn", onComplete: () => { - scene.playSound("se/pb_bounce_1"); + globalScene.playSound("se/pb_bounce_1"); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokeball, y: "-=100", duration: 200, delay: 1000, ease: "Cubic.easeInOut", onStart: () => { - scene.playSound("se/pb_throw"); + globalScene.playSound("se/pb_throw"); }, onComplete: async () => { - await doPokemonTradeFlyBySequence(scene, tradedPokemonSprite, receivedPokemonSprite); - await doTradeReceivedSequence(scene, receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey); + await doPokemonTradeFlyBySequence(tradedPokemonSprite, receivedPokemonSprite); + await doTradeReceivedSequence(receivedPokemon, receivedPokemonSprite, receivedPokemonTintSprite, receivedPokeball, receivedPbAtlasKey); resolve(); } }); @@ -710,9 +715,9 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon }); } -function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) { +function doPokemonTradeFlyBySequence(tradedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonSprite: Phaser.GameObjects.Sprite) { return new Promise(resolve => { - const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; + const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image; const flyByStaticBg = tradeContainer.getByName("Black Background") as Phaser.GameObjects.Rectangle; flyByStaticBg.setVisible(true); @@ -733,47 +738,47 @@ function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Ph const BASE_ANIM_DURATION = 1000; // Fade out trade background - scene.tweens.add({ + globalScene.tweens.add({ targets: tradeBaseBg, alpha: 0, ease: "Cubic.easeInOut", duration: FADE_DELAY, onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: [ receivedPokemonSprite, tradedPokemonSprite ], y: tradeBaseBg.displayWidth / 2 - 100, ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION * 3, onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: receivedPokemonSprite, x: tradeBaseBg.displayWidth / 4, ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION / 2, delay: ANIM_DELAY }); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokemonSprite, x: tradeBaseBg.displayWidth * 3 / 4, ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION / 2, delay: ANIM_DELAY, onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: receivedPokemonSprite, y: "+=200", ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION * 2, delay: ANIM_DELAY, }); - scene.tweens.add({ + globalScene.tweens.add({ targets: tradedPokemonSprite, y: "-=200", ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION * 2, delay: ANIM_DELAY, onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: tradeBaseBg, alpha: 1, ease: "Cubic.easeInOut", @@ -793,9 +798,9 @@ function doPokemonTradeFlyBySequence(scene: BattleScene, tradedPokemonSprite: Ph }); } -function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) { +function doTradeReceivedSequence(receivedPokemon: PlayerPokemon, receivedPokemonSprite: Phaser.GameObjects.Sprite, receivedPokemonTintSprite: Phaser.GameObjects.Sprite, receivedPokeballSprite: Phaser.GameObjects.Sprite, receivedPbAtlasKey: string) { return new Promise(resolve => { - const tradeContainer = scene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; + const tradeContainer = globalScene.fieldUI.getByName("Trade Background") as Phaser.GameObjects.Container; const tradeBaseBg = tradeContainer.getByName("Trade Background Image") as Phaser.GameObjects.Image; receivedPokemonSprite.setVisible(false); @@ -812,7 +817,7 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke // Received pokemon sparkles let pokemonShinySparkle: Phaser.GameObjects.Sprite; if (receivedPokemon.shiny) { - pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny"); + pokemonShinySparkle = globalScene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny"); pokemonShinySparkle.setVisible(false); tradeContainer.add(pokemonShinySparkle); } @@ -820,19 +825,19 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke const BASE_ANIM_DURATION = 1000; // Pokeball falls to the screen - scene.playSound("se/pb_throw"); - scene.tweens.add({ + globalScene.playSound("se/pb_throw"); + globalScene.tweens.add({ targets: receivedPokeballSprite, y: "+=100", ease: "Cubic.easeInOut", duration: BASE_ANIM_DURATION, onComplete: () => { - scene.playSound("se/pb_bounce_1"); - scene.time.delayedCall(100, () => scene.playSound("se/pb_bounce_1")); + globalScene.playSound("se/pb_bounce_1"); + globalScene.time.delayedCall(100, () => globalScene.playSound("se/pb_bounce_1")); - scene.time.delayedCall(2000, () => { - scene.playSound("se/pb_rel"); - scene.fadeOutBgm(500, false); + globalScene.time.delayedCall(2000, () => { + globalScene.playSound("se/pb_rel"); + globalScene.fadeOutBgm(500, false); receivedPokemon.cry(); receivedPokemonTintSprite.scale = 0.25; receivedPokemonTintSprite.alpha = 1; @@ -841,14 +846,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke receivedPokemonTintSprite.alpha = 1; receivedPokemonTintSprite.setVisible(true); receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_opening`); - scene.time.delayedCall(17, () => receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_open`)); - scene.tweens.add({ + globalScene.time.delayedCall(17, () => receivedPokeballSprite.setTexture("pb", `${receivedPbAtlasKey}_open`)); + globalScene.tweens.add({ targets: receivedPokemonSprite, duration: 250, ease: "Sine.easeOut", scale: 1 }); - scene.tweens.add({ + globalScene.tweens.add({ targets: receivedPokemonTintSprite, duration: 250, ease: "Sine.easeOut", @@ -856,12 +861,12 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke alpha: 0, onComplete: () => { if (receivedPokemon.shiny) { - scene.time.delayedCall(500, () => { - doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant); + globalScene.time.delayedCall(500, () => { + doShinySparkleAnim(pokemonShinySparkle, receivedPokemon.variant); }); } receivedPokeballSprite.destroy(); - scene.time.delayedCall(2000, () => resolve()); + globalScene.time.delayedCall(2000, () => resolve()); } }); }); diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index a8cb076bbe9..34c808359b7 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -2,8 +2,9 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils"; import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -41,8 +42,8 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with }, ]) .withIntroDialogue([{ text: `${namespace}:intro` }]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE)); encounter.setDialogueToken("option1RequiredMove", new PokemonMove(OPTION_1_REQUIRED_MOVE).getName()); @@ -70,7 +71,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with }, ], }) - .withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene)) + .withOptionPhase(async () => handlePokemonGuidingYouPhase()) .build() ) .withOption( @@ -89,7 +90,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with }, ], }) - .withOptionPhase(async (scene: BattleScene) => handlePokemonGuidingYouPhase(scene)) + .withOptionPhase(async () => handlePokemonGuidingYouPhase()) .build() ) .withSimpleOption( @@ -103,16 +104,16 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with }, ], }, - async (scene: BattleScene) => { - const allowedPokemon = scene.getPlayerParty().filter((p) => p.isAllowedInBattle()); + async () => { + const allowedPokemon = globalScene.getPlayerParty().filter((p) => p.isAllowedInBattle()); for (const pkm of allowedPokemon) { const percentage = DAMAGE_PERCENTAGE / 100; const damage = Math.floor(pkm.getMaxHp() * percentage); - applyDamageToPokemon(scene, pkm, damage); + applyDamageToPokemon(pkm, damage); } - leaveEncounterWithoutBattle(scene); + leaveEncounterWithoutBattle(); return true; } @@ -126,19 +127,17 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with /** * Generic handler for using a guiding pokemon to guide you back. - * - * @param scene Battle scene */ -function handlePokemonGuidingYouPhase(scene: BattleScene) { +function handlePokemonGuidingYouPhase() { const laprasSpecies = getPokemonSpecies(Species.LAPRAS); - const { mysteryEncounter } = scene.currentBattle; + const { mysteryEncounter } = globalScene.currentBattle; if (mysteryEncounter?.selectedOption?.primaryPokemon?.id) { - setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true); + setEncounterExp(mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true); } else { console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?"); } - leaveEncounterWithoutBattle(scene); + leaveEncounterWithoutBattle(); return true; } diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 7fdd29d36a2..36e12b34e10 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -1,5 +1,6 @@ +import type { + EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { - EnemyPartyConfig, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; @@ -13,9 +14,10 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "#app/utils"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -37,12 +39,12 @@ export const MysteriousChallengersEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculates what trainers are available for battle in the encounter // Normal difficulty trainer is randomly pulled from biome - const normalTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + const normalTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); const normalConfig = trainerConfigs[normalTrainerType].clone(); let female = false; if (normalConfig.hasGenders) { @@ -57,16 +59,16 @@ export const MysteriousChallengersEncounter: MysteryEncounter = // Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config // Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100 let retries = 0; - let hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + let hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); while (retries < 5 && hardTrainerType === normalTrainerType) { // Will try to use a different trainer from the normal trainer type - hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + hardTrainerType = globalScene.arena.randomTrainerType(globalScene.currentBattle.waveIndex); retries++; } const hardTemplate = new TrainerPartyCompoundTemplate( new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true), new TrainerPartyTemplate( - Math.min(Math.ceil(scene.currentBattle.waveIndex / 20), 5), + Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 20), 5), PartyMemberStrength.AVERAGE, false, true @@ -87,8 +89,8 @@ export const MysteriousChallengersEncounter: MysteryEncounter = // Brutal trainer is pulled from pool of boss trainers (gym leaders) for the biome // They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons - const brutalTrainerType = scene.arena.randomTrainerType( - scene.currentBattle.waveIndex, + const brutalTrainerType = globalScene.arena.randomTrainerType( + globalScene.currentBattle.waveIndex, true ); const e4Template = trainerPartyTemplates.ELITE_FOUR; @@ -145,18 +147,18 @@ export const MysteriousChallengersEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn standard trainer battle with memory mushroom reward const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true }); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams let initBattlePromise: Promise; - scene.executeWithSeedOffset(() => { - initBattlePromise = initBattleWithEnemyConfig(scene, config); - }, scene.currentBattle.waveIndex * 10); + globalScene.executeWithSeedOffset(() => { + initBattlePromise = initBattleWithEnemyConfig(config); + }, globalScene.currentBattle.waveIndex * 10); await initBattlePromise!; } ) @@ -170,18 +172,18 @@ export const MysteriousChallengersEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn hard fight const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true }); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams let initBattlePromise: Promise; - scene.executeWithSeedOffset(() => { - initBattlePromise = initBattleWithEnemyConfig(scene, config); - }, scene.currentBattle.waveIndex * 100); + globalScene.executeWithSeedOffset(() => { + initBattlePromise = initBattleWithEnemyConfig(config); + }, globalScene.currentBattle.waveIndex * 100); await initBattlePromise!; } ) @@ -195,21 +197,21 @@ export const MysteriousChallengersEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn brutal fight const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2]; // To avoid player level snowballing from picking this option encounter.expMultiplier = 0.9; - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams let initBattlePromise: Promise; - scene.executeWithSeedOffset(() => { - initBattlePromise = initBattleWithEnemyConfig(scene, config); - }, scene.currentBattle.waveIndex * 1000); + globalScene.executeWithSeedOffset(() => { + initBattlePromise = initBattleWithEnemyConfig(config); + }, globalScene.currentBattle.waveIndex * 1000); await initBattlePromise!; } ) diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 877deee66b7..9b4999020d0 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -1,8 +1,10 @@ -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -66,8 +68,8 @@ export const MysteriousChestEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon const config: EnemyPartyConfig = { @@ -106,9 +108,9 @@ export const MysteriousChestEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Play animation - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const introVisuals = encounter.introVisuals!; // Determine roll first @@ -128,13 +130,13 @@ export const MysteriousChestEncounter: MysteryEncounter = introVisuals.spriteConfigs[1].disableAnimation = false; introVisuals.playAnim(); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Open the chest - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const roll = encounter.misc.roll; if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { // Choose between 2 COMMON / 2 GREAT tier items (20%) - setEncounterRewards(scene, { + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, @@ -143,11 +145,11 @@ export const MysteriousChestEncounter: MysteryEncounter = ], }); // Display result message then proceed to rewards - queueEncounterMessage(scene, `${namespace}:option.1.normal`); - leaveEncounterWithoutBattle(scene); + queueEncounterMessage(`${namespace}:option.1.normal`); + leaveEncounterWithoutBattle(); } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { // Choose between 3 ULTRA tier items (30%) - setEncounterRewards(scene, { + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, @@ -155,39 +157,39 @@ export const MysteriousChestEncounter: MysteryEncounter = ], }); // Display result message then proceed to rewards - queueEncounterMessage(scene, `${namespace}:option.1.good`); - leaveEncounterWithoutBattle(scene); + queueEncounterMessage(`${namespace}:option.1.good`); + leaveEncounterWithoutBattle(); } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { // Choose between 2 ROGUE tier items (10%) - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]}); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE ]}); // Display result message then proceed to rewards - queueEncounterMessage(scene, `${namespace}:option.1.great`); - leaveEncounterWithoutBattle(scene); + queueEncounterMessage(`${namespace}:option.1.great`); + leaveEncounterWithoutBattle(); } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) { // Choose 1 MASTER tier item (5%) - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.MASTER ]}); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.MASTER ]}); // Display result message then proceed to rewards - queueEncounterMessage(scene, `${namespace}:option.1.amazing`); - leaveEncounterWithoutBattle(scene); + queueEncounterMessage(`${namespace}:option.1.amazing`); + leaveEncounterWithoutBattle(); } else { // Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%) - const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true, false); - koPlayerPokemon(scene, highestLevelPokemon); + const highestLevelPokemon = getHighestLevelPlayerPokemon(true, false); + koPlayerPokemon(highestLevelPokemon); encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender()); - await showEncounterText(scene, `${namespace}:option.1.bad`); + await showEncounterText(`${namespace}:option.1.bad`); // Handle game over edge case - const allowedPokemon = scene.getPokemonAllowedInBattle(); + const allowedPokemon = globalScene.getPokemonAllowedInBattle(); if (allowedPokemon.length === 0) { // If there are no longer any legal pokemon in the party, game over. - scene.clearPhaseQueue(); - scene.unshiftPhase(new GameOverPhase(scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase()); } else { // Show which Pokemon was KOed, then start battle against Gimmighoul - await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); - setEncounterRewards(scene, { fillRemaining: true }); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await transitionMysteryEncounterIntroVisuals(true, true, 500); + setEncounterRewards({ fillRemaining: true }); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); } } }) @@ -203,9 +205,9 @@ export const MysteriousChestEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 092d2ab2673..4db8e2eb6e8 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -1,8 +1,9 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -10,7 +11,8 @@ import { Stat } from "#enums/stat"; import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "i18next"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -52,20 +54,20 @@ export const PartTimerEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) - .withOnInit((scene: BattleScene) => { + .withOnInit(() => { // Load sfx - scene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav"); - scene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav"); - scene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav"); - scene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav"); + globalScene.loadSe("PRSFX- Horn Drill1", "battle_anims", "PRSFX- Horn Drill1.wav"); + globalScene.loadSe("PRSFX- Horn Drill3", "battle_anims", "PRSFX- Horn Drill3.wav"); + globalScene.loadSe("PRSFX- Guillotine2", "battle_anims", "PRSFX- Guillotine2.wav"); + globalScene.loadSe("PRSFX- Heavy Slam2", "battle_anims", "PRSFX- Heavy Slam2.wav"); - scene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav"); - scene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav"); - scene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav"); + globalScene.loadSe("PRSFX- Agility", "battle_anims", "PRSFX- Agility.wav"); + globalScene.loadSe("PRSFX- Extremespeed1", "battle_anims", "PRSFX- Extremespeed1.wav"); + globalScene.loadSe("PRSFX- Accelerock1", "battle_anims", "PRSFX- Accelerock1.wav"); - scene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav"); - scene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav"); - scene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav"); + globalScene.loadSe("PRSFX- Captivate", "battle_anims", "PRSFX- Captivate.wav"); + globalScene.loadSe("PRSFX- Attract2", "battle_anims", "PRSFX- Attract2.wav"); + globalScene.loadSe("PRSFX- Aurora Veil2", "battle_anims", "PRSFX- Aurora Veil2.wav"); return true; }) @@ -84,8 +86,8 @@ export const PartTimerEncounter: MysteryEncounter = } ] }) - .withPreOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); @@ -109,41 +111,41 @@ export const PartTimerEncounter: MysteryEncounter = } }); - setEncounterExp(scene, pokemon.id, 100); + setEncounterExp(pokemon.id, 100); // Hide intro visuals - transitionMysteryEncounterIntroVisuals(scene, true, false); + transitionMysteryEncounterIntroVisuals(true, false); // Play sfx for "working" - doDeliverySfx(scene); + doDeliverySfx(); }; // Only Pokemon non-KOd pokemon can be selected const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick Deliveries // Bring visuals back in - await transitionMysteryEncounterIntroVisuals(scene, false, false); + await transitionMysteryEncounterIntroVisuals(false, false); - const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier; + const moneyMultiplier = globalScene.currentBattle.mysteryEncounter!.misc.moneyMultiplier; // Give money and do dialogue if (moneyMultiplier > 2.5) { - await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`); + await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`); } else { - await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`); + await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`); } - const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier); - updatePlayerMoney(scene, moneyChange, true, false); - await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); - await showEncounterText(scene, `${namespace}:pokemon_tired`); + const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); + updatePlayerMoney(moneyChange, true, false); + await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); + await showEncounterText(`${namespace}:pokemon_tired`); - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(); }) .build() ) @@ -158,8 +160,8 @@ export const PartTimerEncounter: MysteryEncounter = } ] }) - .withPreOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); @@ -186,41 +188,41 @@ export const PartTimerEncounter: MysteryEncounter = } }); - setEncounterExp(scene, pokemon.id, 100); + setEncounterExp(pokemon.id, 100); // Hide intro visuals - transitionMysteryEncounterIntroVisuals(scene, true, false); + transitionMysteryEncounterIntroVisuals(true, false); // Play sfx for "working" - doStrongWorkSfx(scene); + doStrongWorkSfx(); }; // Only Pokemon non-KOd pokemon can be selected const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Pick Move Warehouse items // Bring visuals back in - await transitionMysteryEncounterIntroVisuals(scene, false, false); + await transitionMysteryEncounterIntroVisuals(false, false); - const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier; + const moneyMultiplier = globalScene.currentBattle.mysteryEncounter!.misc.moneyMultiplier; // Give money and do dialogue if (moneyMultiplier > 2.5) { - await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`); + await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`); } else { - await showEncounterDialogue(scene, `${namespace}:job_complete_bad`, `${namespace}:speaker`); + await showEncounterDialogue(`${namespace}:job_complete_bad`, `${namespace}:speaker`); } - const moneyChange = scene.getWaveMoneyAmount(moneyMultiplier); - updatePlayerMoney(scene, moneyChange, true, false); - await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); - await showEncounterText(scene, `${namespace}:pokemon_tired`); + const moneyChange = globalScene.getWaveMoneyAmount(moneyMultiplier); + updatePlayerMoney(moneyChange, true, false); + await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); + await showEncounterText(`${namespace}:pokemon_tired`); - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(); }) .build() ) @@ -238,8 +240,8 @@ export const PartTimerEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const selectedPokemon = encounter.selectedOption?.primaryPokemon!; encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender()); @@ -251,28 +253,28 @@ export const PartTimerEncounter: MysteryEncounter = } }); - setEncounterExp(scene, selectedPokemon.id, 100); + setEncounterExp(selectedPokemon.id, 100); // Hide intro visuals - transitionMysteryEncounterIntroVisuals(scene, true, false); + transitionMysteryEncounterIntroVisuals(true, false); // Play sfx for "working" - doSalesSfx(scene); + doSalesSfx(); return true; }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Assist with Sales // Bring visuals back in - await transitionMysteryEncounterIntroVisuals(scene, false, false); + await transitionMysteryEncounterIntroVisuals(false, false); // Give money and do dialogue - await showEncounterDialogue(scene, `${namespace}:job_complete_good`, `${namespace}:speaker`); - const moneyChange = scene.getWaveMoneyAmount(2.5); - updatePlayerMoney(scene, moneyChange, true, false); - await showEncounterText(scene, i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); - await showEncounterText(scene, `${namespace}:pokemon_tired`); + await showEncounterDialogue(`${namespace}:job_complete_good`, `${namespace}:speaker`); + const moneyChange = globalScene.getWaveMoneyAmount(2.5); + updatePlayerMoney(moneyChange, true, false); + await showEncounterText(i18next.t("mysteryEncounterMessages:receive_money", { amount: moneyChange })); + await showEncounterText(`${namespace}:pokemon_tired`); - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(); }) .build() ) @@ -284,51 +286,51 @@ export const PartTimerEncounter: MysteryEncounter = ]) .build(); -function doStrongWorkSfx(scene: BattleScene) { - scene.playSound("battle_anims/PRSFX- Horn Drill1"); - scene.playSound("battle_anims/PRSFX- Horn Drill1"); +function doStrongWorkSfx() { + globalScene.playSound("battle_anims/PRSFX- Horn Drill1"); + globalScene.playSound("battle_anims/PRSFX- Horn Drill1"); - scene.time.delayedCall(1000, () => { - scene.playSound("battle_anims/PRSFX- Guillotine2"); + globalScene.time.delayedCall(1000, () => { + globalScene.playSound("battle_anims/PRSFX- Guillotine2"); }); - scene.time.delayedCall(2000, () => { - scene.playSound("battle_anims/PRSFX- Heavy Slam2"); + globalScene.time.delayedCall(2000, () => { + globalScene.playSound("battle_anims/PRSFX- Heavy Slam2"); }); - scene.time.delayedCall(2500, () => { - scene.playSound("battle_anims/PRSFX- Guillotine2"); + globalScene.time.delayedCall(2500, () => { + globalScene.playSound("battle_anims/PRSFX- Guillotine2"); }); } -function doDeliverySfx(scene: BattleScene) { - scene.playSound("battle_anims/PRSFX- Accelerock1"); +function doDeliverySfx() { + globalScene.playSound("battle_anims/PRSFX- Accelerock1"); - scene.time.delayedCall(1500, () => { - scene.playSound("battle_anims/PRSFX- Extremespeed1"); + globalScene.time.delayedCall(1500, () => { + globalScene.playSound("battle_anims/PRSFX- Extremespeed1"); }); - scene.time.delayedCall(2000, () => { - scene.playSound("battle_anims/PRSFX- Extremespeed1"); + globalScene.time.delayedCall(2000, () => { + globalScene.playSound("battle_anims/PRSFX- Extremespeed1"); }); - scene.time.delayedCall(2250, () => { - scene.playSound("battle_anims/PRSFX- Agility"); + globalScene.time.delayedCall(2250, () => { + globalScene.playSound("battle_anims/PRSFX- Agility"); }); } -function doSalesSfx(scene: BattleScene) { - scene.playSound("battle_anims/PRSFX- Captivate"); +function doSalesSfx() { + globalScene.playSound("battle_anims/PRSFX- Captivate"); - scene.time.delayedCall(1500, () => { - scene.playSound("battle_anims/PRSFX- Attract2"); + globalScene.time.delayedCall(1500, () => { + globalScene.playSound("battle_anims/PRSFX- Attract2"); }); - scene.time.delayedCall(2000, () => { - scene.playSound("battle_anims/PRSFX- Aurora Veil2"); + globalScene.time.delayedCall(2000, () => { + globalScene.playSound("battle_anims/PRSFX- Aurora Veil2"); }); - scene.time.delayedCall(3000, () => { - scene.playSound("battle_anims/PRSFX- Attract2"); + globalScene.time.delayedCall(3000, () => { + globalScene.playSound("battle_anims/PRSFX- Attract2"); }); } diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index e16cf2d6973..fd078e1ffaa 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -1,15 +1,18 @@ import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#app/data/trainer-config"; import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier"; -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { PokeballType } from "#enums/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { IntegerHolder, randSeedInt } from "#app/utils"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterCost, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -59,8 +62,8 @@ export const SafariZoneEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString()); + .withOnInit(() => { + globalScene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString()); return true; }) .withOption(MysteryEncounterOptionBuilder @@ -75,25 +78,25 @@ export const SafariZoneEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Start safari encounter - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.continuousEncounter = true; encounter.misc = { safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS }; - updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); + updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Load bait/mud assets - scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); - scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav"); - scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav"); - scene.loadAtlas("safari_zone_bait", "mystery-encounters"); - scene.loadAtlas("safari_zone_mud", "mystery-encounters"); + globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav"); + globalScene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav"); + globalScene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav"); + globalScene.loadAtlas("safari_zone_bait", "mystery-encounters"); + globalScene.loadAtlas("safari_zone_mud", "mystery-encounters"); // Clear enemy party - scene.currentBattle.enemyParty = []; - await transitionMysteryEncounterIntroVisuals(scene); - await summonSafariPokemon(scene); - initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, hideDescription: true }); + globalScene.currentBattle.enemyParty = []; + await transitionMysteryEncounterIntroVisuals(); + await summonSafariPokemon(); + initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, hideDescription: true }); return true; }) .build() @@ -108,9 +111,9 @@ export const SafariZoneEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) @@ -143,26 +146,26 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ } ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Throw a ball option - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemon = encounter.misc.pokemon; - const catchResult = await throwPokeball(scene, pokemon); + const catchResult = await throwPokeball(pokemon); if (catchResult) { // You caught pokemon // Check how many safari pokemon left if (encounter.misc.safariPokemonRemaining > 0) { - await summonSafariPokemon(scene); - initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true }); + await summonSafariPokemon(); + initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true }); } else { // End safari mode encounter.continuousEncounter = false; - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); } } else { // Pokemon catch failed, end turn - await doEndTurn(scene, 0); + await doEndTurn(0); } return true; }) @@ -178,22 +181,22 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Throw bait option - const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon; - await throwBait(scene, pokemon); + const pokemon = globalScene.currentBattle.mysteryEncounter!.misc.pokemon; + await throwBait(pokemon); // 100% chance to increase catch stage +2 - tryChangeCatchStage(scene, 2); + tryChangeCatchStage(2); // 80% chance to increase flee stage +1 - const fleeChangeResult = tryChangeFleeStage(scene, 1, 8); + const fleeChangeResult = tryChangeFleeStage(1, 8); if (!fleeChangeResult) { - await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.busy_eating`) ?? "", null, 1000, false ); + await showEncounterText(getEncounterText(`${namespace}:safari.busy_eating`) ?? "", null, 1000, false ); } else { - await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.eating`) ?? "", null, 1000, false); + await showEncounterText(getEncounterText(`${namespace}:safari.eating`) ?? "", null, 1000, false); } - await doEndTurn(scene, 1); + await doEndTurn(1); return true; }) .build(), @@ -208,21 +211,21 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Throw mud option - const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon; - await throwMud(scene, pokemon); + const pokemon = globalScene.currentBattle.mysteryEncounter!.misc.pokemon; + await throwMud(pokemon); // 100% chance to decrease flee stage -2 - tryChangeFleeStage(scene, -2); + tryChangeFleeStage(-2); // 80% chance to decrease catch stage -1 - const catchChangeResult = tryChangeCatchStage(scene, -1, 8); + const catchChangeResult = tryChangeCatchStage(-1, 8); if (!catchChangeResult) { - await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false ); + await showEncounterText(getEncounterText(`${namespace}:safari.beside_itself_angry`) ?? "", null, 1000, false ); } else { - await showEncounterText(scene, getEncounterText(scene, `${namespace}:safari.angry`) ?? "", null, 1000, false ); + await showEncounterText(getEncounterText(`${namespace}:safari.angry`) ?? "", null, 1000, false ); } - await doEndTurn(scene, 2); + await doEndTurn(2); return true; }) .build(), @@ -232,40 +235,40 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ buttonLabel: `${namespace}:safari.4.label`, buttonTooltip: `${namespace}:safari.4.tooltip`, }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Flee option - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemon = encounter.misc.pokemon; - await doPlayerFlee(scene, pokemon); + await doPlayerFlee(pokemon); // Check how many safari pokemon left if (encounter.misc.safariPokemonRemaining > 0) { - await summonSafariPokemon(scene); - initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true }); + await summonSafariPokemon(); + initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true }); } else { // End safari mode encounter.continuousEncounter = false; - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); } return true; }) .build() ]; -async function summonSafariPokemon(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +async function summonSafariPokemon() { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Message pokemon remaining encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining); - scene.queueMessage(getEncounterText(scene, `${namespace}:safari.remaining_count`) ?? "", null, true); + globalScene.queueMessage(getEncounterText(`${namespace}:safari.remaining_count`) ?? "", null, true); // Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken // Safari pokemon roll twice on shiny and HA chances, but are otherwise normal let enemySpecies; let pokemon; - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { enemySpecies = getSafariSpeciesSpawn(); - const level = scene.currentBattle.getLevelForWave(); - enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode)); - pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false); + const level = globalScene.currentBattle.getLevelForWave(); + enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, globalScene.gameMode)); + pokemon = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false); // Roll shiny twice if (!pokemon.shiny) { @@ -277,7 +280,7 @@ async function summonSafariPokemon(scene: BattleScene) { const hiddenIndex = pokemon.species.ability2 ? 2 : 1; if (pokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new IntegerHolder(256); - scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -289,10 +292,10 @@ async function summonSafariPokemon(scene: BattleScene) { pokemon.calculateStats(); - scene.currentBattle.enemyParty.unshift(pokemon); - }, scene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining); + globalScene.currentBattle.enemyParty.unshift(pokemon); + }, globalScene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining); - scene.gameData.setPokemonSeen(pokemon, true); + globalScene.gameData.setPokemonSeen(pokemon, true); await pokemon.loadAssets(); // Reset safari catch and flee rates @@ -301,7 +304,7 @@ async function summonSafariPokemon(scene: BattleScene) { encounter.misc.pokemon = pokemon; encounter.misc.safariPokemonRemaining -= 1; - scene.unshiftPhase(new SummonPhase(scene, 0, false)); + globalScene.unshiftPhase(new SummonPhase(0, false)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); @@ -310,49 +313,49 @@ async function summonSafariPokemon(scene: BattleScene) { // shows up and the IV scanner breaks. For now, we place the IV scanner code // separately so that at least the IV scanner works. - const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); + const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); + globalScene.pushPhase(new ScanIvsPhase(pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); } } -function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise { +function throwPokeball(pokemon: EnemyPokemon): Promise { const baseCatchRate = pokemon.species.catchRate; // Catch stage ranges from -6 to +6 (like stat boost stages) - const safariCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage; + const safariCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage; // Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6) const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6)); // Catch rate same as safari ball const pokeballMultiplier = 1.5; const catchRate = Math.round(baseCatchRate * pokeballMultiplier * safariModifier); const ballTwitchRate = Math.round(1048560 / Math.sqrt(Math.sqrt(16711680 / catchRate))); - return trainerThrowPokeball(scene, pokemon, PokeballType.POKEBALL, ballTwitchRate); + return trainerThrowPokeball(pokemon, PokeballType.POKEBALL, ballTwitchRate); } -async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { +async function throwBait(pokemon: EnemyPokemon): Promise { const originalY: number = pokemon.y; const fpOffset = pokemon.getFieldPositionOffset(); - const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "safari_zone_bait", "0001.png"); + const bait: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(16 + 75, 80 + 25, "safari_zone_bait", "0001.png"); bait.setOrigin(0.5, 0.625); - scene.field.add(bait); + globalScene.field.add(bait); return new Promise(resolve => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => { - scene.playSound("se/pb_throw"); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => { + globalScene.playSound("se/pb_throw"); // Trainer throw frames - scene.trainer.setFrame("2"); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => { - scene.trainer.setFrame("3"); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + globalScene.trainer.setFrame("2"); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => { + globalScene.trainer.setFrame("3"); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); }); }); // Pokeball move and catch logic - scene.tweens.add({ + globalScene.tweens.add({ targets: bait, x: { value: 210 + fpOffset[0], ease: "Linear" }, y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" }, @@ -360,8 +363,8 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { let index = 1; - scene.time.delayedCall(768, () => { - scene.tweens.add({ + globalScene.time.delayedCall(768, () => { + globalScene.tweens.add({ targets: pokemon, duration: 150, ease: "Cubic.easeOut", @@ -369,12 +372,12 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { - scene.playSound("battle_anims/PRSFX- Bug Bite"); + globalScene.playSound("battle_anims/PRSFX- Bug Bite"); bait.setFrame("0002.png"); }, onLoop: () => { if (index % 2 === 0) { - scene.playSound("battle_anims/PRSFX- Bug Bite"); + globalScene.playSound("battle_anims/PRSFX- Bug Bite"); } if (index === 4) { bait.setFrame("0003.png"); @@ -382,7 +385,7 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { - scene.time.delayedCall(256, () => { + globalScene.time.delayedCall(256, () => { bait.destroy(); resolve(true); }); @@ -395,55 +398,55 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { +async function throwMud(pokemon: EnemyPokemon): Promise { const originalY: number = pokemon.y; const fpOffset = pokemon.getFieldPositionOffset(); - const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "safari_zone_mud", "0001.png"); + const mud: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(16 + 75, 80 + 35, "safari_zone_mud", "0001.png"); mud.setOrigin(0.5, 0.625); - scene.field.add(mud); + globalScene.field.add(mud); return new Promise(resolve => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => { - scene.playSound("se/pb_throw"); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[0], () => { + globalScene.playSound("se/pb_throw"); // Trainer throw frames - scene.trainer.setFrame("2"); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => { - scene.trainer.setFrame("3"); - scene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + globalScene.trainer.setFrame("2"); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[1], () => { + globalScene.trainer.setFrame("3"); + globalScene.time.delayedCall(TRAINER_THROW_ANIMATION_TIMES[2], () => { + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); }); }); // Mud throw and splat - scene.tweens.add({ + globalScene.tweens.add({ targets: mud, x: { value: 230 + fpOffset[0], ease: "Linear" }, y: { value: 55 + fpOffset[1], ease: "Cubic.easeOut" }, duration: 500, onComplete: () => { // Mud frame 2 - scene.playSound("battle_anims/PRSFX- Sludge Bomb2"); + globalScene.playSound("battle_anims/PRSFX- Sludge Bomb2"); mud.setFrame("0002.png"); // Mud splat - scene.time.delayedCall(200, () => { + globalScene.time.delayedCall(200, () => { mud.setFrame("0003.png"); - scene.time.delayedCall(400, () => { + globalScene.time.delayedCall(400, () => { mud.setFrame("0004.png"); }); }); // Fade mud then angry animation - scene.tweens.add({ + globalScene.tweens.add({ targets: mud, alpha: 0, ease: "Cubic.easeIn", duration: 1000, onComplete: () => { mud.destroy(); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 300, ease: "Cubic.easeOut", @@ -451,10 +454,10 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise { - scene.playSound("battle_anims/PRSFX- Taunt2"); + globalScene.playSound("battle_anims/PRSFX- Taunt2"); }, onLoop: () => { - scene.playSound("battle_anims/PRSFX- Taunt2"); + globalScene.playSound("battle_anims/PRSFX- Taunt2"); }, onComplete: () => { resolve(true); @@ -478,53 +481,53 @@ function isPokemonFlee(pokemon: EnemyPokemon, fleeStage: number): boolean { return roll < fleeRate; } -function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number): boolean { +function tryChangeFleeStage(change: number, chance?: number): boolean { if (chance && randSeedInt(10) >= chance) { return false; } - const currentFleeStage = scene.currentBattle.mysteryEncounter!.misc.fleeStage ?? 0; - scene.currentBattle.mysteryEncounter!.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6); + const currentFleeStage = globalScene.currentBattle.mysteryEncounter!.misc.fleeStage ?? 0; + globalScene.currentBattle.mysteryEncounter!.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6); return true; } -function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number): boolean { +function tryChangeCatchStage(change: number, chance?: number): boolean { if (chance && randSeedInt(10) >= chance) { return false; } - const currentCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage ?? 0; - scene.currentBattle.mysteryEncounter!.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6); + const currentCatchStage = globalScene.currentBattle.mysteryEncounter!.misc.catchStage ?? 0; + globalScene.currentBattle.mysteryEncounter!.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6); return true; } -async function doEndTurn(scene: BattleScene, cursorIndex: number) { +async function doEndTurn(cursorIndex: number) { // First cleanup and destroy old Pokemon objects that were left in the enemyParty // They are left in enemyParty temporarily so that VictoryPhase properly handles EXP - const party = scene.getEnemyParty(); + const party = globalScene.getEnemyParty(); if (party.length > 1) { for (let i = 1; i < party.length; i++) { party[i].destroy(); } - scene.currentBattle.enemyParty = party.slice(0, 1); + globalScene.currentBattle.enemyParty = party.slice(0, 1); } - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemon = encounter.misc.pokemon; const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage); if (isFlee) { // Pokemon flees! - await doPokemonFlee(scene, pokemon); + await doPokemonFlee(pokemon); // Check how many safari pokemon left if (encounter.misc.safariPokemonRemaining > 0) { - await summonSafariPokemon(scene); - initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); + await summonSafariPokemon(); + initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); } else { // End safari mode encounter.continuousEncounter = false; - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); } } else { - scene.queueMessage(getEncounterText(scene, `${namespace}:safari.watching`) ?? "", 0, null, 1000); - initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); + globalScene.queueMessage(getEncounterText(`${namespace}:safari.watching`) ?? "", 0, null, 1000); + initSubsequentOptionSelect({ overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); } } diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 3f1ace47b0f..d5362df28e7 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,18 +1,20 @@ import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { Nature } from "#enums/nature"; +import type { Nature } from "#enums/nature"; import { getNatureName } from "#app/data/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import i18next from "i18next"; @@ -79,15 +81,15 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Update money - updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); + updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!, + generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, + generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); @@ -103,34 +105,34 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = if (!pokemon.isAllowedInChallenge()) { return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null; } - if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) { - return getEncounterText(scene, `${namespace}:invalid_selection`) ?? null; + if (!encounter.pokemonMeetsPrimaryRequirements(pokemon)) { + return getEncounterText(`${namespace}:invalid_selection`) ?? null; } return null; }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Choose Cheap Option - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; const modifiers = encounter.misc.modifiers; for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType); + await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); } - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) - .withPostOptionPhase(async (scene: BattleScene) => { + .withPostOptionPhase(async () => { // Damage and status applied after dealer leaves (to make thematic sense) - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon as PlayerPokemon; // Pokemon takes half max HP damage and nature is randomized (does not update dex) - applyDamageToPokemon(scene, chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2)); + applyDamageToPokemon(chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 2)); const currentNature = chosenPokemon.nature; let newNature = randSeedInt(25) as Nature; @@ -140,8 +142,8 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = chosenPokemon.setCustomNature(newNature); encounter.setDialogueToken("newNature", getNatureName(newNature)); - queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); - setEncounterExp(scene, [ chosenPokemon.id ], 100); + queueEncounterMessage(`${namespace}:cheap_side_effects`); + setEncounterExp([ chosenPokemon.id ], 100); await chosenPokemon.updateInfo(); }) .build() @@ -159,15 +161,15 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Update money - updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); + updatePlayerMoney(-(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!, - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER)!, + generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, + generateModifierType(modifierTypes.BASE_STAT_BOOSTER)!, ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); @@ -179,30 +181,30 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = // Only Pokemon that can gain benefits are unfainted const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Choose Expensive Option - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; const modifiers = encounter.misc.modifiers; for (const modType of modifiers) { - await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType); + await applyModifierTypeToPlayerPokemon(chosenPokemon, modType); } - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) - .withPostOptionPhase(async (scene: BattleScene) => { + .withPostOptionPhase(async () => { // Status applied after dealer leaves (to make thematic sense) - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const chosenPokemon = encounter.misc.chosenPokemon; - queueEncounterMessage(scene, `${namespace}:no_bad_effects`); - setEncounterExp(scene, [ chosenPokemon.id ], 100); + queueEncounterMessage(`${namespace}:no_bad_effects`); + setEncounterExp([ chosenPokemon.id ], 100); await chosenPokemon.updateInfo(); }) @@ -219,9 +221,9 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = } ] }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 8dd03e12caa..923d8f06c23 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -1,13 +1,16 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { StatusEffect } from "#enums/status-effect"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; +import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; +import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; @@ -51,8 +54,8 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; console.log(encounter); // Calculate boss mon @@ -65,11 +68,11 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, stackCount: 2 }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, stackCount: 2 }, ], @@ -83,7 +86,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = encounter.enemyPartyConfigs = [ config ]; // Load animations/sfx for Snorlax fight start moves - loadCustomMovesForEncounter(scene, [ Moves.SNORE ]); + loadCustomMovesForEncounter([ Moves.SNORE ]); encounter.setDialogueToken("snorlaxName", getPokemonSpecies(Species.SNORLAX).getName()); @@ -103,10 +106,10 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true }); + const encounter = globalScene.currentBattle.mysteryEncounter!; + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: true }); encounter.startOfBattleEffects.push( { sourceBattlerIndex: BattlerIndex.ENEMY, @@ -120,7 +123,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = move: new PokemonMove(Moves.SNORE), ignorePp: true }); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); } ) .withSimpleOption( @@ -133,12 +136,12 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Fall asleep waiting for Snorlax // Full heal party - scene.unshiftPhase(new PartyHealPhase(scene, true)); - queueEncounterMessage(scene, `${namespace}:option.2.rest_result`); - leaveEncounterWithoutBattle(scene); + globalScene.unshiftPhase(new PartyHealPhase(true)); + queueEncounterMessage(`${namespace}:option.2.rest_result`); + leaveEncounterWithoutBattle(); } ) .withOption( @@ -155,13 +158,13 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = } ] }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Steal the Snorlax's Leftovers - const instance = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false }); + const instance = globalScene.currentBattle.mysteryEncounter!; + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.LEFTOVERS ], fillRemaining: false }); // Snorlax exp to Pokemon that did the stealing - setEncounterExp(scene, instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp); - leaveEncounterWithoutBattle(scene); + setEncounterExp(instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp); + leaveEncounterWithoutBattle(); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 042e9278673..84768519bea 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -1,10 +1,13 @@ -import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierTypeOption, initBattleWithEnemyConfig, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoneyRequirement, WaveModulusRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import Pokemon, { EnemyPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import PokemonData from "#app/system/pokemon-data"; @@ -62,9 +65,9 @@ export const TeleportingHijinksEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; - const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER); + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; + const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER); encounter.setDialogueToken("price", price.toString()); encounter.misc = { price @@ -85,14 +88,14 @@ export const TeleportingHijinksEncounter: MysteryEncounter = } ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Update money - updatePlayerMoney(scene, -scene.currentBattle.mysteryEncounter!.misc.price, true, false); + updatePlayerMoney(-globalScene.currentBattle.mysteryEncounter!.misc.price, true, false); }) - .withOptionPhase(async (scene: BattleScene) => { - const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene); - setEncounterRewards(scene, { fillRemaining: true }); - await initBattleWithEnemyConfig(scene, config); + .withOptionPhase(async () => { + const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(); + setEncounterRewards({ fillRemaining: true }); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -110,11 +113,11 @@ export const TeleportingHijinksEncounter: MysteryEncounter = } ], }) - .withOptionPhase(async (scene: BattleScene) => { - const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene); - setEncounterRewards(scene, { fillRemaining: true }); - setEncounterExp(scene, scene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100); - await initBattleWithEnemyConfig(scene, config); + .withOptionPhase(async () => { + const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(); + setEncounterRewards({ fillRemaining: true }); + setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -128,14 +131,14 @@ export const TeleportingHijinksEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Inspect the Machine - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; // Init enemy - const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); - const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true); - const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); + const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); + const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); + const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); const config: EnemyPartyConfig = { pokemonConfigs: [{ @@ -146,36 +149,36 @@ export const TeleportingHijinksEncounter: MysteryEncounter = }], }; - const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!; - const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true }); - await transitionMysteryEncounterIntroVisuals(scene, true, true); - await initBattleWithEnemyConfig(scene, config); + const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!; + const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!; + setEncounterRewards({ guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true }); + await transitionMysteryEncounterIntroVisuals(true, true); + await initBattleWithEnemyConfig(config); } ) .build(); -async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +async function doBiomeTransitionDialogueAndBattleInit() { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate new biome (cannot be current biome) - const filteredBiomes = BIOME_CANDIDATES.filter(b => scene.arena.biomeType !== b); + const filteredBiomes = BIOME_CANDIDATES.filter(b => globalScene.arena.biomeType !== b); const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)]; // Show dialogue and transition biome - await showEncounterText(scene, `${namespace}:transport`); - await Promise.all([ animateBiomeChange(scene, newBiome), transitionMysteryEncounterIntroVisuals(scene) ]); - scene.playBgm(); - await showEncounterText(scene, `${namespace}:attacked`); + await showEncounterText(`${namespace}:transport`); + await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]); + globalScene.playBgm(); + await showEncounterText(`${namespace}:attacked`); // Init enemy - const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); - const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true); - const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); + const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); + const bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); + const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); // Defense/Spd buffs below wave 50, +1 to all stats otherwise - const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [ Stat.DEF, Stat.SPDEF, Stat.SPD ] : [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; @@ -187,8 +190,8 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) { isBoss: true, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); + queueEncounterMessage(`${namespace}:boss_enraged`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); } }], }; @@ -196,46 +199,46 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) { return config; } -async function animateBiomeChange(scene: BattleScene, nextBiome: Biome) { +async function animateBiomeChange(nextBiome: Biome) { return new Promise(resolve => { - scene.tweens.add({ - targets: [ scene.arenaEnemy, scene.lastEnemyTrainer ], + globalScene.tweens.add({ + targets: [ globalScene.arenaEnemy, globalScene.lastEnemyTrainer ], x: "+=300", duration: 2000, onComplete: () => { - scene.newArena(nextBiome); + globalScene.newArena(nextBiome); const biomeKey = getBiomeKey(nextBiome); const bgTexture = `${biomeKey}_bg`; - scene.arenaBgTransition.setTexture(bgTexture); - scene.arenaBgTransition.setAlpha(0); - scene.arenaBgTransition.setVisible(true); - scene.arenaPlayerTransition.setBiome(nextBiome); - scene.arenaPlayerTransition.setAlpha(0); - scene.arenaPlayerTransition.setVisible(true); + globalScene.arenaBgTransition.setTexture(bgTexture); + globalScene.arenaBgTransition.setAlpha(0); + globalScene.arenaBgTransition.setVisible(true); + globalScene.arenaPlayerTransition.setBiome(nextBiome); + globalScene.arenaPlayerTransition.setAlpha(0); + globalScene.arenaPlayerTransition.setVisible(true); - scene.tweens.add({ - targets: [ scene.arenaPlayer, scene.arenaBgTransition, scene.arenaPlayerTransition ], + globalScene.tweens.add({ + targets: [ globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition ], duration: 1000, ease: "Sine.easeInOut", - alpha: (target: any) => target === scene.arenaPlayer ? 0 : 1, + alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1, onComplete: () => { - scene.arenaBg.setTexture(bgTexture); - scene.arenaPlayer.setBiome(nextBiome); - scene.arenaPlayer.setAlpha(1); - scene.arenaEnemy.setBiome(nextBiome); - scene.arenaEnemy.setAlpha(1); - scene.arenaNextEnemy.setBiome(nextBiome); - scene.arenaBgTransition.setVisible(false); - scene.arenaPlayerTransition.setVisible(false); - if (scene.lastEnemyTrainer) { - scene.lastEnemyTrainer.destroy(); + globalScene.arenaBg.setTexture(bgTexture); + globalScene.arenaPlayer.setBiome(nextBiome); + globalScene.arenaPlayer.setAlpha(1); + globalScene.arenaEnemy.setBiome(nextBiome); + globalScene.arenaEnemy.setAlpha(1); + globalScene.arenaNextEnemy.setBiome(nextBiome); + globalScene.arenaBgTransition.setVisible(false); + globalScene.arenaPlayerTransition.setVisible(false); + if (globalScene.lastEnemyTrainer) { + globalScene.lastEnemyTrainer.destroy(); } resolve(); - scene.tweens.add({ - targets: scene.arenaEnemy, + globalScene.tweens.add({ + targets: globalScene.arenaEnemy, x: "-=300", }); } diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 5ac9852f27a..702d8262cb6 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -1,9 +1,11 @@ -import { EnemyPartyConfig, generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { randSeedShuffle } from "#app/utils"; -import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import type MysteryEncounter from "../mystery-encounter"; +import { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Biome } from "#enums/biome"; @@ -14,17 +16,18 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Nature } from "#enums/nature"; import { Moves } from "#enums/moves"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { IEggOptions } from "#app/data/egg"; +import type { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { Type } from "#enums/type"; import { getPokeballTintColor } from "#app/data/pokeball"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; @@ -93,14 +96,14 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; - const waveIndex = scene.currentBattle.waveIndex; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; + const waveIndex = globalScene.currentBattle.waveIndex; // Calculates what trainers are available for battle in the encounter // If player is in space biome, uses special "Space" version of the trainer encounter.enemyPartyConfigs = [ - getPartyConfig(scene) + getPartyConfig() ]; const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE; @@ -125,7 +128,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = ]; // Determine the 3 pokemon the player can battle with - let partyCopy = scene.getPlayerParty().slice(0); + let partyCopy = globalScene.getPlayerParty().slice(0); partyCopy = partyCopy .filter(p => p.isAllowedInBattle()) .sort((a, b) => a.friendship - b.friendship); @@ -139,7 +142,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = // Dialogue and egg calcs for Pokemon 1 const [ pokemon1CommonEggs, pokemon1RareEggs ] = calculateEggRewardsForPokemon(pokemon1); - let pokemon1Tooltip = getEncounterText(scene, `${namespace}:option.1.tooltip_base`)!; + let pokemon1Tooltip = getEncounterText(`${namespace}:option.1.tooltip_base`)!; if (pokemon1RareEggs > 0) { const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") }); pokemon1Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); @@ -154,7 +157,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = // Dialogue and egg calcs for Pokemon 2 const [ pokemon2CommonEggs, pokemon2RareEggs ] = calculateEggRewardsForPokemon(pokemon2); - let pokemon2Tooltip = getEncounterText(scene, `${namespace}:option.2.tooltip_base`)!; + let pokemon2Tooltip = getEncounterText(`${namespace}:option.2.tooltip_base`)!; if (pokemon2RareEggs > 0) { const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); @@ -169,7 +172,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = // Dialogue and egg calcs for Pokemon 3 const [ pokemon3CommonEggs, pokemon3RareEggs ] = calculateEggRewardsForPokemon(pokemon3); - let pokemon3Tooltip = getEncounterText(scene, `${namespace}:option.3.tooltip_base`)!; + let pokemon3Tooltip = getEncounterText(`${namespace}:option.3.tooltip_base`)!; if (pokemon3RareEggs > 0) { const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") }); pokemon3Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); @@ -212,22 +215,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn battle with first pokemon const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc; encounter.misc.chosenPokemon = pokemon1; encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); - const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); - setEncounterRewards(scene, + const eggOptions = getEggOptions(pokemon1CommonEggs, pokemon1RareEggs); + setEncounterRewards( { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, eggOptions, - () => doPostEncounterCleanup(scene)); + () => doPostEncounterCleanup()); // Remove all Pokemon from the party except the chosen Pokemon - removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); + removePokemonFromPartyAndStoreHeldItems(encounter, pokemon1); // Configure outro dialogue for egg rewards encounter.dialogue.outro = [ @@ -248,7 +251,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -264,22 +267,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn battle with second pokemon const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc; encounter.misc.chosenPokemon = pokemon2; encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); - const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); - setEncounterRewards(scene, + const eggOptions = getEggOptions(pokemon2CommonEggs, pokemon2RareEggs); + setEncounterRewards( { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, eggOptions, - () => doPostEncounterCleanup(scene)); + () => doPostEncounterCleanup()); // Remove all Pokemon from the party except the chosen Pokemon - removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); + removePokemonFromPartyAndStoreHeldItems(encounter, pokemon2); // Configure outro dialogue for egg rewards encounter.dialogue.outro = [ @@ -300,7 +303,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -316,22 +319,22 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Spawn battle with third pokemon const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc; encounter.misc.chosenPokemon = pokemon3; encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); - const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); - setEncounterRewards(scene, + const eggOptions = getEggOptions(pokemon3CommonEggs, pokemon3RareEggs); + setEncounterRewards( { guaranteedModifierTypeFuncs: [ modifierTypes.SOOTHE_BELL ], fillRemaining: true }, eggOptions, - () => doPostEncounterCleanup(scene)); + () => doPostEncounterCleanup()); // Remove all Pokemon from the party except the chosen Pokemon - removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); + removePokemonFromPartyAndStoreHeldItems(encounter, pokemon3); // Configure outro dialogue for egg rewards encounter.dialogue.outro = [ @@ -352,7 +355,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -364,9 +367,9 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = ]) .build(); -function getPartyConfig(scene: BattleScene): EnemyPartyConfig { +function getPartyConfig(): EnemyPartyConfig { // Bug type superfan trainer config - const waveIndex = scene.currentBattle.waveIndex; + const waveIndex = globalScene.currentBattle.waveIndex; const breederConfig = trainerConfigs[TrainerType.EXPERT_POKEMON_BREEDER].clone(); breederConfig.name = i18next.t(trainerNameKey); @@ -386,14 +389,14 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig { ivs: [ 31, 31, 31, 31, 31, 31 ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.TERA_SHARD, [ Type.STEEL ]) as PokemonHeldItemModifierType, } ] } ] }; - if (scene.arena.biomeType === Biome.SPACE) { + if (globalScene.arena.biomeType === Biome.SPACE) { // All 3 members always Cleffa line, but different configs baseConfig.pokemonConfigs!.push({ nickname: i18next.t(`${namespace}:cleffa_2_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }), @@ -476,14 +479,13 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] return [ numCommons, numRares ]; } -function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) { +function getEggOptions(commonEggs: number, rareEggs: number) { const eggDescription = i18next.t(`${namespace}:title`) + ":\n" + i18next.t(trainerNameKey); const eggOptions: IEggOptions[] = []; if (commonEggs > 0) { for (let i = 0; i < commonEggs; i++) { eggOptions.push({ - scene, pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: eggDescription, @@ -494,7 +496,6 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) if (rareEggs > 0) { for (let i = 0; i < rareEggs; i++) { eggOptions.push({ - scene, pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: eggDescription, @@ -506,36 +507,36 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) return eggOptions; } -function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) { - const party = scene.getPlayerParty(); +function removePokemonFromPartyAndStoreHeldItems(encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) { + const party = globalScene.getPlayerParty(); const chosenIndex = party.indexOf(chosenPokemon); party[chosenIndex] = party[0]; party[0] = chosenPokemon; - encounter.misc.originalParty = scene.getPlayerParty().slice(1); + encounter.misc.originalParty = globalScene.getPlayerParty().slice(1); encounter.misc.originalPartyHeldItems = encounter.misc.originalParty .map(p => p.getHeldItems()); - scene["party"] = [ + globalScene["party"] = [ chosenPokemon ]; } -function restorePartyAndHeldItems(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +function restorePartyAndHeldItems() { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Restore original party - scene.getPlayerParty().push(...encounter.misc.originalParty); + globalScene.getPlayerParty().push(...encounter.misc.originalParty); // Restore held items const originalHeldItems = encounter.misc.originalPartyHeldItems; originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { pokemonHeldItemsList.forEach(heldItem => { - scene.addModifier(heldItem, true, false, false, true); + globalScene.addModifier(heldItem, true, false, false, true); }); }); - scene.updateModifiers(true); + globalScene.updateModifiers(true); } -function onGameOver(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +function onGameOver() { + const encounter = globalScene.currentBattle.mysteryEncounter!; encounter.dialogue.outro = [ { @@ -545,7 +546,7 @@ function onGameOver(scene: BattleScene) { ]; // Restore original party, player loses all friendship with chosen mon (it remains fainted) - restorePartyAndHeldItems(scene); + restorePartyAndHeldItems(); const chosenPokemon = encounter.misc.chosenPokemon; chosenPokemon.friendship = 0; @@ -556,33 +557,33 @@ function onGameOver(scene: BattleScene) { encounter.misc.encounterFailed = true; // Revert BGM - scene.playBgm(scene.arena.bgm); + globalScene.playBgm(globalScene.arena.bgm); // Clear any leftover battle phases - scene.clearPhaseQueue(); - scene.clearPhaseQueueSplice(); + globalScene.clearPhaseQueue(); + globalScene.clearPhaseQueueSplice(); // Return enemy Pokemon - const pokemon = scene.getEnemyPokemon(); + const pokemon = globalScene.getEnemyPokemon(); if (pokemon) { - scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokemon.hideInfo(); pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeIn", scale: 0.5, onComplete: () => { - scene.field.remove(pokemon, true); + globalScene.field.remove(pokemon, true); } }); } // Show the enemy trainer - scene.time.delayedCall(250, () => { - const sprites = scene.currentBattle.trainer?.getSprites(); - const tintSprites = scene.currentBattle.trainer?.getTintSprites(); + globalScene.time.delayedCall(250, () => { + const sprites = globalScene.currentBattle.trainer?.getSprites(); + const tintSprites = globalScene.currentBattle.trainer?.getTintSprites(); if (sprites && tintSprites) { for (let i = 0; i < sprites.length; i++) { sprites[i].setVisible(true); @@ -591,8 +592,8 @@ function onGameOver(scene: BattleScene) { tintSprites[i].clearTint(); } } - scene.tweens.add({ - targets: scene.currentBattle.trainer, + globalScene.tweens.add({ + targets: globalScene.currentBattle.trainer, x: "-=16", y: "+=16", alpha: 1, @@ -602,16 +603,16 @@ function onGameOver(scene: BattleScene) { }); - handleMysteryEncounterBattleFailed(scene, true); + handleMysteryEncounterBattleFailed(true); return false; } -function doPostEncounterCleanup(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +function doPostEncounterCleanup() { + const encounter = globalScene.currentBattle.mysteryEncounter!; if (!encounter.misc.encounterFailed) { // Give 20 friendship to the chosen pokemon encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); - restorePartyAndHeldItems(scene); + restorePartyAndHeldItems(); } } diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index feb6e68d1d1..ff4dd9750c9 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -1,15 +1,18 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { catchPokemon, getRandomSpeciesByStarterCost, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Species } from "#enums/species"; import { PokeballType } from "#enums/pokeball"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import { PlayerPokemon } from "#app/field/pokemon"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import PokemonData from "#app/system/pokemon-data"; @@ -58,8 +61,8 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; let species = getSalesmanSpeciesOffer(); let tries = 0; @@ -74,9 +77,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) { // If no HA mon found or you roll 1%, give shiny Magikarp with random variant species = getPokemonSpecies(Species.MAGIKARP); - pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true); + pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true); } else { - pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex); + pokemon = new PlayerPokemon(species, 5, 2, species.formIndex); } pokemon.generateAndPopulateMoveset(); @@ -101,7 +104,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}:description_shiny`; encounter.options[0].dialogue!.buttonTooltip = `${namespace}:option.1.tooltip_shiny`; } - const price = scene.getWaveMoneyAmount(priceMultiplier); + const price = globalScene.getWaveMoneyAmount(priceMultiplier); encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender()); encounter.setDialogueToken("price", price.toString()); encounter.misc = { @@ -127,24 +130,24 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = } ], }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const price = encounter.misc.price; const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon; // Update money - updatePlayerMoney(scene, -price, true, false); + updatePlayerMoney(-price, true, false); // Show dialogue - await showEncounterDialogue(scene, `${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`); - await transitionMysteryEncounterIntroVisuals(scene); + await showEncounterDialogue(`${namespace}:option.1.selected_dialogue`, `${namespace}:speaker`); + await transitionMysteryEncounterIntroVisuals(); // "Catch" purchased pokemon const data = new PokemonData(purchasedPokemon); data.player = false; - await catchPokemon(scene, data.toPokemon(scene) as EnemyPokemon, null, PokeballType.POKEBALL, true, true); + await catchPokemon(data.toPokemon() as EnemyPokemon, null, PokeballType.POKEBALL, true, true); - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -158,9 +161,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index c5cfd3f954e..6748d133d67 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -1,12 +1,16 @@ -import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#enums/nature"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Moves } from "#enums/moves"; @@ -67,8 +71,8 @@ export const TheStrongStuffEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon const config: EnemyPartyConfig = { @@ -85,26 +89,26 @@ export const TheStrongStuffEncounter: MysteryEncounter = moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, stackCount: 2 } ], tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.2.stat_boost`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2)); + queueEncounterMessage(`${namespace}:option.2.stat_boost`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], 2)); } } ], @@ -112,7 +116,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = encounter.enemyPartyConfigs = [ config ]; - loadCustomMovesForEncounter(scene, [ Moves.GASTRO_ACID, Moves.STEALTH_ROCK ]); + loadCustomMovesForEncounter([ Moves.GASTRO_ACID, Moves.STEALTH_ROCK ]); encounter.setDialogueToken("shuckleName", getPokemonSpecies(Species.SHUCKLE).getName()); @@ -132,16 +136,16 @@ export const TheStrongStuffEncounter: MysteryEncounter = } ] }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Do blackout and hide intro visuals during blackout - scene.time.delayedCall(750, () => { - transitionMysteryEncounterIntroVisuals(scene, true, true, 50); + globalScene.time.delayedCall(750, () => { + transitionMysteryEncounterIntroVisuals(true, true, 50); }); // -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP) // Sort party by bst - const sortedParty = scene.getPlayerParty().slice(0) + const sortedParty = globalScene.getPlayerParty().slice(0) .sort((pokemon1, pokemon2) => { const pokemon1Bst = pokemon1.calculateBaseStats().reduce((a, b) => a + b, 0); const pokemon2Bst = pokemon2.calculateBaseStats().reduce((a, b) => a + b, 0); @@ -161,15 +165,15 @@ export const TheStrongStuffEncounter: MysteryEncounter = encounter.setDialogueToken("reductionValue", HIGH_BST_REDUCTION_VALUE.toString()); encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString()); - await showEncounterText(scene, `${namespace}:option.1.selected_2`, null, undefined, true); + await showEncounterText(`${namespace}:option.1.selected_2`, null, undefined, true); encounter.dialogue.outro = [ { text: `${namespace}:outro`, } ]; - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene, true); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(true); return true; } ) @@ -183,10 +187,10 @@ export const TheStrongStuffEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true }); + const encounter = globalScene.currentBattle.mysteryEncounter!; + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.SOUL_DEW ], fillRemaining: true }); encounter.startOfBattleEffects.push( { sourceBattlerIndex: BattlerIndex.ENEMY, @@ -202,8 +206,8 @@ export const TheStrongStuffEncounter: MysteryEncounter = }); encounter.dialogue.outro = []; - await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await transitionMysteryEncounterIntroVisuals(true, true, 500); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); } ) .build(); diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index f4446241873..d0a44504763 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -1,8 +1,11 @@ -import { EnemyPartyConfig, generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; @@ -83,15 +86,15 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Loaded back to front for pop() operations - encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene)); - encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene)); - encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene)); - encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene)); - encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene)); + encounter.enemyPartyConfigs.push(getVitoTrainerConfig()); + encounter.enemyPartyConfigs.push(getVickyTrainerConfig()); + encounter.enemyPartyConfigs.push(getViviTrainerConfig()); + encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig()); + encounter.enemyPartyConfigs.push(getVictorTrainerConfig()); return true; }) @@ -110,13 +113,13 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Spawn 5 trainer battles back to back with Macho Brace in rewards - scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => { - await endTrainerBattleAndShowDialogue(scene); + globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = async () => { + await endTrainerBattleAndShowDialogue(); }; - await transitionMysteryEncounterIntroVisuals(scene, true, false); - await spawnNextTrainerOrEndEncounter(scene); + await transitionMysteryEncounterIntroVisuals(true, false); + await spawnNextTrainerOrEndEncounter(); } ) .withSimpleOption( @@ -130,47 +133,47 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Refuse the challenge, they full heal the party and give the player a Rarer Candy - scene.unshiftPhase(new PartyHealPhase(scene, true)); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false }); - leaveEncounterWithoutBattle(scene); + globalScene.unshiftPhase(new PartyHealPhase(true)); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.RARER_CANDY ], fillRemaining: false }); + leaveEncounterWithoutBattle(); } ) .build(); -async function spawnNextTrainerOrEndEncounter(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter!; +async function spawnNextTrainerOrEndEncounter() { + const encounter = globalScene.currentBattle.mysteryEncounter!; const nextConfig = encounter.enemyPartyConfigs.pop(); if (!nextConfig) { - await transitionMysteryEncounterIntroVisuals(scene, false, false); - await showEncounterDialogue(scene, `${namespace}:victory`, `${namespace}:speaker`); + await transitionMysteryEncounterIntroVisuals(false, false); + await showEncounterDialogue(`${namespace}:victory`, `${namespace}:speaker`); // Give 10x Voucher const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier(); - await scene.addModifier(newModifier); - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); + await globalScene.addModifier(newModifier); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name })); - await showEncounterDialogue(scene, `${namespace}:victory_2`, `${namespace}:speaker`); - scene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in - const machoBrace = generateModifierTypeOption(scene, modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; + await showEncounterDialogue(`${namespace}:victory_2`, `${namespace}:speaker`); + globalScene.ui.clearText(); // Clears "Winstrate" title from screen as rewards get animated in + const machoBrace = generateModifierTypeOption(modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE)!; machoBrace.type.tier = ModifierTier.MASTER; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false }); + setEncounterRewards({ guaranteedModifierTypeOptions: [ machoBrace ], fillRemaining: false }); encounter.doContinueEncounter = undefined; - leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.NO_BATTLE); + leaveEncounterWithoutBattle(false, MysteryEncounterMode.NO_BATTLE); } else { - await initBattleWithEnemyConfig(scene, nextConfig); + await initBattleWithEnemyConfig(nextConfig); } } -function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { +function endTrainerBattleAndShowDialogue(): Promise { return new Promise(async resolve => { - if (scene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) { + if (globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) { // Battle is over - const trainer = scene.currentBattle.trainer; + const trainer = globalScene.currentBattle.trainer; if (trainer) { - scene.tweens.add({ + globalScene.tweens.add({ targets: trainer, x: "+=16", y: "-=16", @@ -178,38 +181,38 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { ease: "Sine.easeInOut", duration: 750, onComplete: () => { - scene.field.remove(trainer, true); + globalScene.field.remove(trainer, true); } }); } - await spawnNextTrainerOrEndEncounter(scene); + await spawnNextTrainerOrEndEncounter(); resolve(); // Wait for all dialogue/post battle stuff to complete before resolving } else { - scene.arena.resetArenaEffects(); - const playerField = scene.getPlayerField(); + globalScene.arena.resetArenaEffects(); + const playerField = globalScene.getPlayerField(); playerField.forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); - playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p))); + playerField.forEach((_, p) => globalScene.unshiftPhase(new ReturnPhase(p))); - for (const pokemon of scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { // Only trigger form change when Eiscue is in Noice form // Hardcoded Eiscue for now in case it is fused with another pokemon if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) { - scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); } pokemon.resetBattleData(); applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); } - scene.unshiftPhase(new ShowTrainerPhase(scene)); + globalScene.unshiftPhase(new ShowTrainerPhase()); // Hide the trainer and init next battle - const trainer = scene.currentBattle.trainer; + const trainer = globalScene.currentBattle.trainer; // Unassign previous trainer from battle so it isn't destroyed before animation completes - scene.currentBattle.trainer = null; - await spawnNextTrainerOrEndEncounter(scene); + globalScene.currentBattle.trainer = null; + await spawnNextTrainerOrEndEncounter(); if (trainer) { - scene.tweens.add({ + globalScene.tweens.add({ targets: trainer, x: "+=16", y: "-=16", @@ -217,7 +220,7 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { ease: "Sine.easeInOut", duration: 750, onComplete: () => { - scene.field.remove(trainer, true); + globalScene.field.remove(trainer, true); resolve(); } }); @@ -226,7 +229,7 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { }); } -function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig { +function getVictorTrainerConfig(): EnemyPartyConfig { return { trainerType: TrainerType.VICTOR, pokemonConfigs: [ @@ -238,11 +241,11 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false }, @@ -256,11 +259,11 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false } @@ -270,7 +273,7 @@ function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig { }; } -function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig { +function getVictoriaTrainerConfig(): EnemyPartyConfig { return { trainerType: TrainerType.VICTORIA, pokemonConfigs: [ @@ -282,11 +285,11 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false } @@ -300,12 +303,12 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.PSYCHIC ]) as PokemonHeldItemModifierType, stackCount: 1, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.FAIRY ]) as PokemonHeldItemModifierType, stackCount: 1, isTransferable: false } @@ -315,7 +318,7 @@ function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig { }; } -function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { +function getViviTrainerConfig(): EnemyPartyConfig { return { trainerType: TrainerType.VIVI, pokemonConfigs: [ @@ -327,12 +330,12 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, stackCount: 4, isTransferable: false } @@ -346,12 +349,12 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.HP ]) as PokemonHeldItemModifierType, stackCount: 4, isTransferable: false }, { - modifier: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, isTransferable: false } ] @@ -364,7 +367,7 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, stackCount: 3, isTransferable: false }, @@ -374,7 +377,7 @@ function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { }; } -function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig { +function getVickyTrainerConfig(): EnemyPartyConfig { return { trainerType: TrainerType.VICKY, pokemonConfigs: [ @@ -386,7 +389,7 @@ function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, isTransferable: false } ] @@ -395,7 +398,7 @@ function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig { }; } -function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { +function getVitoTrainerConfig(): EnemyPartyConfig { return { trainerType: TrainerType.VITO, pokemonConfigs: [ @@ -407,7 +410,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [ Stat.SPD ]) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false } @@ -421,47 +424,47 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.APICOT ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.STARF ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.SALAC ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LUM ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LANSAT ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LIECHI ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.PETAYA ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.ENIGMA ]) as PokemonHeldItemModifierType, stackCount: 2, }, { - modifier: generateModifierType(scene, modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.BERRY, [ BerryType.LEPPA ]) as PokemonHeldItemModifierType, stackCount: 2, } ] @@ -474,7 +477,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false } @@ -488,7 +491,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false }, @@ -502,7 +505,7 @@ function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { moveSet: [ Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE ], modifierConfigs: [ { - modifier: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, stackCount: 2, isTransferable: false }, diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 725c4ba79eb..9cb388e343c 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -1,22 +1,26 @@ -import { Ability, allAbilities } from "#app/data/ability"; -import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { Ability } from "#app/data/ability"; +import { allAbilities } from "#app/data/ability"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getNatureName } from "#app/data/nature"; import { speciesStarterCosts } from "#app/data/balance/starters"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#app/system/game-data"; import PokemonData from "#app/system/pokemon-data"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { isNullOrUndefined, randSeedShuffle } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import i18next from "i18next"; import { getStatKey } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -71,8 +75,8 @@ export const TrainingSessionEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withPreOptionPhase(async (): Promise => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { encounter.misc = { playerPokemon: pokemon, @@ -81,24 +85,24 @@ export const TrainingSessionEncounter: MysteryEncounter = // Only Pokemon that are not KOed/legal can be trained const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon; // Spawn light training session with chosen pokemon // Every 50 waves, add +1 boss segment, capping at 5 const segments = Math.min( - 2 + Math.floor(scene.currentBattle.waveIndex / 50), + 2 + Math.floor(globalScene.currentBattle.waveIndex / 50), 5 ); const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(scene, playerPokemon, segments, modifiers); - scene.removePokemonFromPlayerParty(playerPokemon, false); + const config = getEnemyConfig(playerPokemon, segments, modifiers); + globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { encounter.setDialogueToken("stat1", "-"); @@ -148,23 +152,23 @@ export const TrainingSessionEncounter: MysteryEncounter = if (improvedCount > 0) { playerPokemon.calculateStats(); - scene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs); - scene.gameData.setPokemonCaught(playerPokemon, false); + globalScene.gameData.updateSpeciesDexIvs(playerPokemon.species.getRootSpeciesId(true), playerPokemon.ivs); + globalScene.gameData.setPokemonCaught(playerPokemon, false); } // Add pokemon and mods back - scene.getPlayerParty().push(playerPokemon); + globalScene.getPlayerParty().push(playerPokemon); for (const mod of modifiers.value) { mod.pokemonId = playerPokemon.id; - scene.addModifier(mod, true, false, false, true); + globalScene.addModifier(mod, true, false, false, true); } - scene.updateModifiers(true); - queueEncounterMessage(scene, `${namespace}:option.1.finished`); + globalScene.updateModifiers(true); + queueEncounterMessage(`${namespace}:option.1.finished`); }; - setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); + setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -182,15 +186,15 @@ export const TrainingSessionEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { + .withPreOptionPhase(async (): Promise => { // Open menu for selecting pokemon and Nature - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const natures = new Array(25).fill(null).map((val, i) => i as Nature); const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for nature selection return natures.map((nature: Nature) => { const option: OptionSelectItem = { - label: getNatureName(nature, true, true, true, scene.uiTheme), + label: getNatureName(nature, true, true, true, globalScene.uiTheme), handler: () => { // Pokemon and second option selected encounter.setDialogueToken("nature", getNatureName(nature)); @@ -207,40 +211,40 @@ export const TrainingSessionEncounter: MysteryEncounter = // Only Pokemon that are not KOed/legal can be trained const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon; // Spawn medium training session with chosen pokemon // Every 40 waves, add +1 boss segment, capping at 6 - const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 40), 6); + const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 40), 6); const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(scene, playerPokemon, segments, modifiers); - scene.removePokemonFromPlayerParty(playerPokemon, false); + const config = getEnemyConfig(playerPokemon, segments, modifiers); + globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { - queueEncounterMessage(scene, `${namespace}:option.2.finished`); + queueEncounterMessage(`${namespace}:option.2.finished`); // Add the pokemon back to party with Nature change playerPokemon.setCustomNature(encounter.misc.chosenNature); - scene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature); + globalScene.gameData.unlockSpeciesNature(playerPokemon.species, encounter.misc.chosenNature); // Add pokemon and modifiers back - scene.getPlayerParty().push(playerPokemon); + globalScene.getPlayerParty().push(playerPokemon); for (const mod of modifiers.value) { mod.pokemonId = playerPokemon.id; - scene.addModifier(mod, true, false, false, true); + globalScene.addModifier(mod, true, false, false, true); } - scene.updateModifiers(true); + globalScene.updateModifiers(true); }; - setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); + setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -258,9 +262,9 @@ export const TrainingSessionEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene): Promise => { + .withPreOptionPhase(async (): Promise => { // Open menu for selecting pokemon and ability to learn - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const onPokemonSelected = (pokemon: PlayerPokemon) => { // Return the options for ability selection const speciesForm = !!pokemon.getFusionSpeciesForm() @@ -286,7 +290,7 @@ export const TrainingSessionEncounter: MysteryEncounter = return true; }, onHover: () => { - showEncounterText(scene, ability.description, 0, 0, false); + showEncounterText(ability.description, 0, 0, false); }, }; optionSelectItems.push(option); @@ -298,28 +302,28 @@ export const TrainingSessionEncounter: MysteryEncounter = // Only Pokemon that are not KOed/legal can be trained const selectableFilter = (pokemon: Pokemon) => { - return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}:invalid_selection`); + return isPokemonValidForEncounterOptionSelection(pokemon, `${namespace}:invalid_selection`); }; - return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter); + return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter); }) - .withOptionPhase(async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOptionPhase(async () => { + const encounter = globalScene.currentBattle.mysteryEncounter!; const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon; // Spawn hard training session with chosen pokemon // Every 30 waves, add +1 boss segment, capping at 6 // Also starts with +1 to all stats - const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 30), 6); + const segments = Math.min(2 + Math.floor(globalScene.currentBattle.waveIndex / 30), 6); const modifiers = new ModifiersHolder(); - const config = getEnemyConfig(scene, playerPokemon, segments, modifiers); + const config = getEnemyConfig(playerPokemon, segments, modifiers); config.pokemonConfigs![0].tags = [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, ]; - scene.removePokemonFromPlayerParty(playerPokemon, false); + globalScene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { - queueEncounterMessage(scene, `${namespace}:option.3.finished`); + queueEncounterMessage(`${namespace}:option.3.finished`); // Add the pokemon back to party with ability change const abilityIndex = encounter.misc.abilityIndex; @@ -330,8 +334,8 @@ export const TrainingSessionEncounter: MysteryEncounter = const rootFusionSpecies = playerPokemon.fusionSpecies?.getRootSpeciesId(); if (!isNullOrUndefined(rootFusionSpecies) && speciesStarterCosts.hasOwnProperty(rootFusionSpecies) - && !!scene.gameData.dexData[rootFusionSpecies].caughtAttr) { - scene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2 + && !!globalScene.gameData.dexData[rootFusionSpecies].caughtAttr) { + globalScene.gameData.starterData[rootFusionSpecies].abilityAttr |= playerPokemon.fusionAbilityIndex !== 1 || playerPokemon.fusionSpecies?.ability2 ? 1 << playerPokemon.fusionAbilityIndex : AbilityAttr.ABILITY_HIDDEN; } @@ -340,20 +344,20 @@ export const TrainingSessionEncounter: MysteryEncounter = } playerPokemon.calculateStats(); - scene.gameData.setPokemonCaught(playerPokemon, false); + globalScene.gameData.setPokemonCaught(playerPokemon, false); // Add pokemon and mods back - scene.getPlayerParty().push(playerPokemon); + globalScene.getPlayerParty().push(playerPokemon); for (const mod of modifiers.value) { mod.pokemonId = playerPokemon.id; - scene.addModifier(mod, true, false, false, true); + globalScene.addModifier(mod, true, false, false, true); } - scene.updateModifiers(true); + globalScene.updateModifiers(true); }; - setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); + setEncounterRewards({ fillRemaining: true }, undefined, onBeforeRewardsPhase); - await initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(config); }) .build() ) @@ -367,15 +371,15 @@ export const TrainingSessionEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave encounter with no rewards or exp - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) .build(); -function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig { +function getEnemyConfig(playerPokemon: PlayerPokemon, segments: number, modifiers: ModifiersHolder): EnemyPartyConfig { playerPokemon.resetSummonData(); // Passes modifiers by reference diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index dfd89cdfb63..a17b05c4e33 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -1,8 +1,11 @@ -import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -58,8 +61,8 @@ export const TrashToTreasureEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon (shiny locked) const bossSpecies = getPokemonSpecies(Species.GARBODOR); @@ -79,10 +82,10 @@ export const TrashToTreasureEncounter: MysteryEncounter = encounter.enemyPartyConfigs = [ config ]; // Load animations/sfx for Garbodor fight start moves - loadCustomMovesForEncounter(scene, [ Moves.TOXIC, Moves.AMNESIA ]); + loadCustomMovesForEncounter([ Moves.TOXIC, Moves.AMNESIA ]); - scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav"); - scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav"); + globalScene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav"); + globalScene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav"); encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString()); @@ -100,24 +103,24 @@ export const TrashToTreasureEncounter: MysteryEncounter = }, ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Play Dig2 and then Venom Drench sfx - doGarbageDig(scene); + doGarbageDig(); }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Gain 2 Leftovers and 2 Shell Bell - await transitionMysteryEncounterIntroVisuals(scene); - await tryApplyDigRewardItems(scene); + await transitionMysteryEncounterIntroVisuals(); + await tryApplyDigRewardItems(); - const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]); + const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]); const modifier = blackSludge?.newModifier(); if (modifier) { - await scene.addModifier(modifier, false, false, false, true); - scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 }); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true); + await globalScene.addModifier(modifier, false, false, false, true); + globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 }); + await showEncounterText(i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true); } - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -133,15 +136,15 @@ export const TrashToTreasureEncounter: MysteryEncounter = }, ], }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Investigate garbage, battle Gmax Garbodor - scene.setFieldScale(0.75); - await showEncounterText(scene, `${namespace}:option.2.selected_2`); - await transitionMysteryEncounterIntroVisuals(scene); + globalScene.setFieldScale(0.75); + await showEncounterText(`${namespace}:option.2.selected_2`); + await transitionMysteryEncounterIntroVisuals(); - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); encounter.startOfBattleEffects.push( { sourceBattlerIndex: BattlerIndex.ENEMY, @@ -155,81 +158,81 @@ export const TrashToTreasureEncounter: MysteryEncounter = move: new PokemonMove(Moves.AMNESIA), ignorePp: true }); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); }) .build() ) .build(); -async function tryApplyDigRewardItems(scene: BattleScene) { - const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; - const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType; +async function tryApplyDigRewardItems() { + const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType; - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); // Iterate over the party until an item was successfully given // First leftovers for (const pokemon of party) { - const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers); + if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { + await applyModifierTypeToPlayerPokemon(pokemon, leftovers); break; } } // Second leftovers for (const pokemon of party) { - const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier; - if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, pokemon, leftovers); + if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) { + await applyModifierTypeToPlayerPokemon(pokemon, leftovers); break; } } - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: leftovers.name, count: 2 }), null, undefined, true); // First Shell bell for (const pokemon of party) { - const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier; - if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell); + if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) { + await applyModifierTypeToPlayerPokemon(pokemon, shellBell); break; } } // Second Shell bell for (const pokemon of party) { - const heldItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const heldItems = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier; - if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, pokemon, shellBell); + if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) { + await applyModifierTypeToPlayerPokemon(pokemon, shellBell); break; } } - scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true); + globalScene.playSound("item_fanfare"); + await showEncounterText(i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true); } -function doGarbageDig(scene: BattleScene) { - scene.playSound("battle_anims/PRSFX- Dig2"); - scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => { - scene.playSound("battle_anims/PRSFX- Dig2"); - scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 }); +function doGarbageDig() { + globalScene.playSound("battle_anims/PRSFX- Dig2"); + globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => { + globalScene.playSound("battle_anims/PRSFX- Dig2"); + globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 }); }); - scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => { - scene.playSound("battle_anims/PRSFX- Dig2"); + globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => { + globalScene.playSound("battle_anims/PRSFX- Dig2"); }); } diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index ebea34253d1..39297ab95b6 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -1,11 +1,14 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { getPartyLuckValue } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -13,7 +16,7 @@ import { TrainerSlot } from "#app/data/trainer-config"; import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; import { SelfStatusMove } from "#app/data/move"; import { PokeballType } from "#enums/pokeball"; @@ -23,7 +26,8 @@ import { BerryModifier } from "#app/modifier/modifier"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -46,21 +50,21 @@ export const UncommonBreedEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter!; + .withOnInit(() => { + const encounter = globalScene.currentBattle.mysteryEncounter!; // Calculate boss mon // Level equal to 2 below highest party member - const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2; + const level = getHighestLevelPlayerPokemon(false, true).level - 2; let species: PokemonSpecies; - if (scene.eventManager.isEventActive() && scene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(scene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, scene.gameMode); + if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { + const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); + const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); species = getPokemonSpecies( levelSpecies ); } else { - species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getPlayerParty()), true); + species = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); } - const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true); + const pokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, true); // Pokemon will always have one of its egg moves in its moveset const eggMoves = pokemon.getEggMoves(); @@ -81,7 +85,7 @@ export const UncommonBreedEncounter: MysteryEncounter = } // Defense/Spd buffs below wave 50, +1 to all stats otherwise - const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = globalScene.currentBattle.waveIndex < 50 ? [ Stat.DEF, Stat.SPDEF, Stat.SPD ] : [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; @@ -93,8 +97,8 @@ export const UncommonBreedEncounter: MysteryEncounter = isBoss: false, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { - queueEncounterMessage(pokemon.scene, `${namespace}:option.1.stat_boost`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); + queueEncounterMessage(`${namespace}:option.1.stat_boost`); + globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); } }], }; @@ -115,16 +119,16 @@ export const UncommonBreedEncounter: MysteryEncounter = ]; encounter.setDialogueToken("enemyPokemon", pokemon.getNameToRender()); - scene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav"); + globalScene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav"); return true; }) - .withOnVisualsStart((scene: BattleScene) => { + .withOnVisualsStart(() => { // Animate the pokemon - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemonSprite = encounter.introVisuals!.getSprites(); // Bounce at the end, then shiny sparkle if the Pokemon is shiny - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonSprite, duration: 300, ease: "Cubic.easeOut", @@ -134,7 +138,7 @@ export const UncommonBreedEncounter: MysteryEncounter = onComplete: () => encounter.introVisuals?.playShinySparkles() }); - scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); + globalScene.time.delayedCall(500, () => globalScene.playSound("battle_anims/PRSFX- Spotlight2")); return true; }) .setLocalizationKey(`${namespace}`) @@ -151,9 +155,9 @@ export const UncommonBreedEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Pick battle - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const eggMove = encounter.misc.eggMove; if (!isNullOrUndefined(eggMove)) { @@ -171,8 +175,8 @@ export const UncommonBreedEncounter: MysteryEncounter = }); } - setEncounterRewards(scene, { fillRemaining: true }); - await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); + setEncounterRewards({ fillRemaining: true }); + await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]); } ) .withOption( @@ -189,33 +193,33 @@ export const UncommonBreedEncounter: MysteryEncounter = } ] }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Give it some food // Remove 4 random berries from player's party // Get all player berry items, remove from party, and store reference - const berryItems: BerryModifier[] = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + const berryItems: BerryModifier[] = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; for (let i = 0; i < 4; i++) { const index = randSeedInt(berryItems.length); const randBerry = berryItems[index]; randBerry.stackCount--; if (randBerry.stackCount === 0) { - scene.removeModifier(randBerry); + globalScene.removeModifier(randBerry); berryItems.splice(index, 1); } } - await scene.updateModifiers(true, true); + await globalScene.updateModifiers(true, true); // Pokemon joins the team, with 2 egg moves - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemon = encounter.misc.pokemon; // Give 1 additional egg move givePokemonExtraEggMove(pokemon, encounter.misc.eggMove); - await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false); - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); + await catchPokemon(pokemon, null, PokeballType.POKEBALL, false); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(); }) .build() ) @@ -233,10 +237,10 @@ export const UncommonBreedEncounter: MysteryEncounter = } ] }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Attract the pokemon with a move // Pokemon joins the team, with 2 egg moves and IVs rolled an additional time - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; const pokemon = encounter.misc.pokemon; // Give 1 additional egg move @@ -248,12 +252,12 @@ export const UncommonBreedEncounter: MysteryEncounter = return newValue > iv ? newValue : iv; }); - await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false); + await catchPokemon(pokemon, null, PokeballType.POKEBALL, false); if (encounter.selectedOption?.primaryPokemon?.id) { - setEncounterExp(scene, encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false); + setEncounterExp(encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false); } - setEncounterRewards(scene, { fillRemaining: true }); - leaveEncounterWithoutBattle(scene); + setEncounterRewards({ fillRemaining: true }); + leaveEncounterWithoutBattle(); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 3d2e8493d44..392a963e639 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -1,20 +1,27 @@ -import { Type } from "#enums/type"; +import type { Type } from "#enums/type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; +import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils"; +import { generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; -import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { getLevelTotalExp } from "#app/data/exp"; @@ -25,7 +32,7 @@ import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config"; import { PartyMemberStrength } from "#enums/party-member-strength"; @@ -138,21 +145,21 @@ export const WeirdDreamEncounter: MysteryEncounter = .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withOnInit((scene: BattleScene) => { - scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); + .withOnInit(() => { + globalScene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); // Calculate all the newly transformed Pokemon and begin asset load - const teamTransformations = getTeamTransformations(scene); + const teamTransformations = getTeamTransformations(); const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); - scene.currentBattle.mysteryEncounter!.misc = { + globalScene.currentBattle.mysteryEncounter!.misc = { teamTransformations, loadAssets }; return true; }) - .withOnVisualsStart((scene: BattleScene) => { - scene.fadeAndSwitchBgm("mystery_encounter_weird_dream"); + .withOnVisualsStart(() => { + globalScene.fadeAndSwitchBgm("mystery_encounter_weird_dream"); return true; }) .withOption( @@ -168,25 +175,25 @@ export const WeirdDreamEncounter: MysteryEncounter = } ], }) - .withPreOptionPhase(async (scene: BattleScene) => { + .withPreOptionPhase(async () => { // Play the animation as the player goes through the dialogue - scene.time.delayedCall(1000, () => { - doShowDreamBackground(scene); + globalScene.time.delayedCall(1000, () => { + doShowDreamBackground(); }); - for (const transformation of scene.currentBattle.mysteryEncounter!.misc.teamTransformations) { - scene.removePokemonFromPlayerParty(transformation.previousPokemon, false); - scene.getPlayerParty().push(transformation.newPokemon); + for (const transformation of globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations) { + globalScene.removePokemonFromPlayerParty(transformation.previousPokemon, false); + globalScene.getPlayerParty().push(transformation.newPokemon); } }) - .withOptionPhase(async (scene: BattleScene) => { + .withOptionPhase(async () => { // Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue - const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}:option.1.cutscene`); + const cutsceneDialoguePromise = showEncounterText(`${namespace}:option.1.cutscene`); // Change the entire player's party // Wait for all new Pokemon assets to be loaded before showing transformation animations - await Promise.all(scene.currentBattle.mysteryEncounter!.misc.loadAssets); - const transformations = scene.currentBattle.mysteryEncounter!.misc.teamTransformations; + await Promise.all(globalScene.currentBattle.mysteryEncounter!.misc.loadAssets); + const transformations = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations; // If there are 1-3 transformations, do them centered back to back // Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions @@ -195,21 +202,21 @@ export const WeirdDreamEncounter: MysteryEncounter = const pokemon1 = transformation.previousPokemon; const pokemon2 = transformation.newPokemon; - await doPokemonTransformationSequence(scene, pokemon1, pokemon2, TransformationScreenPosition.CENTER); + await doPokemonTransformationSequence(pokemon1, pokemon2, TransformationScreenPosition.CENTER); } } else { - await doSideBySideTransformations(scene, transformations); + await doSideBySideTransformations(transformations); } // Make sure player has finished cutscene dialogue await cutsceneDialoguePromise; - doHideDreamBackground(scene); - await showEncounterText(scene, `${namespace}:option.1.dream_complete`); + doHideDreamBackground(); + await showEncounterText(`${namespace}:option.1.dream_complete`); - await doNewTeamPostProcess(scene, transformations); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false }); - leaveEncounterWithoutBattle(scene, true); + await doNewTeamPostProcess(transformations); + setEncounterRewards({ guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT ], fillRemaining: false }); + leaveEncounterWithoutBattle(true); }) .build() ) @@ -223,9 +230,9 @@ export const WeirdDreamEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Battle your "future" team for some item rewards - const transformations: PokemonTransformation[] = scene.currentBattle.mysteryEncounter!.misc.teamTransformations; + const transformations: PokemonTransformation[] = globalScene.currentBattle.mysteryEncounter!.misc.teamTransformations; // Uses the pokemon that player's party would have transformed into const enemyPokemonConfigs: EnemyPokemonConfig[] = []; @@ -233,7 +240,7 @@ export const WeirdDreamEncounter: MysteryEncounter = const newPokemon = transformation.newPokemon; const previousPokemon = transformation.previousPokemon; - await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true); + await postProcessTransformedPokemon(previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true); const dataSource = new PokemonData(newPokemon); dataSource.player = false; @@ -251,7 +258,7 @@ export const WeirdDreamEncounter: MysteryEncounter = if (shouldGetOldGateau(newPokemon)) { const stats = getOldGateauBoostedStats(newPokemon); newPokemonHeldItemConfigs.push({ - modifier: generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType, + modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [ OLD_GATEAU_STATS_UP, stats ]) as PokemonHeldItemModifierType, stackCount: 1, isTransferable: false }); @@ -268,7 +275,7 @@ export const WeirdDreamEncounter: MysteryEncounter = enemyPokemonConfigs.push(enemyConfig); } - const genderIndex = scene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone(); trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG)); const enemyPartyConfig: EnemyPartyConfig = { @@ -280,7 +287,7 @@ export const WeirdDreamEncounter: MysteryEncounter = const onBeforeRewards = () => { // Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) // One random pokemon will get its passive unlocked - const passiveDisabledPokemon = scene.getPlayerParty().filter(p => !p.passive); + const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive); if (passiveDisabledPokemon?.length > 0) { const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)]; enablePassiveMon.passive = true; @@ -288,10 +295,10 @@ export const WeirdDreamEncounter: MysteryEncounter = } }; - setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards); + setEncounterRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: false }, undefined, onBeforeRewards); - await showEncounterText(scene, `${namespace}:option.2.selected_2`, null, undefined, true); - await initBattleWithEnemyConfig(scene, enemyPartyConfig); + await showEncounterText(`${namespace}:option.2.selected_2`, null, undefined, true); + await initBattleWithEnemyConfig(enemyPartyConfig); } ) .withSimpleOption( @@ -304,9 +311,9 @@ export const WeirdDreamEncounter: MysteryEncounter = }, ], }, - async (scene: BattleScene) => { + async () => { // Leave, reduce party levels by 10% - for (const pokemon of scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1); pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); pokemon.levelExp = 0; @@ -316,7 +323,7 @@ export const WeirdDreamEncounter: MysteryEncounter = await pokemon.updateInfo(); } - leaveEncounterWithoutBattle(scene, true); + leaveEncounterWithoutBattle(true); return true; } ) @@ -329,8 +336,8 @@ interface PokemonTransformation { heldItems: PokemonHeldItemModifier[]; } -function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { - const party = scene.getPlayerParty(); +function getTeamTransformations(): PokemonTransformation[] { + const party = globalScene.getPlayerParty(); // Removes all pokemon from the party const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species); const pokemonTransformations: PokemonTransformation[] = party.map(p => { @@ -379,37 +386,37 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { for (const transformation of pokemonTransformations) { const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount()); - transformation.newPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); + transformation.newPokemon = globalScene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); } return pokemonTransformations; } -async function doNewTeamPostProcess(scene: BattleScene, transformations: PokemonTransformation[]) { +async function doNewTeamPostProcess(transformations: PokemonTransformation[]) { let atLeastOneNewStarter = false; for (const transformation of transformations) { const previousPokemon = transformation.previousPokemon; const newPokemon = transformation.newPokemon; const speciesRootForm = newPokemon.species.getRootSpeciesId(); - if (await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, speciesRootForm)) { + if (await postProcessTransformedPokemon(previousPokemon, newPokemon, speciesRootForm)) { atLeastOneNewStarter = true; } // Copy old items to new pokemon for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; - await scene.addModifier(item, false, false, false, true); + await globalScene.addModifier(item, false, false, false, true); } // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats if (shouldGetOldGateau(newPokemon)) { const stats = getOldGateauBoostedStats(newPokemon); const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() - .generateType(scene.getPlayerParty(), [ OLD_GATEAU_STATS_UP, stats ]) + .generateType(globalScene.getPlayerParty(), [ OLD_GATEAU_STATS_UP, stats ]) ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); const modifier = modType?.newModifier(newPokemon); if (modifier) { - await scene.addModifier(modifier, false, false, false, true); + await globalScene.addModifier(modifier, false, false, false, true); } } @@ -418,7 +425,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } // One random pokemon will get its passive unlocked - const passiveDisabledPokemon = scene.getPlayerParty().filter(p => !p.passive); + const passiveDisabledPokemon = globalScene.getPlayerParty().filter(p => !p.passive); if (passiveDisabledPokemon?.length > 0) { const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)]; enablePassiveMon.passive = true; @@ -427,27 +434,26 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon // If at least one new starter was unlocked, play 1 fanfare if (atLeastOneNewStarter) { - scene.playSound("level_up_fanfare"); + globalScene.playSound("level_up_fanfare"); } } /** * Applies special changes to the newly transformed pokemon, such as passing previous moves, gaining egg moves, etc. * Returns whether the transformed pokemon unlocks a new starter for the player. - * @param scene * @param previousPokemon * @param newPokemon * @param speciesRootForm * @param forBattle Default `false`. If false, will perform achievements and dex unlocks for the player. */ -async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { +async function postProcessTransformedPokemon(previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { let isNewStarter = false; // Roll HA a second time if (newPokemon.species.abilityHidden) { const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; if (newPokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new IntegerHolder(256); - scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); @@ -469,26 +475,26 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon // For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) { if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { - scene.validateAchv(achvs.HIDDEN_ABILITY); + globalScene.validateAchv(achvs.HIDDEN_ABILITY); } if (newPokemon.species.subLegendary) { - scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_SUB_LEGENDARY); } if (newPokemon.species.legendary) { - scene.validateAchv(achvs.CATCH_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_LEGENDARY); } if (newPokemon.species.mythical) { - scene.validateAchv(achvs.CATCH_MYTHICAL); + globalScene.validateAchv(achvs.CATCH_MYTHICAL); } - scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); - const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); + globalScene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); + const newStarterUnlocked = await globalScene.gameData.setPokemonCaught(newPokemon, true, false, false); if (newStarterUnlocked) { isNewStarter = true; - await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); + await showEncounterText(i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); } } @@ -504,8 +510,8 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon }); // For pokemon that the player owns (including ones just caught), gain a candy - if (!forBattle && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { - scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); + if (!forBattle && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) { + globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); } // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species @@ -515,7 +521,7 @@ async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon newPokemon.moveset = previousPokemon.moveset.slice(0); - const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm, forBattle); + const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(newPokemon, speciesRootForm, forBattle); // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); @@ -597,31 +603,31 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num return newSpecies; } -function doShowDreamBackground(scene: BattleScene) { - const transformationContainer = scene.add.container(0, -scene.game.canvas.height / 6); +function doShowDreamBackground() { + const transformationContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); transformationContainer.name = "Dream Background"; // In case it takes a bit for video to load - const transformationStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0); + const transformationStaticBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0); transformationStaticBg.setName("Black Background"); transformationStaticBg.setOrigin(0, 0); transformationContainer.add(transformationStaticBg); transformationStaticBg.setVisible(true); - const transformationVideoBg: Phaser.GameObjects.Video = scene.add.video(0, 0, "evo_bg").stop(); + const transformationVideoBg: Phaser.GameObjects.Video = globalScene.add.video(0, 0, "evo_bg").stop(); transformationVideoBg.setLoop(true); transformationVideoBg.setOrigin(0, 0); transformationVideoBg.setScale(0.4359673025); transformationContainer.add(transformationVideoBg); - scene.fieldUI.add(transformationContainer); - scene.fieldUI.bringToTop(transformationContainer); + globalScene.fieldUI.add(transformationContainer); + globalScene.fieldUI.bringToTop(transformationContainer); transformationVideoBg.play(); transformationContainer.setVisible(true); transformationContainer.alpha = 0; - scene.tweens.add({ + globalScene.tweens.add({ targets: transformationContainer, alpha: 1, duration: 3000, @@ -629,39 +635,39 @@ function doShowDreamBackground(scene: BattleScene) { }); } -function doHideDreamBackground(scene: BattleScene) { - const transformationContainer = scene.fieldUI.getByName("Dream Background"); +function doHideDreamBackground() { + const transformationContainer = globalScene.fieldUI.getByName("Dream Background"); - scene.tweens.add({ + globalScene.tweens.add({ targets: transformationContainer, alpha: 0, duration: 3000, ease: "Sine.easeInOut", onComplete: () => { - scene.fieldUI.remove(transformationContainer, true); + globalScene.fieldUI.remove(transformationContainer, true); } }); } -function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) { +function doSideBySideTransformations(transformations: PokemonTransformation[]) { return new Promise(resolve => { const allTransformationPromises: Promise[] = []; for (let i = 0; i < 3; i++) { const delay = i * 4000; - scene.time.delayedCall(delay, () => { + globalScene.time.delayedCall(delay, () => { const transformation = transformations[i]; const pokemon1 = transformation.previousPokemon; const pokemon2 = transformation.newPokemon; const screenPosition = i as TransformationScreenPosition; - const transformationPromise = doPokemonTransformationSequence(scene, pokemon1, pokemon2, screenPosition) + const transformationPromise = doPokemonTransformationSequence(pokemon1, pokemon2, screenPosition) .then(() => { if (transformations.length > i + 3) { const nextTransformationAtPosition = transformations[i + 3]; const nextPokemon1 = nextTransformationAtPosition.previousPokemon; const nextPokemon2 = nextTransformationAtPosition.newPokemon; - allTransformationPromises.push(doPokemonTransformationSequence(scene, nextPokemon1, nextPokemon2, screenPosition)); + allTransformationPromises.push(doPokemonTransformationSequence(nextPokemon1, nextPokemon2, screenPosition)); } }); allTransformationPromises.push(transformationPromise); @@ -682,11 +688,10 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo /** * Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`) - * @param scene * @param newPokemon * @param speciesRootForm */ -async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { +async function addEggMoveToNewPokemonMoveset(newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { let eggMoveIndex: null | number = null; const eggMoves = newPokemon.getEggMoves()?.slice(0); if (eggMoves) { @@ -712,8 +717,8 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla } // For pokemon that the player owns (including ones just caught), unlock the egg move - if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { - await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); + if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!globalScene.gameData.dexData[speciesRootForm].caughtAttr) { + await globalScene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); } } } diff --git a/src/data/mystery-encounters/mystery-encounter-dialogue.ts b/src/data/mystery-encounters/mystery-encounter-dialogue.ts index e0ba8512d34..39db3d58690 100644 --- a/src/data/mystery-encounters/mystery-encounter-dialogue.ts +++ b/src/data/mystery-encounters/mystery-encounter-dialogue.ts @@ -1,4 +1,4 @@ -import { TextStyle } from "#app/ui/text"; +import type { TextStyle } from "#app/ui/text"; export class TextDisplay { speaker?: string; diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index 4ff8fd95f85..d0078b3686e 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -1,15 +1,17 @@ -import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue"; -import { Moves } from "#app/enums/moves"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; -import BattleScene from "#app/battle-scene"; -import { Type } from "#enums/type"; +import type { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue"; +import type { Moves } from "#app/enums/moves"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type { Type } from "#enums/type"; import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; +import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement"; +import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -export type OptionPhaseCallback = (scene: BattleScene) => Promise; +export type OptionPhaseCallback = () => Promise; /** * Used by {@linkcode MysteryEncounterOptionBuilder} class to define required/optional properties on the {@linkcode MysteryEncounterOption} class when building. @@ -74,21 +76,19 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption { /** * Returns true if all {@linkcode EncounterRequirement}s for the option are met - * @param scene */ - meetsRequirements(scene: BattleScene): boolean { - return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) - && this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) - && this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene); + meetsRequirements(): boolean { + return !this.requirements.some(requirement => !requirement.meetsRequirement()) + && this.meetsSupportingRequirementAndSupportingPokemonSelected() + && this.meetsPrimaryRequirementAndPrimaryPokemonSelected(); } /** * Returns true if all PRIMARY {@linkcode EncounterRequirement}s for the option are met - * @param scene * @param pokemon */ - pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean { - return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getPlayerParty()).map(p => p.id).includes(pokemon.id)); + pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean { + return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id)); } /** @@ -96,16 +96,15 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption { * AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}. * If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined, * can cause scenarios where there are not enough Pokemon that are sufficient for all requirements. - * @param scene */ - meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean { + meetsPrimaryRequirementAndPrimaryPokemonSelected(): boolean { if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) { return true; } - let qualified: PlayerPokemon[] = scene.getPlayerParty(); + let qualified: PlayerPokemon[] = globalScene.getPlayerParty(); for (const req of this.primaryPokemonRequirements) { - if (req.meetsRequirement(scene)) { - const queryParty = req.queryParty(scene.getPlayerParty()); + if (req.meetsRequirement()) { + const queryParty = req.queryParty(globalScene.getPlayerParty()); qualified = qualified.filter(pkmn => queryParty.includes(pkmn)); } else { this.primaryPokemon = undefined; @@ -154,18 +153,17 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption { * AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable). * If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined, * can cause scenarios where there are not enough Pokemon that are sufficient for all requirements. - * @param scene */ - meetsSupportingRequirementAndSupportingPokemonSelected(scene: BattleScene): boolean { + meetsSupportingRequirementAndSupportingPokemonSelected(): boolean { if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) { this.secondaryPokemon = []; return true; } - let qualified: PlayerPokemon[] = scene.getPlayerParty(); + let qualified: PlayerPokemon[] = globalScene.getPlayerParty(); for (const req of this.secondaryPokemonRequirements) { - if (req.meetsRequirement(scene)) { - const queryParty = req.queryParty(scene.getPlayerParty()); + if (req.meetsRequirement()) { + const queryParty = req.queryParty(globalScene.getPlayerParty()); qualified = qualified.filter(pkmn => queryParty.includes(pkmn)); } else { this.secondaryPokemon = []; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 811b622de76..63fb0cc5380 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/ability"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; @@ -6,35 +6,33 @@ import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from import { StatusEffect } from "#enums/status-effect"; import { Type } from "#enums/type"; import { WeatherType } from "#enums/weather-type"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; -import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; +import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { isNullOrUndefined } from "#app/utils"; -import { Abilities } from "#enums/abilities"; +import type { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import { SpeciesFormKey } from "#enums/species-form-key"; import { TimeOfDay } from "#enums/time-of-day"; export interface EncounterRequirement { - meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met - getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; + meetsRequirement(): boolean; // Boolean to see if a requirement is met + getDialogueToken(pokemon?: PlayerPokemon): [string, string]; } export abstract class EncounterSceneRequirement implements EncounterRequirement { /** * Returns whether the EncounterSceneRequirement's... requirements, are met by the given scene - * @param partyPokemon */ - abstract meetsRequirement(scene: BattleScene): boolean; + abstract meetsRequirement(): boolean; /** * Returns a dialogue token key/value pair for a given Requirement. * Should be overridden by child Requirement classes. - * @param scene * @param pokemon */ - abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; + abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string]; } /** @@ -61,33 +59,31 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement { /** * Checks if all/any requirements are met (depends on {@linkcode isAnd}) - * @param scene The {@linkcode BattleScene} to check against * @returns true if all/any requirements are met (depends on {@linkcode isAnd}) */ - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { return this.isAnd - ? this.requirements.every(req => req.meetsRequirement(scene)) - : this.requirements.some(req => req.meetsRequirement(scene)); + ? this.requirements.every(req => req.meetsRequirement()) + : this.requirements.some(req => req.meetsRequirement()); } /** * Retrieves a dialogue token key/value pair for the given {@linkcode EncounterSceneRequirement | requirements}. - * @param scene The {@linkcode BattleScene} to check against * @param pokemon The {@linkcode PlayerPokemon} to check against * @returns A dialogue token key/value pair * @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported) */ - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { if (this.isAnd) { throw new Error("Not implemented (Sorry)"); } else { for (const req of this.requirements) { - if (req.meetsRequirement(scene)) { - return req.getDialogueToken(scene, pokemon); + if (req.meetsRequirement()) { + return req.getDialogueToken(pokemon); } } - return this.requirements[0].getDialogueToken(scene, pokemon); + return this.requirements[0].getDialogueToken(pokemon); } } } @@ -98,9 +94,8 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen /** * Returns whether the EncounterPokemonRequirement's... requirements, are met by the given scene - * @param partyPokemon */ - abstract meetsRequirement(scene: BattleScene): boolean; + abstract meetsRequirement(): boolean; /** * Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned. @@ -111,10 +106,9 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen /** * Returns a dialogue token key/value pair for a given Requirement. * Should be overridden by child Requirement classes. - * @param scene * @param pokemon */ - abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string]; + abstract getDialogueToken(pokemon?: PlayerPokemon): [string, string]; } /** @@ -143,13 +137,12 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement { /** * Checks if all/any requirements are met (depends on {@linkcode isAnd}) - * @param scene The {@linkcode BattleScene} to check against * @returns true if all/any requirements are met (depends on {@linkcode isAnd}) */ - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { return this.isAnd - ? this.requirements.every(req => req.meetsRequirement(scene)) - : this.requirements.some(req => req.meetsRequirement(scene)); + ? this.requirements.every(req => req.meetsRequirement()) + : this.requirements.some(req => req.meetsRequirement()); } /** @@ -168,22 +161,21 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement { /** * Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}. - * @param scene The {@linkcode BattleScene} to check against * @param pokemon The {@linkcode PlayerPokemon} to check against * @returns A dialogue token key/value pair * @throws An {@linkcode Error} if {@linkcode isAnd} is `true` (not supported) */ - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { if (this.isAnd) { throw new Error("Not implemented (Sorry)"); } else { for (const req of this.requirements) { - if (req.meetsRequirement(scene)) { - return req.getDialogueToken(scene, pokemon); + if (req.meetsRequirement()) { + return req.getDialogueToken(pokemon); } } - return this.requirements[0].getDialogueToken(scene, pokemon); + return this.requirements[0].getDialogueToken(pokemon); } } } @@ -200,12 +192,12 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement { this.previousEncounterRequirement = previousEncounterRequirement; } - override meetsRequirement(scene: BattleScene): boolean { - return scene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement); + override meetsRequirement(): boolean { + return globalScene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement); } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return [ "previousEncounter", scene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ]; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + return [ "previousEncounter", globalScene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? "" ]; } } @@ -222,9 +214,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement { this.waveRange = waveRange; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) { - const waveIndex = scene.currentBattle.waveIndex; + const waveIndex = globalScene.currentBattle.waveIndex; if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) { return false; } @@ -232,8 +224,8 @@ export class WaveRangeRequirement extends EncounterSceneRequirement { return true; } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return [ "waveIndex", scene.currentBattle.waveIndex.toString() ]; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + return [ "waveIndex", globalScene.currentBattle.waveIndex.toString() ]; } } @@ -257,12 +249,12 @@ export class WaveModulusRequirement extends EncounterSceneRequirement { this.modulusValue = modulusValue; } - override meetsRequirement(scene: BattleScene): boolean { - return this.waveModuli.includes(scene.currentBattle.waveIndex % this.modulusValue); + override meetsRequirement(): boolean { + return this.waveModuli.includes(globalScene.currentBattle.waveIndex % this.modulusValue); } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return [ "waveIndex", scene.currentBattle.waveIndex.toString() ]; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + return [ "waveIndex", globalScene.currentBattle.waveIndex.toString() ]; } } @@ -274,8 +266,8 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [ timeOfDay ]; } - override meetsRequirement(scene: BattleScene): boolean { - const timeOfDay = scene.arena?.getTimeOfDay(); + override meetsRequirement(): boolean { + const timeOfDay = globalScene.arena?.getTimeOfDay(); if (!isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) { return false; } @@ -283,8 +275,8 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement { return true; } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return [ "timeOfDay", TimeOfDay[scene.arena.getTimeOfDay()].toLocaleLowerCase() ]; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + return [ "timeOfDay", TimeOfDay[globalScene.arena.getTimeOfDay()].toLocaleLowerCase() ]; } } @@ -296,8 +288,8 @@ export class WeatherRequirement extends EncounterSceneRequirement { this.requiredWeather = Array.isArray(weather) ? weather : [ weather ]; } - override meetsRequirement(scene: BattleScene): boolean { - const currentWeather = scene.arena.weather?.weatherType; + override meetsRequirement(): boolean { + const currentWeather = globalScene.arena.weather?.weatherType; if (!isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) { return false; } @@ -305,8 +297,8 @@ export class WeatherRequirement extends EncounterSceneRequirement { return true; } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - const currentWeather = scene.arena.weather?.weatherType; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + const currentWeather = globalScene.arena.weather?.weatherType; let token = ""; if (!isNullOrUndefined(currentWeather)) { token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase(); @@ -331,9 +323,9 @@ export class PartySizeRequirement extends EncounterSceneRequirement { this.excludeDisallowedPokemon = excludeDisallowedPokemon; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) { - const partySize = this.excludeDisallowedPokemon ? scene.getPokemonAllowedInBattle().length : scene.getPlayerParty().length; + const partySize = this.excludeDisallowedPokemon ? globalScene.getPokemonAllowedInBattle().length : globalScene.getPlayerParty().length; if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) { return false; } @@ -342,8 +334,8 @@ export class PartySizeRequirement extends EncounterSceneRequirement { return true; } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return [ "partySize", scene.getPlayerParty().length.toString() ]; + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + return [ "partySize", globalScene.getPlayerParty().length.toString() ]; } } @@ -357,14 +349,14 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [ heldItem ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) { return false; } let modifierCount = 0; this.requiredHeldItemModifiers.forEach(modifier => { - const matchingMods = scene.findModifiers(m => m.constructor.name === modifier); + const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier); if (matchingMods?.length > 0) { matchingMods.forEach(matchingMod => { modifierCount += matchingMod.stackCount; @@ -375,7 +367,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { return modifierCount >= this.minNumberOfItems; } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { return [ "requiredItem", this.requiredHeldItemModifiers[0] ]; } } @@ -390,20 +382,20 @@ export class MoneyRequirement extends EncounterSceneRequirement { this.scalingMultiplier = scalingMultiplier ?? 0; } - override meetsRequirement(scene: BattleScene): boolean { - const money = scene.money; + override meetsRequirement(): boolean { + const money = globalScene.money; if (isNullOrUndefined(money)) { return false; } if (this.scalingMultiplier > 0) { - this.requiredMoney = scene.getWaveMoneyAmount(this.scalingMultiplier); + this.requiredMoney = globalScene.getWaveMoneyAmount(this.scalingMultiplier); } return !(this.requiredMoney > 0 && this.requiredMoney > money); } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - const value = this.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString(); + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { + const value = this.scalingMultiplier > 0 ? globalScene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString(); return [ "money", value ]; } } @@ -420,8 +412,8 @@ export class SpeciesRequirement extends EncounterPokemonRequirement { this.requiredSpecies = Array.isArray(species) ? species : [ species ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) { return false; } @@ -437,7 +429,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) { return [ "species", Species[pokemon.species.speciesId] ]; } @@ -458,8 +450,8 @@ export class NatureRequirement extends EncounterPokemonRequirement { this.requiredNature = Array.isArray(nature) ? nature : [ nature ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) { return false; } @@ -475,7 +467,7 @@ export class NatureRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) { return [ "nature", Nature[pokemon.nature] ]; } @@ -497,8 +489,8 @@ export class TypeRequirement extends EncounterPokemonRequirement { this.requiredType = Array.isArray(type) ? type : [ type ]; } - override meetsRequirement(scene: BattleScene): boolean { - let partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + let partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon)) { return false; @@ -520,7 +512,7 @@ export class TypeRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty)); if (includedTypes.length > 0) { return [ "type", Type[includedTypes[0]] ]; @@ -544,8 +536,8 @@ export class MoveRequirement extends EncounterPokemonRequirement { this.requiredMoves = Array.isArray(moves) ? moves : [ moves ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { return false; } @@ -566,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId)); if (includedMoves && includedMoves.length > 0 && includedMoves[0]) { return [ "move", includedMoves[0].getName() ]; @@ -593,8 +585,8 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement { this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [ learnableMove ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { return false; } @@ -610,7 +602,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove)); if (includedCompatMoves.length > 0) { return [ "compatibleMove", Moves[includedCompatMoves[0]] ]; @@ -634,8 +626,8 @@ export class AbilityRequirement extends EncounterPokemonRequirement { this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) { return false; } @@ -655,7 +647,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false)); if (!isNullOrUndefined(matchingAbility)) { return [ "ability", allAbilities[matchingAbility].name ]; @@ -676,8 +668,8 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [ statusEffect ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) { return false; } @@ -713,7 +705,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const reqStatus = this.requiredStatusEffect.filter((a) => { if (a === StatusEffect.NONE) { return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a; @@ -745,8 +737,8 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [ formChangeItem ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) { return false; } @@ -775,7 +767,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const requiredItems = this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)); if (requiredItems.length > 0) { return [ "formChangeItem", FormChangeItem[requiredItems[0]] ]; @@ -797,8 +789,8 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [ evolutionItems ]; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) { return false; } @@ -825,7 +817,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const requiredItems = this.requiredEvolutionItem.filter((evoItem) => this.filterByEvo(pokemon, evoItem)); if (requiredItems.length > 0) { return [ "evolutionItem", EvolutionItem[requiredItems[0]] ]; @@ -848,8 +840,8 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { this.requireTransferable = requireTransferable; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon)) { return false; } @@ -873,7 +865,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const requiredItems = pokemon?.getHeldItems().filter((it) => { return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem) && (!this.requireTransferable || it.isTransferable); @@ -899,8 +891,8 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe this.requireTransferable = requireTransferable; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty(); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty(); if (isNullOrUndefined(partyPokemon)) { return false; } @@ -928,7 +920,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const requiredItems = pokemon?.getHeldItems().filter((it) => { return this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier @@ -954,10 +946,10 @@ export class LevelRequirement extends EncounterPokemonRequirement { this.requiredLevelRange = requiredLevelRange; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { // Party Pokemon inside required level range if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) { - const partyPokemon = scene.getPlayerParty(); + const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { return false; @@ -975,7 +967,7 @@ export class LevelRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { return [ "level", pokemon?.level.toString() ?? "" ]; } } @@ -992,10 +984,10 @@ export class FriendshipRequirement extends EncounterPokemonRequirement { this.requiredFriendshipRange = requiredFriendshipRange; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { // Party Pokemon inside required friendship range if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) { - const partyPokemon = scene.getPlayerParty(); + const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { return false; @@ -1013,7 +1005,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { return [ "friendship", pokemon?.friendship.toString() ?? "" ]; } } @@ -1035,10 +1027,10 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { this.requiredHealthRange = requiredHealthRange; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { // Party Pokemon's health inside required health range if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) { - const partyPokemon = scene.getPlayerParty(); + const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { return false; @@ -1058,7 +1050,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { const hpRatio = pokemon?.getHpRatio(); if (!isNullOrUndefined(hpRatio)) { return [ "healthRatio", Math.floor(hpRatio * 100).toString() + "%" ]; @@ -1079,10 +1071,10 @@ export class WeightRequirement extends EncounterPokemonRequirement { this.requiredWeightRange = requiredWeightRange; } - override meetsRequirement(scene: BattleScene): boolean { + override meetsRequirement(): boolean { // Party Pokemon's weight inside required weight range if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) { - const partyPokemon = scene.getPlayerParty(); + const partyPokemon = globalScene.getPlayerParty(); const pokemonInRange = this.queryParty(partyPokemon); if (pokemonInRange.length < this.minNumberOfPokemon) { return false; @@ -1100,7 +1092,7 @@ export class WeightRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(pokemon?: PlayerPokemon): [string, string] { return [ "weight", pokemon?.getWeight().toString() ?? "" ]; } } diff --git a/src/data/mystery-encounters/mystery-encounter-save-data.ts b/src/data/mystery-encounters/mystery-encounter-save-data.ts index 259fbff7b85..7c8110628f0 100644 --- a/src/data/mystery-encounters/mystery-encounter-save-data.ts +++ b/src/data/mystery-encounters/mystery-encounter-save-data.ts @@ -1,7 +1,7 @@ -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters"; import { isNullOrUndefined } from "#app/utils"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; export class SeenEncounterData { type: MysteryEncounterType; diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index e341da4e435..c90649e551d 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -1,21 +1,26 @@ -import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; +import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import * as Utils from "#app/utils"; -import { StatusEffect } from "#enums/status-effect"; -import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue"; -import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option"; +import type { StatusEffect } from "#enums/status-effect"; +import type { OptionTextDisplay } from "./mystery-encounter-dialogue"; +import type MysteryEncounterDialogue from "./mystery-encounter-dialogue"; +import type { OptionPhaseCallback } from "./mystery-encounter-option"; +import type MysteryEncounterOption from "./mystery-encounter-option"; +import { MysteryEncounterOptionBuilder } from "./mystery-encounter-option"; import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements"; -import { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#app/battle"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { GameModes } from "#app/game-mode"; -import { EncounterAnim } from "#enums/encounter-anims"; -import { Challenges } from "#enums/challenges"; +import type { GameModes } from "#app/game-mode"; +import type { EncounterAnim } from "#enums/encounter-anims"; +import type { Challenges } from "#enums/challenges"; +import { globalScene } from "#app/global-scene"; export interface EncounterStartOfBattleEffect { sourcePokemon?: Pokemon; @@ -55,11 +60,11 @@ export interface IMysteryEncounter { skipToFightInput: boolean; preventGameStatsUpdates: boolean; - onInit?: (scene: BattleScene) => boolean; - onVisualsStart?: (scene: BattleScene) => boolean; - doEncounterExp?: (scene: BattleScene) => boolean; - doEncounterRewards?: (scene: BattleScene) => boolean; - doContinueEncounter?: (scene: BattleScene) => Promise; + onInit?: () => boolean; + onVisualsStart?: () => boolean; + doEncounterExp?: () => boolean; + doEncounterRewards?: () => boolean; + doContinueEncounter?: () => Promise; requirements: EncounterSceneRequirement[]; primaryPokemonRequirements: EncounterPokemonRequirement[]; @@ -159,24 +164,24 @@ export default class MysteryEncounter implements IMysteryEncounter { // #region Event callback functions /** Event when Encounter is first loaded, use it for data conditioning */ - onInit?: (scene: BattleScene) => boolean; + onInit?: () => boolean; /** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */ - onVisualsStart?: (scene: BattleScene) => boolean; + onVisualsStart?: () => boolean; /** Event triggered prior to {@linkcode CommandPhase}, during {@linkcode TurnInitPhase} */ - onTurnStart?: (scene: BattleScene) => boolean; + onTurnStart?: () => boolean; /** Event prior to any rewards logic in {@linkcode MysteryEncounterRewardsPhase} */ - onRewards?: (scene: BattleScene) => Promise; + onRewards?: () => Promise; /** Will provide the player party EXP before rewards are displayed for that wave */ - doEncounterExp?: (scene: BattleScene) => boolean; + doEncounterExp?: () => boolean; /** Will provide the player a rewards shop for that wave */ - doEncounterRewards?: (scene: BattleScene) => boolean; + doEncounterRewards?: () => boolean; /** Will execute callback during VictoryPhase of a continuousEncounter */ - doContinueEncounter?: (scene: BattleScene) => Promise; + doContinueEncounter?: () => Promise; /** * Can perform special logic when a ME battle is lost, before GameOver/battle retry prompt. * Should return `true` if it is treated as "real" Game Over, `false` if not. */ - onGameOver?: (scene: BattleScene) => boolean; + onGameOver?: () => boolean; /** * Requirements @@ -296,13 +301,12 @@ export default class MysteryEncounter implements IMysteryEncounter { /** * Checks if the current scene state meets the requirements for the {@linkcode MysteryEncounter} to spawn * This is used to filter the pool of encounters down to only the ones with all requirements met - * @param scene * @returns */ - meetsRequirements(scene: BattleScene): boolean { - const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement(scene)); - const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(scene); // secondary is checked first to handle cases of primary overlapping with secondary - const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene); + meetsRequirements(): boolean { + const sceneReq = !this.requirements.some(requirement => !requirement.meetsRequirement()); + const secReqs = this.meetsSecondaryRequirementAndSecondaryPokemonSelected(); // secondary is checked first to handle cases of primary overlapping with secondary + const priReqs = this.meetsPrimaryRequirementAndPrimaryPokemonSelected(); return sceneReq && secReqs && priReqs; } @@ -310,11 +314,10 @@ export default class MysteryEncounter implements IMysteryEncounter { /** * Checks if a specific player pokemon meets all given primary EncounterPokemonRequirements * Used automatically as part of {@linkcode meetsRequirements}, but can also be used to manually check certain Pokemon where needed - * @param scene * @param pokemon */ - pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean { - return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getPlayerParty()).map(p => p.id).includes(pokemon.id)); + pokemonMeetsPrimaryRequirements(pokemon: Pokemon): boolean { + return !this.primaryPokemonRequirements.some(req => !req.queryParty(globalScene.getPlayerParty()).map(p => p.id).includes(pokemon.id)); } /** @@ -322,22 +325,21 @@ export default class MysteryEncounter implements IMysteryEncounter { * AND there is a valid Pokemon assigned to {@linkcode primaryPokemon}. * If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined, * can cause scenarios where there are not enough Pokemon that are sufficient for all requirements. - * @param scene */ - private meetsPrimaryRequirementAndPrimaryPokemonSelected(scene: BattleScene): boolean { + private meetsPrimaryRequirementAndPrimaryPokemonSelected(): boolean { if (!this.primaryPokemonRequirements || this.primaryPokemonRequirements.length === 0) { - const activeMon = scene.getPlayerParty().filter(p => p.isActive(true)); + const activeMon = globalScene.getPlayerParty().filter(p => p.isActive(true)); if (activeMon.length > 0) { this.primaryPokemon = activeMon[0]; } else { - this.primaryPokemon = scene.getPlayerParty().filter(p => p.isAllowedInBattle())[0]; + this.primaryPokemon = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle())[0]; } return true; } - let qualified: PlayerPokemon[] = scene.getPlayerParty(); + let qualified: PlayerPokemon[] = globalScene.getPlayerParty(); for (const req of this.primaryPokemonRequirements) { - if (req.meetsRequirement(scene)) { - qualified = qualified.filter(pkmn => req.queryParty(scene.getPlayerParty()).includes(pkmn)); + if (req.meetsRequirement()) { + qualified = qualified.filter(pkmn => req.queryParty(globalScene.getPlayerParty()).includes(pkmn)); } else { this.primaryPokemon = undefined; return false; @@ -386,18 +388,17 @@ export default class MysteryEncounter implements IMysteryEncounter { * AND there is a valid Pokemon assigned to {@linkcode secondaryPokemon} (if applicable). * If both {@linkcode primaryPokemonRequirements} and {@linkcode secondaryPokemonRequirements} are defined, * can cause scenarios where there are not enough Pokemon that are sufficient for all requirements. - * @param scene */ - private meetsSecondaryRequirementAndSecondaryPokemonSelected(scene: BattleScene): boolean { + private meetsSecondaryRequirementAndSecondaryPokemonSelected(): boolean { if (!this.secondaryPokemonRequirements || this.secondaryPokemonRequirements.length === 0) { this.secondaryPokemon = []; return true; } - let qualified: PlayerPokemon[] = scene.getPlayerParty(); + let qualified: PlayerPokemon[] = globalScene.getPlayerParty(); for (const req of this.secondaryPokemonRequirements) { - if (req.meetsRequirement(scene)) { - qualified = qualified.filter(pkmn => req.queryParty(scene.getPlayerParty()).includes(pkmn)); + if (req.meetsRequirement()) { + qualified = qualified.filter(pkmn => req.queryParty(globalScene.getPlayerParty()).includes(pkmn)); } else { this.secondaryPokemon = []; return false; @@ -409,10 +410,9 @@ export default class MysteryEncounter implements IMysteryEncounter { /** * Initializes encounter intro sprites based on the sprite configs defined in spriteConfigs - * @param scene */ - initIntroVisuals(scene: BattleScene): void { - this.introVisuals = new MysteryEncounterIntroVisuals(scene, this); + initIntroVisuals(): void { + this.introVisuals = new MysteryEncounterIntroVisuals(this); } /** @@ -420,11 +420,11 @@ export default class MysteryEncounter implements IMysteryEncounter { * Will use the first support pokemon in list * For multiple support pokemon in the dialogue token, it will have to be overridden. */ - populateDialogueTokensFromRequirements(scene: BattleScene): void { - this.meetsRequirements(scene); + populateDialogueTokensFromRequirements(): void { + this.meetsRequirements(); if (this.requirements?.length > 0) { for (const req of this.requirements) { - const dialogueToken = req.getDialogueToken(scene); + const dialogueToken = req.getDialogueToken(); if (dialogueToken?.length === 2) { this.setDialogueToken(...dialogueToken); } @@ -434,7 +434,7 @@ export default class MysteryEncounter implements IMysteryEncounter { this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender()); for (const req of this.primaryPokemonRequirements) { if (!req.invertQuery) { - const value = req.getDialogueToken(scene, this.primaryPokemon); + const value = req.getDialogueToken(this.primaryPokemon); if (value?.length === 2) { this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]); } @@ -445,7 +445,7 @@ export default class MysteryEncounter implements IMysteryEncounter { this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender()); for (const req of this.secondaryPokemonRequirements) { if (!req.invertQuery) { - const value = req.getDialogueToken(scene, this.secondaryPokemon[0]); + const value = req.getDialogueToken(this.secondaryPokemon[0]); if (value?.length === 2) { this.setDialogueToken("primary" + capitalizeFirstLetter(value[0]), value[1]); } @@ -457,11 +457,11 @@ export default class MysteryEncounter implements IMysteryEncounter { // Dialogue tokens for options for (let i = 0; i < this.options.length; i++) { const opt = this.options[i]; - opt.meetsRequirements(scene); + opt.meetsRequirements(); const j = i + 1; if (opt.requirements.length > 0) { for (const req of opt.requirements) { - const dialogueToken = req.getDialogueToken(scene); + const dialogueToken = req.getDialogueToken(); if (dialogueToken?.length === 2) { this.setDialogueToken("option" + j + capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]); } @@ -471,7 +471,7 @@ export default class MysteryEncounter implements IMysteryEncounter { this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender()); for (const req of opt.primaryPokemonRequirements) { if (!req.invertQuery) { - const value = req.getDialogueToken(scene, opt.primaryPokemon); + const value = req.getDialogueToken(opt.primaryPokemon); if (value?.length === 2) { this.setDialogueToken("option" + j + "Primary" + capitalizeFirstLetter(value[0]), value[1]); } @@ -482,7 +482,7 @@ export default class MysteryEncounter implements IMysteryEncounter { this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender()); for (const req of opt.secondaryPokemonRequirements) { if (!req.invertQuery) { - const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); + const value = req.getDialogueToken(opt.secondaryPokemon[0]); if (value?.length === 2) { this.setDialogueToken("option" + j + "Secondary" + capitalizeFirstLetter(value[0]), value[1]); } @@ -518,10 +518,9 @@ export default class MysteryEncounter implements IMysteryEncounter { /** * Maintains seed offset for RNG consistency * Increments if the same {@linkcode MysteryEncounter} has multiple option select cycles - * @param scene */ - updateSeedOffset(scene: BattleScene) { - const currentOffset = this.seedOffset ?? scene.currentBattle.waveIndex * 1000; + updateSeedOffset() { + const currentOffset = this.seedOffset ?? globalScene.currentBattle.waveIndex * 1000; this.seedOffset = currentOffset + 512; } } @@ -858,7 +857,7 @@ export class MysteryEncounterBuilder implements Partial { * @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter * @returns */ - withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required> { + withRewards(doEncounterRewards: () => boolean): this & Required> { return Object.assign(this, { doEncounterRewards: doEncounterRewards }); } @@ -872,7 +871,7 @@ export class MysteryEncounterBuilder implements Partial { * @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter * @returns */ - withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required> { + withExp(doEncounterExp: () => boolean): this & Required> { return Object.assign(this, { doEncounterExp: doEncounterExp }); } @@ -883,7 +882,7 @@ export class MysteryEncounterBuilder implements Partial { * @param onInit Synchronous callback function to perform as soon as the encounter is selected for the next phase * @returns */ - withOnInit(onInit: (scene: BattleScene) => boolean): this & Required> { + withOnInit(onInit: () => boolean): this & Required> { return Object.assign(this, { onInit }); } @@ -893,7 +892,7 @@ export class MysteryEncounterBuilder implements Partial { * @param onVisualsStart Synchronous callback function to perform as soon as the enemy field finishes sliding in * @returns */ - withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required> { + withOnVisualsStart(onVisualsStart: () => boolean): this & Required> { return Object.assign(this, { onVisualsStart: onVisualsStart }); } diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 44feabeeaea..3219640e4e5 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -10,7 +10,7 @@ import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounte import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter"; import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter"; import { TrainingSessionEncounter } from "./encounters/training-session-encounter"; -import MysteryEncounter from "./mystery-encounter"; +import type MysteryEncounter from "./mystery-encounter"; import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index a2c08938fbe..3d454269204 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -1,8 +1,9 @@ -import BattleScene from "#app/battle-scene"; -import { Moves } from "#app/enums/moves"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { Moves } from "#app/enums/moves"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { isNullOrUndefined } from "#app/utils"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { globalScene } from "#app/global-scene"; /** * {@linkcode CanLearnMoveRequirement} options @@ -38,8 +39,8 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { this.invertQuery = options.invertQuery ?? false; } - override meetsRequirement(scene: BattleScene): boolean { - const partyPokemon = scene.getPlayerParty().filter((pkm) => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle())); + override meetsRequirement(): boolean { + const partyPokemon = globalScene.getPlayerParty().filter((pkm) => (this.includeFainted ? pkm.isAllowedInChallenge() : pkm.isAllowedInBattle())); if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) { return false; @@ -63,7 +64,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { } } - override getDialogueToken(_scene: BattleScene, _pokemon?: PlayerPokemon): [string, string] { + override getDialogueToken(__pokemon?: PlayerPokemon): [string, string] { return [ "requiredMoves", this.requiredMoves.map(m => new PokemonMove(m).getName()).join(", ") ]; } diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index acaa7c6244f..df9b6355017 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -1,23 +1,24 @@ -import BattleScene from "#app/battle-scene"; -import { getTextWithColors, TextStyle } from "#app/ui/text"; +import { globalScene } from "#app/global-scene"; +import type { TextStyle } from "#app/ui/text"; +import { getTextWithColors } from "#app/ui/text"; import { UiTheme } from "#enums/ui-theme"; import { isNullOrUndefined } from "#app/utils"; import i18next from "i18next"; /** - * Will inject all relevant dialogue tokens that exist in the {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text. + * Will inject all relevant dialogue tokens that exist in the {@linkcode BattlegScene.currentBattle.mysteryEncounter.dialogueTokens}, into i18n text. * Also adds BBCodeText fragments for colored text, if applicable * @param keyOrString * @param primaryStyle Can define a text style to be applied to the entire string. Must be defined for BBCodeText styles to be applied correctly */ -export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle): string | null { +export function getEncounterText(keyOrString?: string, primaryStyle?: TextStyle): string | null { if (isNullOrUndefined(keyOrString)) { return null; } - const uiTheme = scene.uiTheme ?? UiTheme.DEFAULT; + const uiTheme = globalScene.uiTheme ?? UiTheme.DEFAULT; - let textString: string | null = getTextWithDialogueTokens(scene, keyOrString); + let textString: string | null = getTextWithDialogueTokens(keyOrString); // Can only color the text if a Primary Style is defined // primaryStyle is applied to all text that does not have its own specified style @@ -29,12 +30,12 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima } /** - * Helper function to inject {@linkcode BattleScene.currentBattle.mysteryEncounter.dialogueTokens} into a given content string + * Helper function to inject {@linkcode globalScene.currentBattle.mysteryEncounter.dialogueTokens} into a given content string * @param scene * @param keyOrString */ -function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): string | null { - const tokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens; +function getTextWithDialogueTokens(keyOrString: string): string | null { + const tokens = globalScene.currentBattle?.mysteryEncounter?.dialogueTokens; if (i18next.exists(keyOrString, tokens)) { return i18next.t(keyOrString, tokens) as string; @@ -48,9 +49,9 @@ function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): str * @param scene * @param contentKey */ -export function queueEncounterMessage(scene: BattleScene, contentKey: string): void { - const text: string | null = getEncounterText(scene, contentKey); - scene.queueMessage(text ?? "", null, true); +export function queueEncounterMessage(contentKey: string): void { + const text: string | null = getEncounterText(contentKey); + globalScene.queueMessage(text ?? "", null, true); } /** @@ -62,10 +63,10 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: string): v * @param callbackDelay * @param promptDelay */ -export function showEncounterText(scene: BattleScene, contentKey: string, delay: number | null = null, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise { +export function showEncounterText(contentKey: string, delay: number | null = null, callbackDelay: number = 0, prompt: boolean = true, promptDelay: number | null = null): Promise { return new Promise(resolve => { - const text: string | null = getEncounterText(scene, contentKey); - scene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay); + const text: string | null = getEncounterText(contentKey); + globalScene.ui.showText(text ?? "", delay, () => resolve(), callbackDelay, prompt, promptDelay); }); } @@ -77,10 +78,10 @@ export function showEncounterText(scene: BattleScene, contentKey: string, delay: * @param speakerContentKey * @param callbackDelay */ -export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, delay: number | null = null, callbackDelay: number = 0): Promise { +export function showEncounterDialogue(textContentKey: string, speakerContentKey: string, delay: number | null = null, callbackDelay: number = 0): Promise { return new Promise(resolve => { - const text: string | null = getEncounterText(scene, textContentKey); - const speaker: string | null = getEncounterText(scene, speakerContentKey); - scene.ui.showDialogue(text ?? "", speaker ?? "", delay, () => resolve(), callbackDelay); + const text: string | null = getEncounterText(textContentKey); + const speaker: string | null = getEncounterText(speakerContentKey); + globalScene.ui.showDialogue(text ?? "", speaker ?? "", delay, () => resolve(), callbackDelay); }); } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index d43bce0ace5..8768fb06b37 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,34 +1,40 @@ -import Battle, { BattlerIndex, BattleType } from "#app/battle"; +import type Battle from "#app/battle"; +import { BattlerIndex, BattleType } from "#app/battle"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; -import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; +import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import Pokemon, { AiType, FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; -import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import type { AiType, PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; +import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; +import { ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; -import PokemonData from "#app/system/pokemon-data"; -import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; -import { PartyOption, PartyUiMode, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import type PokemonData from "#app/system/pokemon-data"; +import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import * as Utils from "#app/utils"; import { isNullOrUndefined } from "#app/utils"; -import { BattlerTagType } from "#enums/battler-tag-type"; +import type { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; -import { TrainerType } from "#enums/trainer-type"; +import type { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; -import BattleScene from "#app/battle-scene"; import Trainer, { TrainerVariant } from "#app/field/trainer"; -import { Gender } from "#app/data/gender"; -import { Nature } from "#enums/nature"; -import { Moves } from "#enums/moves"; +import type { Gender } from "#app/data/gender"; +import type { Nature } from "#enums/nature"; +import type { Moves } from "#enums/moves"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { Status } from "#app/data/status-effect"; -import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; -import PokemonSpecies from "#app/data/pokemon-species"; -import { Egg, IEggOptions } from "#app/data/egg"; -import { CustomPokemonData } from "#app/data/custom-pokemon-data"; -import HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import type { TrainerConfig } from "#app/data/trainer-config"; +import { trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type { IEggOptions } from "#app/data/egg"; +import { Egg } from "#app/data/egg"; +import type { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import type HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { MovePhase } from "#app/phases/move-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; import { TrainerVictoryPhase } from "#app/phases/trainer-victory-phase"; @@ -36,34 +42,35 @@ import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { GameOverPhase } from "#app/phases/game-over-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { StatusEffect } from "#enums/status-effect"; +import { globalScene } from "#app/global-scene"; /** * Animates exclamation sprite over trainer's head at start of encounter * @param scene */ -export function doTrainerExclamation(scene: BattleScene) { - const exclamationSprite = scene.add.sprite(0, 0, "encounter_exclaim"); +export function doTrainerExclamation() { + const exclamationSprite = globalScene.add.sprite(0, 0, "encounter_exclaim"); exclamationSprite.setName("exclamation"); - scene.field.add(exclamationSprite); - scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1); + globalScene.field.add(exclamationSprite); + globalScene.field.moveTo(exclamationSprite, globalScene.field.getAll().length - 1); exclamationSprite.setVisible(true); exclamationSprite.setPosition(110, 68); - scene.tweens.add({ + globalScene.tweens.add({ targets: exclamationSprite, y: "-=25", ease: "Cubic.easeOut", duration: 300, yoyo: true, onComplete: () => { - scene.time.delayedCall(800, () => { - scene.field.remove(exclamationSprite, true); + globalScene.time.delayedCall(800, () => { + globalScene.field.remove(exclamationSprite, true); }); } }); - scene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 }); + globalScene.playSound("battle_anims/GEN8- Exclaim", { volume: 0.7 }); } export interface EnemyPokemonConfig { @@ -114,14 +121,13 @@ export interface EnemyPartyConfig { * Generates an enemy party for a mystery encounter battle * This will override and replace any standard encounter generation logic * Useful for tailoring specific battles to mystery encounters - * @param scene Battle Scene * @param partyConfig Can pass various customizable attributes for the enemy party, see EnemyPartyConfig */ -export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: EnemyPartyConfig): Promise { +export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): Promise { const loaded: boolean = false; const loadEnemyAssets: Promise[] = []; - const battle: Battle = scene.currentBattle; + const battle: Battle = globalScene.currentBattle; let doubleBattle: boolean = partyConfig?.doubleBattle ?? false; @@ -130,10 +136,10 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: const partyTrainerConfig = partyConfig?.trainerConfig; let trainerConfig: TrainerConfig; if (!isNullOrUndefined(trainerType) || partyTrainerConfig) { - scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.TRAINER_BATTLE; - if (scene.currentBattle.trainer) { - scene.currentBattle.trainer.setVisible(false); - scene.currentBattle.trainer.destroy(); + globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.TRAINER_BATTLE; + if (globalScene.currentBattle.trainer) { + globalScene.currentBattle.trainer.setVisible(false); + globalScene.currentBattle.trainer.destroy(); } trainerConfig = partyTrainerConfig ? partyTrainerConfig : trainerConfigs[trainerType!]; @@ -141,23 +147,23 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); doubleBattle = doubleTrainer; const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!(Utils.randSeedInt(2)) : partyConfig.female; - const newTrainer = new Trainer(scene, trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, undefined, undefined, undefined, trainerConfig); + const newTrainer = new Trainer(trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, undefined, undefined, undefined, trainerConfig); newTrainer.x += 300; newTrainer.setVisible(false); - scene.field.add(newTrainer); - scene.currentBattle.trainer = newTrainer; + globalScene.field.add(newTrainer); + globalScene.currentBattle.trainer = newTrainer; loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite())); - battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); + battle.enemyLevels = globalScene.currentBattle.trainer.getPartyLevels(globalScene.currentBattle.waveIndex); } else { // Wild - scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE; + globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE; const numEnemies = partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1; - battle.enemyLevels = new Array(numEnemies).fill(null).map(() => scene.currentBattle.getLevelForWave()); + battle.enemyLevels = new Array(numEnemies).fill(null).map(() => globalScene.currentBattle.getLevelForWave()); } - scene.getEnemyParty().forEach(enemyPokemon => { - scene.field.remove(enemyPokemon, true); + globalScene.getEnemyParty().forEach(enemyPokemon => { + globalScene.field.remove(enemyPokemon, true); }); battle.enemyParty = []; battle.double = doubleBattle; @@ -168,7 +174,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc. // Leaving null/undefined will disable level scaling const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier : 0; - const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0); + const additive = Math.max(Math.round((globalScene.currentBattle.waveIndex / 10) * mult), 0); battle.enemyLevels = battle.enemyLevels.map(level => level + additive); battle.enemyLevels.forEach((level, e) => { @@ -184,7 +190,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: dataSource = config.dataSource; enemySpecies = config.species; isBoss = config.isBoss; - battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource); + battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource); } else { battle.enemyParty[e] = battle.trainer.genPartyMember(e); } @@ -196,17 +202,17 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemySpecies = config.species; isBoss = config.isBoss; if (isBoss) { - scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.BOSS_BATTLE; + globalScene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.BOSS_BATTLE; } } else { - enemySpecies = scene.randomSpecies(battle.waveIndex, level, true); + enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); } - battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource); + battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource); } } - const enemyPokemon = scene.getEnemyParty()[e]; + const enemyPokemon = globalScene.getEnemyParty()[e]; // Make sure basic data is clean enemyPokemon.hp = enemyPokemon.getMaxHp(); @@ -219,7 +225,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) { - scene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig)); + globalScene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig)); } if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) { @@ -257,7 +263,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Set Boss if (config.isBoss) { - let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true); + let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, level, enemySpecies, true); if (!isNullOrUndefined(config.bossSegmentModifier)) { segments += config.bossSegmentModifier; } @@ -343,7 +349,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: console.log(`Pokemon: ${enemyPokemon.name}`, `Species ID: ${enemyPokemon.species.speciesId}`, `Stats: ${enemyPokemon.stats}`, `Ability: ${enemyPokemon.getAbility().name}`, `Passive Ability: ${enemyPokemon.getPassiveAbility().name}`); }); - scene.pushPhase(new MysteryEncounterBattlePhase(scene, partyConfig.disableSwitch)); + globalScene.pushPhase(new MysteryEncounterBattlePhase(partyConfig.disableSwitch)); await Promise.all(loadEnemyAssets); battle.enemyParty.forEach((enemyPokemon_2, e_1) => { @@ -357,11 +363,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } }); if (!loaded) { - regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); + regenerateModifierPoolThresholds(globalScene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); const customModifierTypes = partyConfig?.pokemonConfigs ?.filter(config => config?.modifierConfigs) .map(config => config.modifierConfigs!); - scene.generateEnemyModifiers(customModifierTypes); + globalScene.generateEnemyModifiers(customModifierTypes); } } @@ -370,45 +376,42 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: * See: [startOfBattleEffects](IMysteryEncounter.startOfBattleEffects) for more details * * This promise does not need to be awaited on if called in an encounter onInit (will just load lazily) - * @param scene * @param moves */ -export function loadCustomMovesForEncounter(scene: BattleScene, moves: Moves | Moves[]) { +export function loadCustomMovesForEncounter(moves: Moves | Moves[]) { moves = Array.isArray(moves) ? moves : [ moves ]; - return Promise.all(moves.map(move => initMoveAnim(scene, move))) - .then(() => loadMoveAnimAssets(scene, moves)); + return Promise.all(moves.map(move => initMoveAnim(move))) + .then(() => loadMoveAnimAssets(moves)); } /** * Will update player money, and animate change (sound optional) - * @param scene * @param changeValue * @param playSound * @param showMessage */ -export function updatePlayerMoney(scene: BattleScene, changeValue: number, playSound: boolean = true, showMessage: boolean = true) { - scene.money = Math.min(Math.max(scene.money + changeValue, 0), Number.MAX_SAFE_INTEGER); - scene.updateMoneyText(); - scene.animateMoneyChanged(false); +export function updatePlayerMoney(changeValue: number, playSound: boolean = true, showMessage: boolean = true) { + globalScene.money = Math.min(Math.max(globalScene.money + changeValue, 0), Number.MAX_SAFE_INTEGER); + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); if (playSound) { - scene.playSound("se/buy"); + globalScene.playSound("se/buy"); } if (showMessage) { if (changeValue < 0) { - scene.queueMessage(i18next.t("mysteryEncounterMessages:paid_money", { amount: -changeValue }), null, true); + globalScene.queueMessage(i18next.t("mysteryEncounterMessages:paid_money", { amount: -changeValue }), null, true); } else { - scene.queueMessage(i18next.t("mysteryEncounterMessages:receive_money", { amount: changeValue }), null, true); + globalScene.queueMessage(i18next.t("mysteryEncounterMessages:receive_money", { amount: changeValue }), null, true); } } } /** * Converts modifier bullshit to an actual item - * @param scene Battle Scene * @param modifier * @param pregenArgs Can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null { +export function generateModifierType(modifier: () => ModifierType, pregenArgs?: any[]): ModifierType | null { const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); if (!modifierId) { return null; @@ -419,19 +422,18 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie // Populates item id and tier (order matters) result = result .withIdFromFunc(modifierTypes[modifierId]) - .withTierFromPool(ModifierPoolType.PLAYER, scene.getPlayerParty()); + .withTierFromPool(ModifierPoolType.PLAYER, globalScene.getPlayerParty()); - return result instanceof ModifierTypeGenerator ? result.generateType(scene.getPlayerParty(), pregenArgs) : result; + return result instanceof ModifierTypeGenerator ? result.generateType(globalScene.getPlayerParty(), pregenArgs) : result; } /** * Converts modifier bullshit to an actual item - * @param scene - Battle Scene * @param modifier * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption | null { - const result = generateModifierType(scene, modifier, pregenArgs); +export function generateModifierTypeOption(modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption | null { + const result = generateModifierType(modifier, pregenArgs); if (result) { return new ModifierTypeOption(result, 0); } @@ -440,30 +442,29 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M /** * This function is intended for use inside onPreOptionPhase() of an encounter option - * @param scene * @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen * If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object * @param onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen * @param selectablePokemonFilter */ -export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: PokemonSelectFilter): Promise { +export function selectPokemonForOption(onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: PokemonSelectFilter): Promise { return new Promise(resolve => { - const modeToSetOnExit = scene.ui.getMode(); + const modeToSetOnExit = globalScene.ui.getMode(); // Open party screen to choose pokemon - scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => { - if (slotIndex < scene.getPlayerParty().length) { - scene.ui.setMode(modeToSetOnExit).then(() => { - const pokemon = scene.getPlayerParty()[slotIndex]; + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => { + if (slotIndex < globalScene.getPlayerParty().length) { + globalScene.ui.setMode(modeToSetOnExit).then(() => { + const pokemon = globalScene.getPlayerParty()[slotIndex]; const secondaryOptions = onPokemonSelected(pokemon); if (!secondaryOptions) { - scene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); + globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); resolve(true); return; } // There is a second option to choose after selecting the Pokemon - scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { const displayOptions = () => { // Always appends a cancel option to bottom of options const fullOptions = secondaryOptions.map(option => { @@ -471,7 +472,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p const onSelect = option.handler; option.handler = () => { onSelect(); - scene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); + globalScene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); resolve(true); return true; }; @@ -479,13 +480,13 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p }).concat({ label: i18next.t("menu:cancel"), handler: () => { - scene.ui.clearText(); - scene.ui.setMode(modeToSetOnExit); + globalScene.ui.clearText(); + globalScene.ui.setMode(modeToSetOnExit); resolve(false); return true; }, onHover: () => { - showEncounterText(scene, i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); + showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); } }); @@ -500,19 +501,19 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p if (fullOptions[0].onHover) { fullOptions[0].onHover(); } - scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); + globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); }; - const textPromptKey = scene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt; + const textPromptKey = globalScene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt; if (!textPromptKey) { displayOptions(); } else { - showEncounterText(scene, textPromptKey).then(() => displayOptions()); + showEncounterText(textPromptKey).then(() => displayOptions()); } }); }); } else { - scene.ui.setMode(modeToSetOnExit).then(() => { + globalScene.ui.setMode(modeToSetOnExit).then(() => { if (onPokemonNotSelected) { onPokemonNotSelected(); } @@ -529,33 +530,33 @@ interface PokemonAndOptionSelected { } /** - * This function is intended for use inside onPreOptionPhase() of an encounter option - * @param scene - * If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object + * This function is intended for use inside `onPreOptionPhase()` of an encounter option + * + * If a second option needs to be selected, `onPokemonSelected` should return a {@linkcode OptionSelectItem}`[]` object * @param options * @param optionSelectPromptKey * @param selectablePokemonFilter * @param onHoverOverCancelOption */ -export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelectItem[], optionSelectPromptKey: string, selectablePokemonFilter?: PokemonSelectFilter, onHoverOverCancelOption?: () => void): Promise { +export function selectOptionThenPokemon(options: OptionSelectItem[], optionSelectPromptKey: string, selectablePokemonFilter?: PokemonSelectFilter, onHoverOverCancelOption?: () => void): Promise { return new Promise(resolve => { - const modeToSetOnExit = scene.ui.getMode(); + const modeToSetOnExit = globalScene.ui.getMode(); const displayOptions = (config: OptionSelectConfig) => { - scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (!optionSelectPromptKey) { // Do hover over the starting selection option if (fullOptions[0].onHover) { fullOptions[0].onHover(); } - scene.ui.setMode(Mode.OPTION_SELECT, config); + globalScene.ui.setMode(Mode.OPTION_SELECT, config); } else { - showEncounterText(scene, optionSelectPromptKey).then(() => { + showEncounterText(optionSelectPromptKey).then(() => { // Do hover over the starting selection option if (fullOptions[0].onHover) { fullOptions[0].onHover(); } - scene.ui.setMode(Mode.OPTION_SELECT, config); + globalScene.ui.setMode(Mode.OPTION_SELECT, config); }); } }); @@ -563,10 +564,10 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec const selectPokemonAfterOption = (selectedOptionIndex: number) => { // Open party screen to choose a Pokemon - scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => { - if (slotIndex < scene.getPlayerParty().length) { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: number, option: PartyOption) => { + if (slotIndex < globalScene.getPlayerParty().length) { // Pokemon and option selected - scene.ui.setMode(modeToSetOnExit).then(() => { + globalScene.ui.setMode(modeToSetOnExit).then(() => { const result: PokemonAndOptionSelected = { selectedPokemonIndex: slotIndex, selectedOptionIndex: selectedOptionIndex }; resolve(result); }); @@ -590,8 +591,8 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec }).concat({ label: i18next.t("menu:cancel"), handler: () => { - scene.ui.clearText(); - scene.ui.setMode(modeToSetOnExit); + globalScene.ui.clearText(); + globalScene.ui.setMode(modeToSetOnExit); resolve(null); return true; }, @@ -599,7 +600,7 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec if (onHoverOverCancelOption) { onHoverOverCancelOption(); } - showEncounterText(scene, i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); + showEncounterText(i18next.t("mysteryEncounterMessages:cancel_option"), 0, 0, false); } }); @@ -617,27 +618,26 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec /** * Will initialize reward phases to follow the mystery encounter * Can have shop displayed or skipped - * @param scene - Battle Scene * @param customShopRewards - adds a shop phase with the specified rewards / reward tiers * @param eggRewards * @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase}) */ -export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) { - scene.currentBattle.mysteryEncounter!.doEncounterRewards = (scene: BattleScene) => { +export function setEncounterRewards(customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) { + globalScene.currentBattle.mysteryEncounter!.doEncounterRewards = () => { if (preRewardsCallback) { preRewardsCallback(); } if (customShopRewards) { - scene.unshiftPhase(new SelectModifierPhase(scene, 0, undefined, customShopRewards)); + globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, customShopRewards)); } else { - scene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); } if (eggRewards) { eggRewards.forEach(eggOptions => { const egg = new Egg(eggOptions); - egg.addEggToGameData(scene); + egg.addEggToGameData(); }); } @@ -648,10 +648,11 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust /** * Will initialize exp phases into the phase queue (these are in addition to any combat or other exp earned) * Exp Share and Exp Balance will still function as normal - * @param scene - Battle Scene * @param participantId - id/s of party pokemon that get full exp value. Other party members will receive Exp Share amounts * @param baseExpValue - gives exp equivalent to a pokemon of the wave index's level. + * * Guidelines: + * ```md * 36 - Sunkern (lowest in game) * 62-64 - regional starter base evos * 100 - Scyther @@ -660,14 +661,15 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust * 290 - trio legendaries * 340 - box legendaries * 608 - Blissey (highest in game) + * ``` * https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX) * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue */ -export function setEncounterExp(scene: BattleScene, participantId: number | number[], baseExpValue: number, useWaveIndex: boolean = true) { +export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex: boolean = true) { const participantIds = Array.isArray(participantId) ? participantId : [ participantId ]; - scene.currentBattle.mysteryEncounter!.doEncounterExp = (scene: BattleScene) => { - scene.unshiftPhase(new PartyExpPhase(scene, baseExpValue, useWaveIndex, new Set(participantIds))); + globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { + globalScene.unshiftPhase(new PartyExpPhase(baseExpValue, useWaveIndex, new Set(participantIds))); return true; }; @@ -686,60 +688,57 @@ export class OptionSelectSettings { /** * Can be used to queue a new series of Options to select for an Encounter * MUST be used only in onOptionPhase, will not work in onPreOptionPhase or onPostOptionPhase - * @param scene * @param optionSelectSettings */ -export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSettings: OptionSelectSettings) { - scene.pushPhase(new MysteryEncounterPhase(scene, optionSelectSettings)); +export function initSubsequentOptionSelect(optionSelectSettings: OptionSelectSettings) { + globalScene.pushPhase(new MysteryEncounterPhase(optionSelectSettings)); } /** * Can be used to exit an encounter without any battles or followup * Will skip any shops and rewards, and queue the next encounter phase as normal - * @param scene * @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available * @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase) */ -export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) { - scene.currentBattle.mysteryEncounter!.encounterMode = encounterMode; - scene.clearPhaseQueue(); - scene.clearPhaseQueueSplice(); - handleMysteryEncounterVictory(scene, addHealPhase); +export function leaveEncounterWithoutBattle(addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) { + globalScene.currentBattle.mysteryEncounter!.encounterMode = encounterMode; + globalScene.clearPhaseQueue(); + globalScene.clearPhaseQueueSplice(); + handleMysteryEncounterVictory(addHealPhase); } /** * - * @param scene * @param addHealPhase - Adds an empty shop phase to allow player to purchase healing items * @param doNotContinue - default `false`. If set to true, will not end the battle and continue to next wave */ -export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: boolean = false, doNotContinue: boolean = false) { - const allowedPkm = scene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); +export function handleMysteryEncounterVictory(addHealPhase: boolean = false, doNotContinue: boolean = false) { + const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); if (allowedPkm.length === 0) { - scene.clearPhaseQueue(); - scene.unshiftPhase(new GameOverPhase(scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase()); return; } // If in repeated encounter variant, do nothing // Variant must eventually be swapped in order to handle "true" end of the encounter - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.continuousEncounter || doNotContinue) { return; } else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { - scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); - scene.pushPhase(new EggLapsePhase(scene)); - } else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { - scene.pushPhase(new BattleEndPhase(scene, true)); + globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); + globalScene.pushPhase(new EggLapsePhase()); + } else if (!globalScene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { + globalScene.pushPhase(new BattleEndPhase(true)); if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { - scene.pushPhase(new TrainerVictoryPhase(scene)); + globalScene.pushPhase(new TrainerVictoryPhase()); } - if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) { - scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); + if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { + globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - scene.pushPhase(new EggLapsePhase(scene)); + globalScene.pushPhase(new EggLapsePhase()); } } } @@ -747,48 +746,46 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: /** * Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge - * @param scene * @param addHealPhase */ -export function handleMysteryEncounterBattleFailed(scene: BattleScene, addHealPhase: boolean = false, doNotContinue: boolean = false) { - const allowedPkm = scene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); +export function handleMysteryEncounterBattleFailed(addHealPhase: boolean = false, doNotContinue: boolean = false) { + const allowedPkm = globalScene.getPlayerParty().filter((pkm) => pkm.isAllowedInBattle()); if (allowedPkm.length === 0) { - scene.clearPhaseQueue(); - scene.unshiftPhase(new GameOverPhase(scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase()); return; } // If in repeated encounter variant, do nothing // Variant must eventually be swapped in order to handle "true" end of the encounter - const encounter = scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.continuousEncounter || doNotContinue) { return; } else if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) { - scene.pushPhase(new BattleEndPhase(scene, false)); + globalScene.pushPhase(new BattleEndPhase(false)); } - scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); + globalScene.pushPhase(new MysteryEncounterRewardsPhase(addHealPhase)); if (!encounter.doContinueEncounter) { // Only lapse eggs once for multi-battle encounters - scene.pushPhase(new EggLapsePhase(scene)); + globalScene.pushPhase(new EggLapsePhase()); } } /** * - * @param scene * @param hide - If true, performs ease out and hide visuals. If false, eases in visuals. Defaults to true * @param destroy - If true, will destroy visuals ONLY ON HIDE TRANSITION. Does nothing on show. Defaults to true * @param duration */ -export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise { +export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise { return new Promise(resolve => { - const introVisuals = scene.currentBattle.mysteryEncounter!.introVisuals; - const enemyPokemon = scene.getEnemyField(); + const introVisuals = globalScene.currentBattle.mysteryEncounter!.introVisuals; + const enemyPokemon = globalScene.getEnemyField(); if (enemyPokemon) { - scene.currentBattle.enemyParty = []; + globalScene.currentBattle.enemyParty = []; } if (introVisuals) { if (!hide) { @@ -800,7 +797,7 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: } // Transition - scene.tweens.add({ + globalScene.tweens.add({ targets: [ introVisuals, enemyPokemon ], x: `${hide ? "+" : "-"}=16`, y: `${hide ? "-" : "+"}=16`, @@ -809,13 +806,13 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: duration, onComplete: () => { if (hide && destroy) { - scene.field.remove(introVisuals, true); + globalScene.field.remove(introVisuals, true); enemyPokemon.forEach(pokemon => { - scene.field.remove(pokemon, true); + globalScene.field.remove(pokemon, true); }); - scene.currentBattle.mysteryEncounter!.introVisuals = undefined; + globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined; } resolve(true); } @@ -829,11 +826,10 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: /** * Will queue moves for any pokemon to use before the first CommandPhase of a battle * Mostly useful for allowing {@linkcode MysteryEncounter} enemies to "cheat" and use moves before the first turn - * @param scene */ -export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) { - const encounter = scene.currentBattle.mysteryEncounter; - if (scene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) { +export function handleMysteryEncounterBattleStartEffects() { + const encounter = globalScene.currentBattle.mysteryEncounter; + if (globalScene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) { const effects = encounter.startOfBattleEffects; effects.forEach(effect => { let source; @@ -841,24 +837,24 @@ export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) { source = effect.sourcePokemon; } else if (!isNullOrUndefined(effect.sourceBattlerIndex)) { if (effect.sourceBattlerIndex === BattlerIndex.ATTACKER) { - source = scene.getEnemyField()[0]; + source = globalScene.getEnemyField()[0]; } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY) { - source = scene.getEnemyField()[0]; + source = globalScene.getEnemyField()[0]; } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY_2) { - source = scene.getEnemyField()[1]; + source = globalScene.getEnemyField()[1]; } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER) { - source = scene.getPlayerField()[0]; + source = globalScene.getPlayerField()[0]; } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER_2) { - source = scene.getPlayerField()[1]; + source = globalScene.getPlayerField()[1]; } } else { - source = scene.getEnemyField()[0]; + source = globalScene.getEnemyField()[0]; } - scene.pushPhase(new MovePhase(scene, source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); + globalScene.pushPhase(new MovePhase(source, effect.targets, effect.move, effect.followUp, effect.ignorePp)); }); // Pseudo turn end phase to reset flinch states, Endure, etc. - scene.pushPhase(new MysteryEncounterBattleStartCleanupPhase(scene)); + globalScene.pushPhase(new MysteryEncounterBattleStartCleanupPhase()); encounter.startOfBattleEffectsComplete = true; } @@ -867,13 +863,12 @@ export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) { /** * Can queue extra phases or logic during {@linkcode TurnInitPhase} * Should mostly just be used for injecting custom phases into the battle system on turn start - * @param scene * @return boolean - if true, will skip the remainder of the {@linkcode TurnInitPhase} */ -export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): boolean { - const encounter = scene.currentBattle.mysteryEncounter; - if (scene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.onTurnStart) { - return encounter.onTurnStart(scene); +export function handleMysteryEncounterTurnStartEffects(): boolean { + const encounter = globalScene.currentBattle.mysteryEncounter; + if (globalScene.currentBattle.isBattleMysteryEncounter() && encounter && encounter.onTurnStart) { + return encounter.onTurnStart(); } return false; @@ -882,10 +877,9 @@ export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): bool /** * TODO: remove once encounter spawn rate is finalized * Just a helper function to calculate aggregate stats for MEs in a Classic run - * @param scene * @param baseSpawnWeight */ -export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: number) { +export function calculateMEAggregateStats(baseSpawnWeight: number) { const numRuns = 1000; let run = 0; const biomes = Object.keys(Biome).filter(key => isNaN(Number(key))); @@ -898,9 +892,9 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n const encountersByBiome = new Map(biomes.map(b => [ b, 0 ])); const validMEfloorsByBiome = new Map(biomes.map(b => [ b, 0 ])); let currentBiome = Biome.TOWN; - let currentArena = scene.newArena(currentBiome); - scene.setSeed(Utils.randomString(24)); - scene.resetSeed(); + let currentArena = globalScene.newArena(currentBiome); + globalScene.setSeed(Utils.randomString(24)); + globalScene.resetSeed(); for (let i = 10; i < 180; i++) { // Boss if (i % 10 === 0) { @@ -911,7 +905,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n if (i % 10 === 1) { if (Array.isArray(biomeLinks[currentBiome])) { let biomes: Biome[]; - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { biomes = (biomeLinks[currentBiome] as (Biome | [Biome, number])[]) .filter(b => { return !Array.isArray(b) || !Utils.randSeedInt(b[1]); @@ -932,20 +926,20 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n if (!(i % 50)) { currentBiome = Biome.END; } else { - currentBiome = scene.generateRandomBiome(i); + currentBiome = globalScene.generateRandomBiome(i); } } - currentArena = scene.newArena(currentBiome); + currentArena = globalScene.newArena(currentBiome); } // Fixed battle - if (scene.gameMode.isFixedBattle(i)) { + if (globalScene.gameMode.isFixedBattle(i)) { continue; } // Trainer - if (scene.gameMode.isWaveTrainer(i, currentArena)) { + if (globalScene.gameMode.isWaveTrainer(i, currentArena)) { continue; } @@ -995,7 +989,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n const encountersByBiomeRuns: Map[] = []; const validFloorsByBiome: Map[] = []; while (run < numRuns) { - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const [ numEncounters, encountersByBiome, validMEfloorsByBiome ] = calculateNumEncounters(); encounterRuns.push(numEncounters); encountersByBiomeRuns.push(encountersByBiome); @@ -1047,17 +1041,16 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n /** * TODO: remove once encounter spawn rate is finalized * Just a helper function to calculate aggregate stats for MEs in a Classic run - * @param scene * @param luckValue - 0 to 14 */ -export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: number) { +export function calculateRareSpawnAggregateStats(luckValue: number) { const numRuns = 1000; let run = 0; const calculateNumRareEncounters = (): any[] => { const bossEncountersByRarity = [ 0, 0, 0, 0 ]; - scene.setSeed(Utils.randomString(24)); - scene.resetSeed(); + globalScene.setSeed(Utils.randomString(24)); + globalScene.resetSeed(); // There are 12 wild boss floors for (let i = 0; i < 12; i++) { // Roll boss tier @@ -1091,7 +1084,7 @@ export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: const encounterRuns: number[][] = []; while (run < numRuns) { - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const bossEncountersByRarity = calculateNumRareEncounters(); encounterRuns.push(bossEncountersByRarity); }, 1000 * run); diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 072b5e5b160..bcce2ad1349 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -1,28 +1,32 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import i18next from "i18next"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball"; import { PlayerGender } from "#enums/player-gender"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { achvs } from "#app/system/achv"; import { Mode } from "#app/ui/ui"; -import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; +import type { PartyOption } from "#app/ui/party-ui-handler"; +import { PartyUiMode } from "#app/ui/party-ui-handler"; import { Species } from "#enums/species"; -import { Type } from "#enums/type"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type { Type } from "#enums/type"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { Gender } from "#app/data/gender"; -import { PermanentStat } from "#enums/stat"; +import type { PermanentStat } from "#enums/stat"; import { VictoryPhase } from "#app/phases/victory-phase"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; -import { Abilities } from "#enums/abilities"; +import type { Abilities } from "#enums/abilities"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; @@ -45,7 +49,6 @@ export function getSpriteKeysFromSpecies(species: Species, female?: boolean, for /** * Gets the sprite key and file root for a given Pokemon (accounts for gender, shiny, variants, forms, and experimental) - * @param pokemon */ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string, fileRoot: string } { const spriteKey = pokemon.getSpeciesForm().getSpriteKey(pokemon.getGender() === Gender.FEMALE, pokemon.formIndex, pokemon.shiny, pokemon.variant); @@ -57,14 +60,13 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string, /** * Will never remove the player's last non-fainted Pokemon (if they only have 1). * Otherwise, picks a Pokemon completely at random and removes from the party - * @param scene * @param isAllowed Default `false`. If `true`, only picks from legal mons. If no legal mons are found (or there is 1, with `doNotReturnLastAllowedMon = true`), will return a mon that is not allowed. * @param isFainted Default `false`. If `true`, includes fainted mons. * @param doNotReturnLastAllowedMon Default `false`. If `true`, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted) * @returns */ -export function getRandomPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon { - const party = scene.getPlayerParty(); +export function getRandomPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon { + const party = globalScene.getPlayerParty(); let chosenIndex: number; let chosenPokemon: PlayerPokemon | null = null; const fullyLegalMons = party.filter(p => (!isAllowed || p.isAllowedInChallenge()) && (isFainted || !p.isFainted())); @@ -102,8 +104,8 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowed: boolean = * @param isFainted Default false. If true, includes fainted mons. * @returns */ -export function getHighestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { - const party = scene.getPlayerParty(); +export function getHighestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { + const party = globalScene.getPlayerParty(); let pokemon: PlayerPokemon | null = null; for (const p of party) { @@ -128,8 +130,8 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, isAllowed: bool * @param isFainted Default false. If true, includes fainted mons. * @returns */ -export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { - const party = scene.getPlayerParty(); +export function getHighestStatPlayerPokemon(stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { + const party = globalScene.getPlayerParty(); let pokemon: PlayerPokemon | null = null; for (const p of party) { @@ -153,8 +155,8 @@ export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentS * @param isFainted Default false. If true, includes fainted mons. * @returns */ -export function getLowestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { - const party = scene.getPlayerParty(); +export function getLowestLevelPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { + const party = globalScene.getPlayerParty(); let pokemon: PlayerPokemon | null = null; for (const p of party) { @@ -178,8 +180,8 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, isAllowed: boole * @param isFainted Default false. If true, includes fainted mons. * @returns */ -export function getHighestStatTotalPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { - const party = scene.getPlayerParty(); +export function getHighestStatTotalPlayerPokemon(isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon { + const party = globalScene.getPlayerParty(); let pokemon: PlayerPokemon | null = null; for (const p of party) { @@ -253,11 +255,11 @@ export function getRandomSpeciesByStarterCost(starterTiers: number | [number, nu * @param scene the battle scene * @param pokemon the player pokemon to KO */ -export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) { +export function koPlayerPokemon(pokemon: PlayerPokemon) { pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); pokemon.updateInfo(); - queueEncounterMessage(scene, i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + queueEncounterMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } /** @@ -269,11 +271,11 @@ export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) { * @param value the hp change amount. Positive for heal. Negative for damage * */ -function applyHpChangeToPokemon(scene: BattleScene, pokemon: PlayerPokemon, value: number) { +function applyHpChangeToPokemon(pokemon: PlayerPokemon, value: number) { const hpChange = Math.round(pokemon.hp + value); const nextHp = Math.max(Math.min(hpChange, pokemon.getMaxHp()), 0); if (nextHp === 0) { - koPlayerPokemon(scene, pokemon); + koPlayerPokemon(pokemon); } else { pokemon.hp = nextHp; } @@ -286,7 +288,7 @@ function applyHpChangeToPokemon(scene: BattleScene, pokemon: PlayerPokemon, valu * @param damage the amount of damage to apply * @see {@linkcode applyHpChangeToPokemon} */ -export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, damage: number) { +export function applyDamageToPokemon(pokemon: PlayerPokemon, damage: number) { if (damage <= 0) { console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead."); } @@ -294,7 +296,7 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) { damage = pokemon.hp - 1; } - applyHpChangeToPokemon(scene, pokemon, -damage); + applyHpChangeToPokemon(pokemon, -damage); } /** @@ -304,12 +306,12 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, * @param heal the amount of heal to apply * @see {@linkcode applyHpChangeToPokemon} */ -export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) { +export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) { if (heal <= 0) { console.warn("Damaging pokemon with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead."); } - applyHpChangeToPokemon(scene, pokemon, heal); + applyHpChangeToPokemon(pokemon, heal); } /** @@ -320,11 +322,11 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h */ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) { const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE() - .generateType(pokemon.scene.getPlayerParty(), [ value ]) + .generateType(globalScene.getPlayerParty(), [ value ]) ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE); const modifier = modType?.newModifier(pokemon); if (modifier) { - await pokemon.scene.addModifier(modifier, false, false, false, true); + await globalScene.addModifier(modifier, false, false, false, true); pokemon.calculateStats(); } } @@ -337,10 +339,10 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb * @param modType * @param fallbackModifierType */ -export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) { +export async function applyModifierTypeToPlayerPokemon(pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) { // Check if the Pokemon has max stacks of that item already const modifier = modType.newModifier(pokemon); - const existing = scene.findModifier(m => ( + const existing = globalScene.findModifier(m => ( m instanceof PokemonHeldItemModifier && m.type.id === modType.id && m.pokemonId === pokemon.id && @@ -348,16 +350,16 @@ export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokem )) as PokemonHeldItemModifier; // At max stacks - if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { + if (existing && existing.getStackCount() >= existing.getMaxStackCount()) { if (!fallbackModifierType) { return; } // Apply fallback - return applyModifierTypeToPlayerPokemon(scene, pokemon, fallbackModifierType); + return applyModifierTypeToPlayerPokemon(pokemon, fallbackModifierType); } - await scene.addModifier(modifier, false, false, false, true); + await globalScene.addModifier(modifier, false, false, false, true); } /** @@ -371,7 +373,7 @@ export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokem * @param pokeballType * @param ballTwitchRate - can pass custom ball catch rates (for special events, like safari) */ -export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise { +export function trainerThrowPokeball(pokemon: EnemyPokemon, pokeballType: PokeballType, ballTwitchRate?: number): Promise { const originalY: number = pokemon.y; if (!ballTwitchRate) { @@ -386,43 +388,43 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, const fpOffset = pokemon.getFieldPositionOffset(); const pokeballAtlasKey = getPokeballAtlasKey(pokeballType); - const pokeball: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey); + const pokeball: Phaser.GameObjects.Sprite = globalScene.addFieldSprite(16 + 75, 80 + 25, "pb", pokeballAtlasKey); pokeball.setOrigin(0.5, 0.625); - scene.field.add(pokeball); + globalScene.field.add(pokeball); - scene.time.delayedCall(300, () => { - scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon); + globalScene.time.delayedCall(300, () => { + globalScene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon); }); return new Promise(resolve => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - scene.time.delayedCall(512, () => { - scene.playSound("se/pb_throw"); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + globalScene.time.delayedCall(512, () => { + globalScene.playSound("se/pb_throw"); // Trainer throw frames - scene.trainer.setFrame("2"); - scene.time.delayedCall(256, () => { - scene.trainer.setFrame("3"); - scene.time.delayedCall(768, () => { - scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + globalScene.trainer.setFrame("2"); + globalScene.time.delayedCall(256, () => { + globalScene.trainer.setFrame("3"); + globalScene.time.delayedCall(768, () => { + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); }); }); // Pokeball move and catch logic - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, x: { value: 236 + fpOffset[0], ease: "Linear" }, y: { value: 16 + fpOffset[1], ease: "Cubic.easeOut" }, duration: 500, onComplete: () => { pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - scene.playSound("se/pb_rel"); + globalScene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + globalScene.playSound("se/pb_rel"); pokemon.tint(getPokeballTintColor(pokeballType)); - addPokeballOpenParticles(scene, pokeball.x, pokeball.y, pokeballType); + addPokeballOpenParticles(pokeball.x, pokeball.y, pokeballType); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 500, ease: "Sine.easeIn", @@ -431,13 +433,13 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, onComplete: () => { pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); pokemon.setVisible(false); - scene.playSound("se/pb_catch"); - scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`)); + globalScene.playSound("se/pb_catch"); + globalScene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}`)); const doShake = () => { let shakeCount = 0; const pbX = pokeball.x; - const shakeCounter = scene.tweens.addCounter({ + const shakeCounter = globalScene.tweens.addCounter({ from: 0, to: 1, repeat: 4, @@ -456,30 +458,30 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, onRepeat: () => { if (!pokemon.species.isObtainable()) { shakeCounter.stop(); - failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false)); + failCatch(pokemon, originalY, pokeball, pokeballType).then(() => resolve(false)); } else if (shakeCount++ < 3) { if (randSeedInt(65536) < ballTwitchRate) { - scene.playSound("se/pb_move"); + globalScene.playSound("se/pb_move"); } else { shakeCounter.stop(); - failCatch(scene, pokemon, originalY, pokeball, pokeballType).then(() => resolve(false)); + failCatch(pokemon, originalY, pokeball, pokeballType).then(() => resolve(false)); } } else { - scene.playSound("se/pb_lock"); - addPokeballCaptureStars(scene, pokeball); + globalScene.playSound("se/pb_lock"); + addPokeballCaptureStars(pokeball); - const pbTint = scene.add.sprite(pokeball.x, pokeball.y, "pb", "pb"); + const pbTint = globalScene.add.sprite(pokeball.x, pokeball.y, "pb", "pb"); pbTint.setOrigin(pokeball.originX, pokeball.originY); pbTint.setTintFill(0); pbTint.setAlpha(0); - scene.field.add(pbTint); - scene.tweens.add({ + globalScene.field.add(pbTint); + globalScene.tweens.add({ targets: pbTint, alpha: 0.375, duration: 200, easing: "Sine.easeOut", onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: pbTint, alpha: 0, duration: 200, @@ -491,12 +493,12 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, } }, onComplete: () => { - catchPokemon(scene, pokemon, pokeball, pokeballType).then(() => resolve(true)); + catchPokemon(pokemon, pokeball, pokeballType).then(() => resolve(true)); } }); }; - scene.time.delayedCall(250, () => doPokeballBounceAnim(scene, pokeball, 16, 72, 350, doShake)); + globalScene.time.delayedCall(250, () => doPokeballBounceAnim(pokeball, 16, 72, 350, doShake)); } }); } @@ -513,9 +515,9 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, * @param pokeball * @param pokeballType */ -function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) { +function failCatch(pokemon: EnemyPokemon, originalY: number, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType) { return new Promise(resolve => { - scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokemon.setY(originalY); if (pokemon.status?.effect !== StatusEffect.SLEEP) { pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); @@ -526,19 +528,19 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, const pokeballAtlasKey = getPokeballAtlasKey(pokeballType); pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - scene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + globalScene.time.delayedCall(17, () => pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeOut", scale: 1 }); - scene.currentBattle.lastUsedPokeball = pokeballType; - removePb(scene, pokeball); + globalScene.currentBattle.lastUsedPokeball = pokeballType; + removePb(pokeball); - scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true); + globalScene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true); }); } @@ -551,56 +553,56 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, * @param showCatchObtainMessage * @param isObtain */ -export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise { +export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise { const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { - scene.validateAchv(achvs.HIDDEN_ABILITY); + globalScene.validateAchv(achvs.HIDDEN_ABILITY); } if (pokemon.species.subLegendary) { - scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_SUB_LEGENDARY); } if (pokemon.species.legendary) { - scene.validateAchv(achvs.CATCH_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_LEGENDARY); } if (pokemon.species.mythical) { - scene.validateAchv(achvs.CATCH_MYTHICAL); + globalScene.validateAchv(achvs.CATCH_MYTHICAL); } - scene.pokemonInfoContainer.show(pokemon, true); + globalScene.pokemonInfoContainer.show(pokemon, true); - scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); return new Promise(resolve => { const doPokemonCatchMenu = () => { const end = () => { // Ensure the pokemon is in the enemy party in all situations - if (!scene.getEnemyParty().some(p => p.id === pokemon.id)) { - scene.getEnemyParty().push(pokemon); + if (!globalScene.getEnemyParty().some(p => p.id === pokemon.id)) { + globalScene.getEnemyParty().push(pokemon); } - scene.unshiftPhase(new VictoryPhase(scene, pokemon.id, true)); - scene.pokemonInfoContainer.hide(); + globalScene.unshiftPhase(new VictoryPhase(pokemon.id, true)); + globalScene.pokemonInfoContainer.hide(); if (pokeball) { - removePb(scene, pokeball); + removePb(pokeball); } resolve(); }; const removePokemon = () => { if (pokemon) { - scene.field.remove(pokemon, true); + globalScene.field.remove(pokemon, true); } }; const addToParty = (slotIndex?: number) => { const newPokemon = pokemon.addToParty(pokeballType, slotIndex); - const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); - if (scene.getPlayerParty().filter(p => p.isShiny()).length === 6) { - scene.validateAchv(achvs.SHINY_PARTY); + const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === 6) { + globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => scene.addModifier(m, true))).then(() => { - scene.updateModifiers(true); + Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { + globalScene.updateModifiers(true); removePokemon(); if (newPokemon) { newPokemon.loadAssets().then(end); @@ -609,21 +611,21 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po } }); }; - Promise.all([ pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon) ]).then(() => { - if (scene.getPlayerParty().length === 6) { + Promise.all([ pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon) ]).then(() => { + if (globalScene.getPlayerParty().length === 6) { const promptRelease = () => { - scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { - scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); - scene.ui.setMode(Mode.CONFIRM, () => { - const newPokemon = scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); - scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { - scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { + globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); + globalScene.ui.setMode(Mode.CONFIRM, () => { + const newPokemon = globalScene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); + globalScene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { promptRelease(); }); }, false); }, () => { - scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { - scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { addToParty(slotIndex); } else { @@ -632,7 +634,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po }); }); }, () => { - scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { removePokemon(); end(); }); @@ -647,7 +649,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po }; if (showCatchObtainMessage) { - scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true); + globalScene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true); } else { doPokemonCatchMenu(); } @@ -659,9 +661,9 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po * @param scene * @param pokeball */ -function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) { +function removePb(pokeball: Phaser.GameObjects.Sprite) { if (pokeball) { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 250, delay: 250, @@ -679,11 +681,11 @@ function removePb(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite) { * @param scene * @param pokemon */ -export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise { +export async function doPokemonFlee(pokemon: EnemyPokemon): Promise { await new Promise(resolve => { - scene.playSound("se/flee"); + globalScene.playSound("se/flee"); // Ease pokemon out - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, x: "+=16", y: "-=16", @@ -693,8 +695,8 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): scale: pokemon.getSpriteScale(), onComplete: () => { pokemon.setVisible(false); - scene.field.remove(pokemon, true); - showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) + globalScene.field.remove(pokemon, true); + showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) .then(() => { resolve(); }); @@ -708,10 +710,10 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): * @param scene * @param pokemon */ -export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise { +export function doPlayerFlee(pokemon: EnemyPokemon): Promise { return new Promise(resolve => { // Ease pokemon out - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, x: "+=16", y: "-=16", @@ -721,8 +723,8 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise scale: pokemon.getSpriteScale(), onComplete: () => { pokemon.setVisible(false); - scene.field.remove(pokemon, true); - showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) + globalScene.field.remove(pokemon, true); + showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false) .then(() => { resolve(); }); @@ -790,35 +792,35 @@ export function getGoldenBugNetSpecies(level: number): PokemonSpecies { * @param scene * @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation */ -export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) { - const currentBattle = scene.currentBattle; +export function getEncounterPokemonLevelForWave(levelAdditiveModifier: number = 0) { + const currentBattle = globalScene.currentBattle; const baseLevel = currentBattle.getLevelForWave(); // Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0); } -export async function addPokemonDataToDexAndValidateAchievements(scene: BattleScene, pokemon: PlayerPokemon) { +export async function addPokemonDataToDexAndValidateAchievements(pokemon: PlayerPokemon) { const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { - scene.validateAchv(achvs.HIDDEN_ABILITY); + globalScene.validateAchv(achvs.HIDDEN_ABILITY); } if (pokemon.species.subLegendary) { - scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_SUB_LEGENDARY); } if (pokemon.species.legendary) { - scene.validateAchv(achvs.CATCH_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_LEGENDARY); } if (pokemon.species.mythical) { - scene.validateAchv(achvs.CATCH_MYTHICAL); + globalScene.validateAchv(achvs.CATCH_MYTHICAL); } - scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); - return scene.gameData.setPokemonCaught(pokemon, true, false, false); + globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + return globalScene.gameData.setPokemonCaught(pokemon, true, false, false); } /** @@ -830,12 +832,12 @@ export async function addPokemonDataToDexAndValidateAchievements(scene: BattleSc * @param scene * @param invalidSelectionKey */ -export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scene: BattleScene, invalidSelectionKey: string): string | null { +export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, invalidSelectionKey: string): string | null { if (!pokemon.isAllowedInChallenge()) { return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null; } if (!pokemon.isAllowedInBattle()) { - return getEncounterText(scene, invalidSelectionKey) ?? null; + return getEncounterText(invalidSelectionKey) ?? null; } return null; diff --git a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts index 424ba15f811..d4ae3496b0c 100644 --- a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts +++ b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts @@ -1,8 +1,8 @@ -import BattleScene from "#app/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getFrameMs } from "#app/utils"; import { cos, sin } from "#app/field/anims"; import { getTypeRgb } from "#app/data/type"; +import { globalScene } from "#app/global-scene"; export enum TransformationScreenPosition { CENTER, @@ -17,10 +17,10 @@ export enum TransformationScreenPosition { * @param transformPokemon * @param screenPosition */ -export function doPokemonTransformationSequence(scene: BattleScene, previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) { +export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) { return new Promise(resolve => { - const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container; - const transformationBaseBg = scene.add.image(0, 0, "default_bg"); + const transformationContainer = globalScene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container; + const transformationBaseBg = globalScene.add.image(0, 0, "default_bg"); transformationBaseBg.setOrigin(0, 0); transformationBaseBg.setVisible(false); transformationContainer.add(transformationBaseBg); @@ -36,8 +36,8 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0; const getPokemonSprite = () => { - const ret = scene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub"); - ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + const ret = globalScene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub"); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); return ret; }; @@ -61,7 +61,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); sprite.setPipelineData("shiny", previousPokemon.shiny); @@ -94,14 +94,14 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke }); }); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonSprite, alpha: 1, ease: "Cubic.easeInOut", duration: 2000, onComplete: () => { - doSpiralUpward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset); - scene.tweens.addCounter({ + doSpiralUpward(transformationBaseBg, transformationContainer, xOffset, yOffset); + globalScene.tweens.addCounter({ from: 0, to: 1, duration: 1000, @@ -110,26 +110,26 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke }, onComplete: () => { pokemonSprite.setVisible(false); - scene.time.delayedCall(700, () => { - doArcDownward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset); - scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(700, () => { + doArcDownward(transformationBaseBg, transformationContainer, xOffset, yOffset); + globalScene.time.delayedCall(1000, () => { pokemonEvoTintSprite.setScale(0.25); pokemonEvoTintSprite.setVisible(true); - doCycle(scene, 1.5, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => { + doCycle(1.5, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => { pokemonEvoSprite.setVisible(true); - doCircleInward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset); + doCircleInward(transformationBaseBg, transformationContainer, xOffset, yOffset); - scene.time.delayedCall(900, () => { - scene.tweens.add({ + globalScene.time.delayedCall(900, () => { + globalScene.tweens.add({ targets: pokemonEvoTintSprite, alpha: 0, duration: 1500, delay: 150, easing: "Sine.easeIn", onComplete: () => { - scene.time.delayedCall(3000, () => { + globalScene.time.delayedCall(3000, () => { resolve(); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonEvoSprite, alpha: 0, duration: 2000, @@ -163,17 +163,17 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke * @param xOffset * @param yOffset */ -function doSpiralUpward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doSpiralUpward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { let f = 0; - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 64, duration: getFrameMs(1), onRepeat: () => { if (f < 64) { if (!(f & 7)) { for (let i = 0; i < 4; i++) { - doSpiralUpwardParticle(scene, (f & 120) * 2 + i * 64, transformationBaseBg, transformationContainer, xOffset, yOffset); + doSpiralUpwardParticle((f & 120) * 2 + i * 64, transformationBaseBg, transformationContainer, xOffset, yOffset); } } f++; @@ -190,17 +190,17 @@ function doSpiralUpward(scene: BattleScene, transformationBaseBg: Phaser.GameObj * @param xOffset * @param yOffset */ -function doArcDownward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doArcDownward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { let f = 0; - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 96, duration: getFrameMs(1), onRepeat: () => { if (f < 96) { if (f < 6) { for (let i = 0; i < 9; i++) { - doArcDownParticle(scene, i * 16, transformationBaseBg, transformationContainer, xOffset, yOffset); + doArcDownParticle(i * 16, transformationBaseBg, transformationContainer, xOffset, yOffset); } } f++; @@ -217,17 +217,17 @@ function doArcDownward(scene: BattleScene, transformationBaseBg: Phaser.GameObje * @param pokemonTintSprite * @param pokemonEvoTintSprite */ -function doCycle(scene: BattleScene, l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObjects.Sprite, pokemonEvoTintSprite: Phaser.GameObjects.Sprite): Promise { +function doCycle(l: number, lastCycle: number, pokemonTintSprite: Phaser.GameObjects.Sprite, pokemonEvoTintSprite: Phaser.GameObjects.Sprite): Promise { return new Promise(resolve => { const isLastCycle = l === lastCycle; - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonTintSprite, scale: 0.25, ease: "Cubic.easeInOut", duration: 500 / l, yoyo: !isLastCycle }); - scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonEvoTintSprite, scale: 1, ease: "Cubic.easeInOut", @@ -235,7 +235,7 @@ function doCycle(scene: BattleScene, l: number, lastCycle: number, pokemonTintSp yoyo: !isLastCycle, onComplete: () => { if (l < lastCycle) { - doCycle(scene, l + 0.5, lastCycle, pokemonTintSprite, pokemonEvoTintSprite).then(success => resolve(success)); + doCycle(l + 0.5, lastCycle, pokemonTintSprite, pokemonEvoTintSprite).then(success => resolve(success)); } else { pokemonTintSprite.setVisible(false); resolve(true); @@ -253,20 +253,20 @@ function doCycle(scene: BattleScene, l: number, lastCycle: number, pokemonTintSp * @param xOffset * @param yOffset */ -function doCircleInward(scene: BattleScene, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doCircleInward(transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { let f = 0; - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 48, duration: getFrameMs(1), onRepeat: () => { if (!f) { for (let i = 0; i < 16; i++) { - doCircleInwardParticle(scene, i * 16, 4, transformationBaseBg, transformationContainer, xOffset, yOffset); + doCircleInwardParticle(i * 16, 4, transformationBaseBg, transformationContainer, xOffset, yOffset); } } else if (f === 32) { for (let i = 0; i < 16; i++) { - doCircleInwardParticle(scene, i * 16, 8, transformationBaseBg, transformationContainer, xOffset, yOffset); + doCircleInwardParticle(i * 16, 8, transformationBaseBg, transformationContainer, xOffset, yOffset); } } f++; @@ -283,15 +283,15 @@ function doCircleInward(scene: BattleScene, transformationBaseBg: Phaser.GameObj * @param xOffset * @param yOffset */ -function doSpiralUpwardParticle(scene: BattleScene, trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doSpiralUpwardParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { const initialX = transformationBaseBg.displayWidth / 2 + xOffset; - const particle = scene.add.image(initialX, 0, "evo_sparkle"); + const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); transformationContainer.add(particle); let f = 0; let amp = 48; - const particleTimer = scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: getFrameMs(1), onRepeat: () => { @@ -328,16 +328,16 @@ function doSpiralUpwardParticle(scene: BattleScene, trigIndex: number, transform * @param xOffset * @param yOffset */ -function doArcDownParticle(scene: BattleScene, trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doArcDownParticle(trigIndex: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { const initialX = transformationBaseBg.displayWidth / 2 + xOffset; - const particle = scene.add.image(initialX, 0, "evo_sparkle"); + const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); particle.setScale(0.5); transformationContainer.add(particle); let f = 0; let amp = 8; - const particleTimer = scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: getFrameMs(1), onRepeat: () => { @@ -371,15 +371,15 @@ function doArcDownParticle(scene: BattleScene, trigIndex: number, transformation * @param xOffset * @param yOffset */ -function doCircleInwardParticle(scene: BattleScene, trigIndex: number, speed: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { +function doCircleInwardParticle(trigIndex: number, speed: number, transformationBaseBg: Phaser.GameObjects.Image, transformationContainer: Phaser.GameObjects.Container, xOffset: number, yOffset: number) { const initialX = transformationBaseBg.displayWidth / 2 + xOffset; const initialY = transformationBaseBg.displayHeight / 2 + yOffset; - const particle = scene.add.image(initialX, initialY, "evo_sparkle"); + const particle = globalScene.add.image(initialX, initialY, "evo_sparkle"); transformationContainer.add(particle); let amp = 120; - const particleTimer = scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: getFrameMs(1), onRepeat: () => { diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 4c9fc719a4d..049baf11f3d 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -1,7 +1,7 @@ +import { globalScene } from "#app/global-scene"; import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { NumberHolder } from "#app/utils"; import { PokeballType } from "#enums/pokeball"; -import BattleScene from "../battle-scene"; import i18next from "i18next"; export const MAX_PER_TYPE_POKEBALLS: integer = 99; @@ -85,18 +85,17 @@ export function getPokeballTintColor(type: PokeballType): number { /** * Gets the critical capture chance based on number of mons registered in Dex and modified {@link https://bulbapedia.bulbagarden.net/wiki/Catch_rate Catch rate} * Formula from {@link https://www.dragonflycave.com/mechanics/gen-vi-vii-capturing Dragonfly Cave Gen 6 Capture Mechanics page} - * @param scene {@linkcode BattleScene} current BattleScene * @param modifiedCatchRate the modified catch rate as calculated in {@linkcode AttemptCapturePhase} * @returns the chance of getting a critical capture, out of 256 */ -export function getCriticalCaptureChance(scene: BattleScene, modifiedCatchRate: number): number { - if (scene.gameMode.isFreshStartChallenge()) { +export function getCriticalCaptureChance(modifiedCatchRate: number): number { + if (globalScene.gameMode.isFreshStartChallenge()) { return 0; } - const dexCount = scene.gameData.getSpeciesCount(d => !!d.caughtAttr); + const dexCount = globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr); const catchingCharmMultiplier = new NumberHolder(1); - scene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); - const dexMultiplier = scene.gameMode.isDaily || dexCount > 800 ? 2.5 + globalScene.findModifier(m => m instanceof CriticalCatchChanceBoosterModifier)?.apply(catchingCharmMultiplier); + const dexMultiplier = globalScene.gameMode.isDaily || dexCount > 800 ? 2.5 : dexCount > 600 ? 2 : dexCount > 400 ? 1.5 : dexCount > 200 ? 1 @@ -105,7 +104,7 @@ export function getCriticalCaptureChance(scene: BattleScene, modifiedCatchRate: return Math.floor(catchingCharmMultiplier.value * dexMultiplier * Math.min(255, modifiedCatchRate) / 6); } -export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: number, callback: Function, isCritical: boolean = false) { +export function doPokeballBounceAnim(pokeball: Phaser.GameObjects.Sprite, y1: number, y2: number, baseBounceDuration: number, callback: Function, isCritical: boolean = false) { let bouncePower = 1; let bounceYOffset = y1; let bounceY = y2; @@ -116,13 +115,13 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb let critShakes = 4; const doBounce = () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, y: y2, duration: bouncePower * baseBounceDuration, ease: "Cubic.easeIn", onComplete: () => { - scene.playSound("se/pb_bounce_1", { volume: bouncePower }); + globalScene.playSound("se/pb_bounce_1", { volume: bouncePower }); bouncePower = bouncePower > 0.01 ? bouncePower * 0.5 : 0; @@ -130,7 +129,7 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb bounceYOffset = yd * bouncePower; bounceY = y2 - bounceYOffset; - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, y: bounceY, duration: bouncePower * baseBounceDuration, @@ -145,13 +144,13 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb }; const doCritShake = () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, x: x2, duration: 125, ease: "Linear", onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, x: x1, duration: 125, @@ -161,12 +160,12 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb if (critShakes > 0) { doCritShake(); } else { - scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, x: x0, duration: 60, ease: "Linear", - onComplete: () => scene.time.delayedCall(500, doBounce) + onComplete: () => globalScene.time.delayedCall(500, doBounce) }); } } @@ -176,7 +175,7 @@ export function doPokeballBounceAnim(scene: BattleScene, pokeball: Phaser.GameOb }; if (isCritical) { - scene.time.delayedCall(500, doCritShake); + globalScene.time.delayedCall(500, doCritShake); } else { doBounce(); } diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index d3cccbefdba..b1c3db47768 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,18 +1,19 @@ import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; -import Pokemon from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import { MoveCategory, allMoves } from "./move"; import { Type } from "#enums/type"; -import { Constructor, nil } from "#app/utils"; +import type { Constructor, nil } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { TimeOfDay } from "#enums/time-of-day"; +import type { TimeOfDay } from "#enums/time-of-day"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { WeatherType } from "#enums/weather-type"; import { Challenges } from "#app/enums/challenges"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { globalScene } from "#app/global-scene"; export enum FormChangeItem { NONE, @@ -259,7 +260,7 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { } canChange(pokemon: Pokemon): boolean { - return !!pokemon.scene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id && m.formChangeItem === this.item && m.active === this.active); + return !!globalScene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id && m.formChangeItem === this.item && m.active === this.active); } } @@ -272,7 +273,7 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger } canChange(pokemon: Pokemon): boolean { - return this.timesOfDay.indexOf(pokemon.scene.arena.getTimeOfDay()) > -1; + return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; } } @@ -335,7 +336,7 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { canChange(pokemon: Pokemon): boolean { - const command = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; + const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; return !!command?.move && this.movePredicate(command.move.move) === this.used; } } @@ -348,7 +349,7 @@ export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigg export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger { override canChange(pokemon: Pokemon): boolean { - if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { + if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { return false; } else { // Meloetta will not transform if it has the ability Sheer Force when using Relic Song @@ -369,7 +370,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { } canChange(pokemon: Pokemon): boolean { - return this.formKey === pokemon.species.forms[pokemon.scene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)].formKey; + return this.formKey === pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)].formKey; } } @@ -393,7 +394,7 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { * @returns `true` if the Pokémon can change forms, `false` otherwise */ canChange(pokemon: Pokemon): boolean { - return !!pokemon.scene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id && m.teraType === this.teraType); + return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id && m.teraType === this.teraType); } } @@ -404,7 +405,7 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { */ export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { canChange(pokemon: Pokemon): boolean { - return !!pokemon.scene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id); + return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id); } } @@ -432,8 +433,8 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { * @returns `true` if the Pokemon can change forms, `false` otherwise */ canChange(pokemon: Pokemon): boolean { - const currentWeather = pokemon.scene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene); + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; return !isAbilitySuppressed && !isWeatherSuppressed && (pokemon.hasAbility(this.ability) && this.weathers.includes(currentWeather)); @@ -466,8 +467,8 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange */ canChange(pokemon: Pokemon): boolean { if (pokemon.hasAbility(this.ability, false, true)) { - const currentWeather = pokemon.scene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene); + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; const summonDataAbility = pokemon.summonData.ability; const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== Abilities.NONE; @@ -510,7 +511,7 @@ export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: Specie * @returns A {@linkcode SpeciesFormChangeCondition} checking if that species is registered as caught */ function getSpeciesDependentFormChangeCondition(species: Species): SpeciesFormChangeCondition { - return new SpeciesFormChangeCondition(p => !!p.scene.gameData.dexData[species].caughtAttr); + return new SpeciesFormChangeCondition(p => !!globalScene.gameData.dexData[species].caughtAttr); } interface PokemonFormChanges { @@ -770,8 +771,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.KYUREM, "", "white", new SpeciesFormChangeItemTrigger(FormChangeItem.LIGHT_STONE), false, getSpeciesDependentFormChangeCondition(Species.RESHIRAM)) ], [Species.KELDEO]: [ - new SpeciesFormChange(Species.KELDEO, "ordinary", "resolute", new SpeciesFormChangeMoveLearnedTrigger(Moves.SECRET_SWORD), false, new SpeciesFormChangeCondition((p) => p.scene.gameMode.isDaily !== true)), - new SpeciesFormChange(Species.KELDEO, "resolute", "ordinary", new SpeciesFormChangeMoveLearnedTrigger(Moves.SECRET_SWORD, false), false, new SpeciesFormChangeCondition((p) => p.scene.gameMode.isDaily !== true)) + new SpeciesFormChange(Species.KELDEO, "ordinary", "resolute", new SpeciesFormChangeMoveLearnedTrigger(Moves.SECRET_SWORD), false, new SpeciesFormChangeCondition(() => globalScene.gameMode.isDaily !== true)), + new SpeciesFormChange(Species.KELDEO, "resolute", "ordinary", new SpeciesFormChangeMoveLearnedTrigger(Moves.SECRET_SWORD, false), false, new SpeciesFormChangeCondition(() => globalScene.gameMode.isDaily !== true)) ], [Species.MELOETTA]: [ new SpeciesFormChange(Species.MELOETTA, "aria", "pirouette", new MeloettaFormChangePostMoveTrigger(Moves.RELIC_SONG), true), diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 09788e353cf..84486b30372 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1,21 +1,25 @@ -import { Localizable } from "#app/interfaces/locales"; +import type { Localizable } from "#app/interfaces/locales"; import { Abilities } from "#enums/abilities"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import i18next from "i18next"; -import BattleScene, { AnySound } from "#app/battle-scene"; -import { GameMode } from "#app/game-mode"; -import { StarterMoveset } from "#app/system/game-data"; +import type { AnySound } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { GameMode } from "#app/game-mode"; +import type { StarterMoveset } from "#app/system/game-data"; import * as Utils from "#app/utils"; import { uncatchableSpecies } from "#app/data/balance/biomes"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate } from "#app/data/exp"; -import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; +import type { EvolutionLevel } from "#app/data/balance/pokemon-evolutions"; +import { SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { Type } from "#enums/type"; -import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; -import { Stat } from "#enums/stat"; -import { Variant, VariantSet, variantData } from "#app/data/variant"; +import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; +import type { Stat } from "#enums/stat"; +import type { Variant, VariantSet } from "#app/data/variant"; +import { variantData } from "#app/data/variant"; import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -490,34 +494,34 @@ export abstract class PokemonSpeciesForm { return true; } - loadAssets(scene: BattleScene, female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean): Promise { + loadAssets(female: boolean, formIndex?: number, shiny?: boolean, variant?: Variant, startLoad?: boolean): Promise { return new Promise(resolve => { const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant); - scene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant)); - scene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`); - scene.load.once(Phaser.Loader.Events.COMPLETE, () => { + globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant)); + globalScene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`); + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { const originalWarn = console.warn; // Ignore warnings for missing frames, because there will be a lot console.warn = () => {}; - const frameNames = scene.anims.generateFrameNames(spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 400 }); + const frameNames = globalScene.anims.generateFrameNames(spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 400 }); console.warn = originalWarn; - if (!(scene.anims.exists(spriteKey))) { - scene.anims.create({ + if (!(globalScene.anims.exists(spriteKey))) { + globalScene.anims.create({ key: this.getSpriteKey(female, formIndex, shiny, variant), frames: frameNames, frameRate: 10, repeat: -1 }); } else { - scene.anims.get(spriteKey).frameRate = 10; + globalScene.anims.get(spriteKey).frameRate = 10; } const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); - scene.loadPokemonVariantAssets(spriteKey, spritePath, variant); + globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant); resolve(); }); if (startLoad) { - if (!scene.load.isLoading()) { - scene.load.start(); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } } else { resolve(); @@ -525,21 +529,21 @@ export abstract class PokemonSpeciesForm { }); } - cry(scene: BattleScene, soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound { + cry(soundConfig?: Phaser.Types.Sound.SoundConfig, ignorePlay?: boolean): AnySound { const cryKey = this.getCryKey(this.formIndex); - let cry: AnySound | null = scene.sound.get(cryKey) as AnySound; + let cry: AnySound | null = globalScene.sound.get(cryKey) as AnySound; if (cry?.pendingRemove) { cry = null; } - cry = scene.playSound(cry ?? cryKey, soundConfig); + cry = globalScene.playSound(cry ?? cryKey, soundConfig); if (ignorePlay) { cry.stop(); } return cry; } - generateCandyColors(scene: BattleScene): number[][] { - const sourceTexture = scene.textures.get(this.getSpriteKey(false)); + generateCandyColors(): number[][] { + const sourceTexture = globalScene.textures.get(this.getSpriteKey(false)); const sourceFrame = sourceTexture.frames[sourceTexture.firstFrame]; const sourceImage = sourceTexture.getSourceImage() as HTMLImageElement; @@ -582,7 +586,7 @@ export abstract class PokemonSpeciesForm { const originalRandom = Math.random; Math.random = () => Phaser.Math.RND.realInRange(0, 1); - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { paletteColors = QuantizerCelebi.quantize(pixelColors, 2); }, 0, "This result should not vary"); @@ -959,14 +963,13 @@ export const noStarterFormKeys: string[] = [ /** * Method to get the daily list of starters with Pokerus. -* @param scene {@linkcode BattleScene} used as part of RNG * @returns A list of starters with Pokerus */ -export function getPokerusStarters(scene: BattleScene): PokemonSpecies[] { +export function getPokerusStarters(): PokemonSpecies[] { const pokerusStarters: PokemonSpecies[] = []; const date = new Date(); date.setUTCHours(0, 0, 0, 0); - scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { while (pokerusStarters.length < POKERUS_STARTER_COUNT) { const randomSpeciesId = parseInt(Utils.randSeedItem(Object.keys(speciesStarterCosts)), 10); const species = getPokemonSpecies(randomSpeciesId); diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index 6b4e1d546df..3c085bdd099 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -1,6 +1,7 @@ import { randIntRange } from "#app/utils"; import { StatusEffect } from "#enums/status-effect"; -import i18next, { ParseKeys } from "i18next"; +import type { ParseKeys } from "i18next"; +import i18next from "i18next"; export class Status { public effect: StatusEffect; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 6ba9acfd166..da315a14a86 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -1,8 +1,8 @@ -import Pokemon from "../field/pokemon"; -import Move from "./move"; +import type Pokemon from "../field/pokemon"; +import type Move from "./move"; import { Type } from "#enums/type"; import { ProtectAttr } from "./move"; -import { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#app/battle"; import i18next from "i18next"; export enum TerrainType { diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 707f6c5fdb9..266dc342402 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -1,14 +1,19 @@ -import BattleScene, { startingWave } from "#app/battle-scene"; -import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; +import { startingWave } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import * as Utils from "#app/utils"; import { PokeballType } from "#enums/pokeball"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import PokemonSpecies, { getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; +import type { PokemonSpeciesFilter } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { tmSpecies } from "#app/data/balance/tms"; import { Type } from "#enums/type"; import { doubleBattleDialogue } from "#app/data/dialogue"; -import { PersistentModifier } from "#app/modifier/modifier"; +import type { PersistentModifier } from "#app/modifier/modifier"; import { TrainerVariant } from "#app/field/trainer"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import i18next from "i18next"; @@ -169,8 +174,8 @@ export const trainerPartyTemplates = { RIVAL_6: new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), new TrainerPartyTemplate(1, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true), new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)) }; -type PartyTemplateFunc = (scene: BattleScene) => TrainerPartyTemplate; -type PartyMemberFunc = (scene: BattleScene, level: integer, strength: PartyMemberStrength) => EnemyPokemon; +type PartyTemplateFunc = () => TrainerPartyTemplate; +type PartyMemberFunc = (level: integer, strength: PartyMemberStrength) => EnemyPokemon; type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; export interface PartyMemberFuncs { @@ -842,7 +847,7 @@ export class TrainerConfig { this.setBattleBgm("battle_unova_gym"); this.setVictoryBgm("victory_gym"); this.setGenModifiersFunc(party => { - const waveIndex = party[0].scene.currentBattle.waveIndex; + const waveIndex = globalScene.currentBattle.waveIndex; return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined); }); @@ -1011,28 +1016,28 @@ export class TrainerConfig { return ret; } - loadAssets(scene: BattleScene, variant: TrainerVariant): Promise { + loadAssets(variant: TrainerVariant): Promise { return new Promise(resolve => { const isDouble = variant === TrainerVariant.DOUBLE; const trainerKey = this.getSpriteKey(variant === TrainerVariant.FEMALE, false); const partnerTrainerKey = this.getSpriteKey(true, true); - scene.loadAtlas(trainerKey, "trainer"); + globalScene.loadAtlas(trainerKey, "trainer"); if (isDouble) { - scene.loadAtlas(partnerTrainerKey, "trainer"); + globalScene.loadAtlas(partnerTrainerKey, "trainer"); } - scene.load.once(Phaser.Loader.Events.COMPLETE, () => { + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { const originalWarn = console.warn; // Ignore warnings for missing frames, because there will be a lot console.warn = () => { }; - const frameNames = scene.anims.generateFrameNames(trainerKey, { + const frameNames = globalScene.anims.generateFrameNames(trainerKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }); const partnerFrameNames = isDouble - ? scene.anims.generateFrameNames(partnerTrainerKey, { + ? globalScene.anims.generateFrameNames(partnerTrainerKey, { zeroPad: 4, suffix: ".png", start: 1, @@ -1040,16 +1045,16 @@ export class TrainerConfig { }) : ""; console.warn = originalWarn; - if (!(scene.anims.exists(trainerKey))) { - scene.anims.create({ + if (!(globalScene.anims.exists(trainerKey))) { + globalScene.anims.create({ key: trainerKey, frames: frameNames, frameRate: 24, repeat: -1 }); } - if (isDouble && !(scene.anims.exists(partnerTrainerKey))) { - scene.anims.create({ + if (isDouble && !(globalScene.anims.exists(partnerTrainerKey))) { + globalScene.anims.create({ key: partnerTrainerKey, frames: partnerFrameNames, frameRate: 24, @@ -1058,8 +1063,8 @@ export class TrainerConfig { } resolve(); }); - if (!scene.load.isLoading()) { - scene.load.start(); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } }); } @@ -1133,11 +1138,10 @@ interface TrainerConfigs { /** * The function to get variable strength grunts - * @param scene the singleton scene being passed in * @returns the correct TrainerPartyTemplate */ -function getEvilGruntPartyTemplate(scene: BattleScene): TrainerPartyTemplate { - const waveIndex = scene.currentBattle?.waveIndex; +function getEvilGruntPartyTemplate(): TrainerPartyTemplate { + const waveIndex = globalScene.currentBattle?.waveIndex; if (waveIndex < 40) { return trainerPartyTemplates.TWO_AVG; } else if (waveIndex < 63) { @@ -1151,32 +1155,32 @@ function getEvilGruntPartyTemplate(scene: BattleScene): TrainerPartyTemplate { } } -function getWavePartyTemplate(scene: BattleScene, ...templates: TrainerPartyTemplate[]) { - return templates[Math.min(Math.max(Math.ceil((scene.gameMode.getWaveForDifficulty(scene.currentBattle?.waveIndex || startingWave, true) - 20) / 30), 0), templates.length - 1)]; +function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) { + return templates[Math.min(Math.max(Math.ceil((globalScene.gameMode.getWaveForDifficulty(globalScene.currentBattle?.waveIndex || startingWave, true) - 20) / 30), 0), templates.length - 1)]; } -function getGymLeaderPartyTemplate(scene: BattleScene) { - return getWavePartyTemplate(scene, trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5); +function getGymLeaderPartyTemplate() { + return getWavePartyTemplate(trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5); } /** * Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength. - * Then adds Pokemon to scene. + * Then adds Pokemon to globalScene. * @param speciesPool * @param trainerSlot * @param ignoreEvolution * @param postProcess */ export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) { - return (scene: BattleScene, level: number, strength: PartyMemberStrength) => { + return (level: number, strength: PartyMemberStrength) => { let species = Utils.randSeedItem(speciesPool); - if (scene.gameMode.isClassic && scene.currentBattle.waveIndex === 20) { + if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 20) { ignoreEvolution = true; } if (!ignoreEvolution) { - species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex); + species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, globalScene.currentBattle.waveIndex); } - return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess); + return globalScene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess); }; } @@ -1192,12 +1196,12 @@ function getSpeciesFilterRandomPartyMemberFunc( return (allowLegendaries || notLegendary) && !species.isTrainerForbidden() && originalSpeciesFilter(species); }; - return (scene: BattleScene, level: number, strength: PartyMemberStrength) => { - const waveIndex = scene.currentBattle.waveIndex; - const species = getPokemonSpecies(scene.randomSpecies(waveIndex, level, false, speciesFilter) + return (level: number, strength: PartyMemberStrength) => { + const waveIndex = globalScene.currentBattle.waveIndex; + const species = getPokemonSpecies(globalScene.randomSpecies(waveIndex, level, false, speciesFilter) .getTrainerSpeciesForLevel(level, true, strength, waveIndex)); - return scene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess); + return globalScene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess); }; } @@ -1356,7 +1360,7 @@ export const signatureSpecies: SignatureSpecies = { export const trainerConfigs: TrainerConfigs = { [TrainerType.UNKNOWN]: new TrainerConfig(0).setHasGenders(), [TrainerType.ACE_TRAINER]: new TrainerConfig(++t).setHasGenders("Ace Trainer Female").setHasDouble("Ace Duo").setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) - .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.THREE_WEAK_BALANCED, trainerPartyTemplates.FOUR_WEAK_BALANCED, trainerPartyTemplates.FIVE_WEAK_BALANCED, trainerPartyTemplates.SIX_WEAK_BALANCED)), + .setPartyTemplateFunc(() => getWavePartyTemplate(trainerPartyTemplates.THREE_WEAK_BALANCED, trainerPartyTemplates.FOUR_WEAK_BALANCED, trainerPartyTemplates.FIVE_WEAK_BALANCED, trainerPartyTemplates.SIX_WEAK_BALANCED)), [TrainerType.ARTIST]: new TrainerConfig(++t).setEncounterBgm(TrainerType.RICH).setPartyTemplates(trainerPartyTemplates.ONE_STRONG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.THREE_AVG) .setSpeciesPools([ Species.SMEARGLE ]), [TrainerType.BACKERS]: new TrainerConfig(++t).setHasGenders("Backers").setDoubleOnly().setEncounterBgm(TrainerType.CYCLIST), @@ -1381,7 +1385,7 @@ export const trainerConfigs: TrainerConfigs = { [TrainerPoolTier.ULTRA_RARE]: [ Species.KUBFU ] }), [TrainerType.BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(1.325).setEncounterBgm(TrainerType.POKEFAN).setHasGenders("Breeder Female").setHasDouble("Breeders") - .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.FOUR_WEAKER, trainerPartyTemplates.FIVE_WEAKER, trainerPartyTemplates.SIX_WEAKER)) + .setPartyTemplateFunc(() => getWavePartyTemplate(trainerPartyTemplates.FOUR_WEAKER, trainerPartyTemplates.FIVE_WEAKER, trainerPartyTemplates.SIX_WEAKER)) .setSpeciesFilter(s => s.baseTotal < 450), [TrainerType.CLERK]: new TrainerConfig(++t).setHasGenders("Clerk Female").setHasDouble("Colleagues").setEncounterBgm(TrainerType.CLERK) .setPartyTemplates(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.THREE_WEAK, trainerPartyTemplates.ONE_AVG, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_WEAK_ONE_AVG) @@ -1500,7 +1504,7 @@ export const trainerConfigs: TrainerConfigs = { }), [TrainerType.SWIMMER]: new TrainerConfig(++t).setMoneyMultiplier(1.3).setEncounterBgm(TrainerType.PARASOL_LADY).setHasGenders("Swimmer Female").setHasDouble("Swimmers").setSpecialtyTypes(Type.WATER).setSpeciesFilter(s => s.isOfType(Type.WATER)), [TrainerType.TWINS]: new TrainerConfig(++t).setDoubleOnly().setMoneyMultiplier(0.65).setUseSameSeedForAllMembers() - .setPartyTemplateFunc(scene => getWavePartyTemplate(scene, trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_STRONG)) + .setPartyTemplateFunc(() => getWavePartyTemplate(trainerPartyTemplates.TWO_WEAK, trainerPartyTemplates.TWO_AVG, trainerPartyTemplates.TWO_STRONG)) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PLUSLE, Species.VOLBEAT, Species.PACHIRISU, Species.SILCOON, Species.METAPOD, Species.IGGLYBUFF, Species.PETILIL, Species.EEVEE ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MINUN, Species.ILLUMISE, Species.EMOLGA, Species.CASCOON, Species.KAKUNA, Species.CLEFFA, Species.COTTONEE, Species.EEVEE ], TrainerSlot.TRAINER_PARTNER)) .setEncounterBgm(TrainerType.TWINS), @@ -1516,115 +1520,115 @@ export const trainerConfigs: TrainerConfigs = { .setSpeciesPools( [ Species.CATERPIE, Species.WEEDLE, Species.RATTATA, Species.SENTRET, Species.POOCHYENA, Species.ZIGZAGOON, Species.WURMPLE, Species.BIDOOF, Species.PATRAT, Species.LILLIPUP ] ), - [TrainerType.ROCKET_GRUNT]: new TrainerConfig(++t).setHasGenders("Rocket Grunt Female").setHasDouble("Rocket Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ROCKET_GRUNT]: new TrainerConfig(++t).setHasGenders("Rocket Grunt Female").setHasDouble("Rocket Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.WEEDLE, Species.RATTATA, Species.EKANS, Species.SANDSHREW, Species.ZUBAT, Species.GEODUDE, Species.KOFFING, Species.GRIMER, Species.ODDISH, Species.SLOWPOKE ], [TrainerPoolTier.UNCOMMON]: [ Species.GYARADOS, Species.LICKITUNG, Species.TAUROS, Species.MANKEY, Species.SCYTHER, Species.ELEKID, Species.MAGBY, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.MAGNEMITE ], [TrainerPoolTier.RARE]: [ Species.PORYGON, Species.ALOLA_RATTATA, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO ], [TrainerPoolTier.SUPER_RARE]: [ Species.DRATINI, Species.LARVITAR ] }), - [TrainerType.ARCHER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.HOUNDOOM ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.ARIANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin_female", "rocket", [ Species.ARBOK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.PROTON]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.CROBAT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.PETREL]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.WEEZING ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.MAGMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Magma Grunt Female").setHasDouble("Magma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ARCHER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.HOUNDOOM ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.ARIANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin_female", "rocket", [ Species.ARBOK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.PROTON]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.CROBAT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.PETREL]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("rocket_admin", "rocket", [ Species.WEEZING ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_rocket_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.MAGMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Magma Grunt Female").setHasDouble("Magma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ], [TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ], [TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ], [TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ] }), - [TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.COURTNEY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin_female", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.AQUA_GRUNT]: new TrainerConfig(++t).setHasGenders("Aqua Grunt Female").setHasDouble("Aqua Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.COURTNEY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin_female", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.AQUA_GRUNT]: new TrainerConfig(++t).setHasGenders("Aqua Grunt Female").setHasDouble("Aqua Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL, Species.REMORAID, Species.QWILFISH, Species.BARBOACH ], [TrainerPoolTier.UNCOMMON]: [ Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.CLOBBOPUS, Species.HORSEA ], [TrainerPoolTier.RARE]: [ Species.MANTYKE, Species.DHELMISE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.PALDEA_WOOPER, Species.SKRELP ], [TrainerPoolTier.SUPER_RARE]: [ Species.DONDOZO, Species.BASCULEGION ] }), - [TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [ Species.SHARPEDO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.SHELLY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin_female", "aqua", [ Species.SHARPEDO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.GALACTIC_GRUNT]: new TrainerConfig(++t).setHasGenders("Galactic Grunt Female").setHasDouble("Galactic Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [ Species.SHARPEDO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.SHELLY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin_female", "aqua", [ Species.SHARPEDO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.GALACTIC_GRUNT]: new TrainerConfig(++t).setHasGenders("Galactic Grunt Female").setHasDouble("Galactic Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.GLAMEOW, Species.STUNKY, Species.CROAGUNK, Species.SHINX, Species.WURMPLE, Species.BRONZOR, Species.DRIFLOON, Species.BURMY, Species.CARNIVINE ], [TrainerPoolTier.UNCOMMON]: [ Species.LICKITUNG, Species.RHYHORN, Species.TANGELA, Species.ZUBAT, Species.YANMA, Species.SKORUPI, Species.GLIGAR, Species.SWINUB ], [TrainerPoolTier.RARE]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.ELEKID, Species.MAGBY, Species.DUSKULL ], [TrainerPoolTier.SUPER_RARE]: [ Species.ROTOM, Species.SPIRITOMB, Species.HISUI_SNEASEL ] }), - [TrainerType.JUPITER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [ Species.SKUNTANK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.MARS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [ Species.PURUGLY ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.SATURN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander", "galactic", [ Species.TOXICROAK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.JUPITER]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [ Species.SKUNTANK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.MARS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander_female", "galactic", [ Species.PURUGLY ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.SATURN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("galactic_commander", "galactic", [ Species.TOXICROAK ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_galactic_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH, Species.TYMPOLE ], [TrainerPoolTier.UNCOMMON]: [ Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.JOLTIK, Species.CUBCHOO, Species.KLINK ], [TrainerPoolTier.RARE]: [ Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT ], [TrainerPoolTier.SUPER_RARE]: [ Species.DRUDDIGON, Species.HISUI_ZORUA, Species.AXEW, Species.DEINO ] }), - [TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [ Species.CRYOGONAL ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.ROOD]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [ Species.SWOOBAT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [ Species.CRYOGONAL ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.ROOD]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [ Species.SWOOBAT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ], [TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP, Species.FOONGUS ], [TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ], [TrainerPoolTier.SUPER_RARE]: [ Species.NOIBAT, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ] }), - [TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.AETHER_GRUNT]: new TrainerConfig(++t).setHasGenders("Aether Grunt Female").setHasDouble("Aether Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.AETHER_GRUNT]: new TrainerConfig(++t).setHasGenders("Aether Grunt Female").setHasDouble("Aether Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.PIKIPEK, Species.ROCKRUFF, Species.ALOLA_DIGLETT, Species.ALOLA_EXEGGUTOR, Species.YUNGOOS, Species.CORSOLA, Species.ALOLA_GEODUDE, Species.ALOLA_RAICHU, Species.BOUNSWEET, Species.LILLIPUP, Species.KOMALA, Species.MORELULL, Species.COMFEY, Species.TOGEDEMARU ], [TrainerPoolTier.UNCOMMON]: [ Species.POLIWAG, Species.STUFFUL, Species.ORANGURU, Species.PASSIMIAN, Species.BRUXISH, Species.MINIOR, Species.WISHIWASHI, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.CRABRAWLER, Species.CUTIEFLY, Species.ORICORIO, Species.MUDBRAY, Species.PYUKUMUKU, Species.ALOLA_MAROWAK ], [TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.TURTONATOR, Species.MIMIKYU, Species.MAGNEMITE, Species.DRAMPA ], [TrainerPoolTier.SUPER_RARE]: [ Species.JANGMO_O, Species.PORYGON ] }), - [TrainerType.FABA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aether_admin", "aether", [ Species.HYPNO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.SKULL_GRUNT]: new TrainerConfig(++t).setHasGenders("Skull Grunt Female").setHasDouble("Skull Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.FABA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aether_admin", "aether", [ Species.HYPNO ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.SKULL_GRUNT]: new TrainerConfig(++t).setHasGenders("Skull Grunt Female").setHasDouble("Skull Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.SALANDIT, Species.ALOLA_RATTATA, Species.EKANS, Species.ALOLA_MEOWTH, Species.SCRAGGY, Species.KOFFING, Species.ALOLA_GRIMER, Species.MAREANIE, Species.SPINARAK, Species.TRUBBISH, Species.DROWZEE ], [TrainerPoolTier.UNCOMMON]: [ Species.FOMANTIS, Species.SABLEYE, Species.SANDILE, Species.HOUNDOUR, Species.ALOLA_MAROWAK, Species.GASTLY, Species.PANCHAM, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY ], [TrainerPoolTier.RARE]: [ Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE ], [TrainerPoolTier.SUPER_RARE]: [ Species.GRUBBIN, Species.DEWPIDER ] }), - [TrainerType.PLUMERIA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("skull_admin", "skull", [ Species.SALAZZLE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.MACRO_GRUNT]: new TrainerConfig(++t).setHasGenders("Macro Grunt Female").setHasDouble("Macro Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_macro_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.PLUMERIA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("skull_admin", "skull", [ Species.SALAZZLE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.MACRO_GRUNT]: new TrainerConfig(++t).setHasGenders("Macro Grunt Female").setHasDouble("Macro Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_macro_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.CUFANT, Species.GALAR_MEOWTH, Species.KLINK, Species.ROOKIDEE, Species.CRAMORANT, Species.GALAR_ZIGZAGOON, Species.SKWOVET, Species.STEELIX, Species.MAWILE, Species.FERROSEED ], [TrainerPoolTier.UNCOMMON]: [ Species.DRILBUR, Species.MAGNEMITE, Species.HATENNA, Species.ARROKUDA, Species.APPLIN, Species.GALAR_PONYTA, Species.GALAR_YAMASK, Species.SINISTEA, Species.RIOLU ], [TrainerPoolTier.RARE]: [ Species.FALINKS, Species.BELDUM, Species.GALAR_FARFETCHD, Species.GALAR_MR_MIME, Species.HONEDGE, Species.SCIZOR, Species.GALAR_DARUMAKA ], [TrainerPoolTier.SUPER_RARE]: [ Species.DURALUDON, Species.DREEPY ] }), - [TrainerType.OLEANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("macro_admin", "macro", [ Species.GARBODOR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_oleana").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)), - [TrainerType.STAR_GRUNT]: new TrainerConfig(++t).setHasGenders("Star Grunt Female").setHasDouble("Star Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.OLEANA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("macro_admin", "macro", [ Species.GARBODOR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_oleana").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()), + [TrainerType.STAR_GRUNT]: new TrainerConfig(++t).setHasGenders("Star Grunt Female").setHasDouble("Star Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setSpeciesPools({ [TrainerPoolTier.COMMON]: [ Species.DUNSPARCE, Species.HOUNDOUR, Species.AZURILL, Species.GULPIN, Species.FOONGUS, Species.FLETCHLING, Species.LITLEO, Species.FLABEBE, Species.CRABRAWLER, Species.NYMBLE, Species.PAWMI, Species.FIDOUGH, Species.SQUAWKABILLY, Species.MASCHIFF, Species.SHROODLE, Species.KLAWF, Species.WIGLETT, Species.PALDEA_WOOPER ], [TrainerPoolTier.UNCOMMON]: [ Species.KOFFING, Species.EEVEE, Species.GIRAFARIG, Species.RALTS, Species.TORKOAL, Species.SEVIPER, Species.SCRAGGY, Species.ZORUA, Species.MIMIKYU, Species.IMPIDIMP, Species.FALINKS, Species.CAPSAKID, Species.TINKATINK, Species.BOMBIRDIER, Species.CYCLIZAR, Species.FLAMIGO, Species.PALDEA_TAUROS ], [TrainerPoolTier.RARE]: [ Species.MANKEY, Species.PAWNIARD, Species.CHARCADET, Species.FLITTLE, Species.VAROOM, Species.ORTHWORM ], [TrainerPoolTier.SUPER_RARE]: [ Species.DONDOZO, Species.GIMMIGHOUL ] }), - [TrainerType.GIACOMO]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_1", [ Species.KINGAMBIT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.GIACOMO]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_1", [ Species.KINGAMBIT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { p.formIndex = 1; // Segin Starmobile p.moveset = [ new PokemonMove(Moves.WICKED_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; })), - [TrainerType.MELA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_2", [ Species.ARMAROUGE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.MELA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_2", [ Species.ARMAROUGE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { p.formIndex = 2; // Schedar Starmobile p.moveset = [ new PokemonMove(Moves.BLAZING_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; })), - [TrainerType.ATTICUS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_3", [ Species.REVAVROOM ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ATTICUS]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_3", [ Species.REVAVROOM ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { p.formIndex = 3; // Navi Starmobile p.moveset = [ new PokemonMove(Moves.NOXIOUS_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; })), - [TrainerType.ORTEGA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_4", [ Species.DACHSBUN ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ORTEGA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_4", [ Species.DACHSBUN ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { p.formIndex = 4; // Ruchbah Starmobile p.moveset = [ new PokemonMove(Moves.MAGICAL_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; })), - [TrainerType.ERI]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_5", [ Species.ANNIHILAPE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)) + [TrainerType.ERI]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("star_admin", "star_5", [ Species.ANNIHILAPE ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_star_admin").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(() => getEvilGruntPartyTemplate()) .setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => { p.formIndex = 5; // Caph Starmobile p.moveset = [ new PokemonMove(Moves.COMBAT_TORQUE), new PokemonMove(Moves.SPIN_OUT), new PokemonMove(Moves.SHIFT_GEAR), new PokemonMove(Moves.HIGH_HORSEPOWER) ]; diff --git a/src/data/weather.ts b/src/data/weather.ts index 24434206bcd..d971f726543 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -1,14 +1,16 @@ import { Biome } from "#enums/biome"; import { WeatherType } from "#enums/weather-type"; import { getPokemonNameWithAffix } from "../messages"; -import Pokemon from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; import { Type } from "#enums/type"; -import Move, { AttackMove } from "./move"; +import type Move from "./move"; +import { AttackMove } from "./move"; import * as Utils from "../utils"; -import BattleScene from "../battle-scene"; import { SuppressWeatherEffectAbAttr } from "./ability"; import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; +import type { Arena } from "#app/field/arena"; export class Weather { public weatherType: WeatherType; @@ -100,8 +102,8 @@ export class Weather { return false; } - isEffectSuppressed(scene: BattleScene): boolean { - const field = scene.getField(true); + isEffectSuppressed(): boolean { + const field = globalScene.getField(true); for (const pokemon of field) { let suppressWeatherEffectAbAttr: SuppressWeatherEffectAbAttr | null = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr)[0]; @@ -247,7 +249,7 @@ export interface WeatherPoolEntry { weight: integer; } -export function getRandomWeatherType(arena: any /* Importing from arena causes a circular dependency */): WeatherType { +export function getRandomWeatherType(arena: Arena): WeatherType { let weatherPool: WeatherPoolEntry[] = []; const hasSun = arena.getTimeOfDay() < 2; switch (arena.biomeType) { @@ -373,8 +375,8 @@ export function getRandomWeatherType(arena: any /* Importing from arena causes a break; } - if (arena.biomeType === Biome.TOWN && arena.scene.eventManager.isEventActive() && arena.scene.eventManager.activeEvent()?.weather?.length > 0) { - arena.scene.eventManager.activeEvent().weather.map(w => weatherPool.push(w)); + if (arena.biomeType === Biome.TOWN && globalScene.eventManager.isEventActive() && (globalScene.eventManager.activeEvent()?.weather?.length ?? 0) > 0) { + globalScene.eventManager.activeEvent()?.weather?.map(w => weatherPool.push(w)); } if (weatherPool.length > 1) { diff --git a/src/events/arena.ts b/src/events/arena.ts index b1126e5c03d..660a113f96b 100644 --- a/src/events/arena.ts +++ b/src/events/arena.ts @@ -1,7 +1,7 @@ -import { ArenaTagSide } from "#app/data/arena-tag"; -import { ArenaTagType } from "#enums/arena-tag-type"; -import { TerrainType } from "#app/data/terrain"; -import { WeatherType } from "#enums/weather-type"; +import type { ArenaTagSide } from "#app/data/arena-tag"; +import type { ArenaTagType } from "#enums/arena-tag-type"; +import type { TerrainType } from "#app/data/terrain"; +import type { WeatherType } from "#enums/weather-type"; /** Alias for all {@linkcode ArenaEvent} type strings */ export enum ArenaEventType { diff --git a/src/events/battle-scene.ts b/src/events/battle-scene.ts index b2e4461f8d0..548666c96de 100644 --- a/src/events/battle-scene.ts +++ b/src/events/battle-scene.ts @@ -1,5 +1,5 @@ -import Move from "../data/move"; -import { BerryModifier } from "../modifier/modifier"; +import type Move from "../data/move"; +import type { BerryModifier } from "../modifier/modifier"; /** Alias for all {@linkcode BattleScene} events */ export enum BattleSceneEventType { diff --git a/src/field/anims.ts b/src/field/anims.ts index 10198c29005..f96427326b5 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -1,32 +1,32 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { PokeballType } from "#enums/pokeball"; -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { getFrameMs, randGauss } from "#app/utils"; -export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { +export function addPokeballOpenParticles(x: number, y: number, pokeballType: PokeballType): void { switch (pokeballType) { case PokeballType.POKEBALL: - doDefaultPbOpenParticles(scene, x, y, 48); + doDefaultPbOpenParticles(x, y, 48); break; case PokeballType.GREAT_BALL: - doDefaultPbOpenParticles(scene, x, y, 96); + doDefaultPbOpenParticles(x, y, 96); break; case PokeballType.ULTRA_BALL: - doUbOpenParticles(scene, x, y, 8); + doUbOpenParticles(x, y, 8); break; case PokeballType.ROGUE_BALL: - doUbOpenParticles(scene, x, y, 10); + doUbOpenParticles(x, y, 10); break; case PokeballType.MASTER_BALL: - doMbOpenParticles(scene, x, y); + doMbOpenParticles(x, y); break; } } -function doDefaultPbOpenParticles(scene: BattleScene, x: number, y: number, radius: number) { - const pbOpenParticlesFrameNames = scene.anims.generateFrameNames("pb_particles", { start: 0, end: 3, suffix: ".png" }); - if (!(scene.anims.exists("pb_open_particle"))) { - scene.anims.create({ +function doDefaultPbOpenParticles(x: number, y: number, radius: number) { + const pbOpenParticlesFrameNames = globalScene.anims.generateFrameNames("pb_particles", { start: 0, end: 3, suffix: ".png" }); + if (!(globalScene.anims.exists("pb_open_particle"))) { + globalScene.anims.create({ key: "pb_open_particle", frames: pbOpenParticlesFrameNames, frameRate: 16, @@ -35,11 +35,11 @@ function doDefaultPbOpenParticles(scene: BattleScene, x: number, y: number, radi } const addParticle = (index: integer) => { - const particle = scene.add.sprite(x, y, "pb_open_particle"); - scene.field.add(particle); + const particle = globalScene.add.sprite(x, y, "pb_open_particle"); + globalScene.field.add(particle); const angle = index * 45; const [ xCoord, yCoord ] = [ radius * Math.cos(angle * Math.PI / 180), radius * Math.sin(angle * Math.PI / 180) ]; - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, x: x + xCoord, y: y + yCoord, @@ -48,9 +48,9 @@ function doDefaultPbOpenParticles(scene: BattleScene, x: number, y: number, radi particle.play({ key: "pb_open_particle", startFrame: (index + 3) % 4, - frameRate: Math.floor(16 * scene.gameSpeed) + frameRate: Math.floor(16 * globalScene.gameSpeed) }); - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, delay: 500, duration: 75, @@ -61,20 +61,20 @@ function doDefaultPbOpenParticles(scene: BattleScene, x: number, y: number, radi }; let particleCount = 0; - scene.time.addEvent({ + globalScene.time.addEvent({ delay: 20, repeat: 16, callback: () => addParticle(++particleCount) }); } -function doUbOpenParticles(scene: BattleScene, x: number, y: number, frameIndex: integer) { +function doUbOpenParticles(x: number, y: number, frameIndex: integer) { const particles: Phaser.GameObjects.Image[] = []; for (let i = 0; i < 10; i++) { - particles.push(doFanOutParticle(scene, i * 25, x, y, 1, 1, 5, frameIndex)); + particles.push(doFanOutParticle(i * 25, x, y, 1, 1, 5, frameIndex)); } - scene.tweens.add({ + globalScene.tweens.add({ targets: particles, delay: 750, duration: 250, @@ -88,14 +88,14 @@ function doUbOpenParticles(scene: BattleScene, x: number, y: number, frameIndex: }); } -function doMbOpenParticles(scene: BattleScene, x: number, y: number) { +function doMbOpenParticles(x: number, y: number) { const particles: Phaser.GameObjects.Image[] = []; for (let j = 0; j < 2; j++) { for (let i = 0; i < 8; i++) { - particles.push(doFanOutParticle(scene, i * 32, x, y, j ? 1 : 2, j ? 2 : 1, 8, 4)); + particles.push(doFanOutParticle(i * 32, x, y, j ? 1 : 2, j ? 2 : 1, 8, 4)); } - scene.tweens.add({ + globalScene.tweens.add({ targets: particles, delay: 750, duration: 250, @@ -110,11 +110,11 @@ function doMbOpenParticles(scene: BattleScene, x: number, y: number) { } } -function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y: integer, xSpeed: integer, ySpeed: integer, angle: integer, frameIndex: integer): Phaser.GameObjects.Image { +function doFanOutParticle(trigIndex: integer, x: integer, y: integer, xSpeed: integer, ySpeed: integer, angle: integer, frameIndex: integer): Phaser.GameObjects.Image { let f = 0; - const particle = scene.add.image(x, y, "pb_particles", `${frameIndex}.png`); - scene.field.add(particle); + const particle = globalScene.add.image(x, y, "pb_particles", `${frameIndex}.png`); + globalScene.field.add(particle); const updateParticle = () => { if (!particle.scene) { @@ -126,7 +126,7 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y: f++; }; - const particleTimer = scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: getFrameMs(1), onRepeat: () => { @@ -137,20 +137,20 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y: return particle; } -export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite): void { +export function addPokeballCaptureStars(pokeball: Phaser.GameObjects.Sprite): void { const addParticle = () => { - const particle = scene.add.sprite(pokeball.x, pokeball.y, "pb_particles", "4.png"); + const particle = globalScene.add.sprite(pokeball.x, pokeball.y, "pb_particles", "4.png"); particle.setOrigin(pokeball.originX, pokeball.originY); particle.setAlpha(0.5); - scene.field.add(particle); + globalScene.field.add(particle); - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, y: pokeball.y - 10, ease: "Sine.easeOut", duration: 250, onComplete: () => { - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, y: pokeball.y, alpha: 0, @@ -161,13 +161,13 @@ export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.Gam }); const dist = randGauss(25); - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, x: pokeball.x + dist, duration: 500 }); - scene.tweens.add({ + globalScene.tweens.add({ targets: particle, alpha: 0, delay: 425, @@ -193,15 +193,15 @@ export function cos(index: integer, amplitude: integer): number { * @param sparkleSprite the Sprite to play the animation on * @param variant which shiny {@linkcode variant} to play the animation for */ -export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) { +export function doShinySparkleAnim(sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) { const keySuffix = variant ? `_${variant + 1}` : ""; const spriteKey = `shiny${keySuffix}`; const animationKey = `sparkle${keySuffix}`; // Make sure the animation exists, and create it if not - if (!scene.anims.exists(animationKey)) { - const frameNames = scene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 }); - scene.anims.create({ + if (!globalScene.anims.exists(animationKey)) { + const frameNames = globalScene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 }); + globalScene.anims.create({ key: `sparkle${keySuffix}`, frames: frameNames, frameRate: 32, @@ -212,5 +212,5 @@ export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.Gam // Play the animation sparkleSprite.play(animationKey); - scene.playSound("se/sparkle"); + globalScene.playSound("se/sparkle"); } diff --git a/src/field/arena.ts b/src/field/arena.ts index 3cbef659d7a..71966b97775 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -1,14 +1,17 @@ -import BattleScene from "#app/battle-scene"; -import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "#app/data/balance/biomes"; -import { Constructor } from "#app/utils"; +import { globalScene } from "#app/global-scene"; +import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biomes"; +import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes"; +import type { Constructor } from "#app/utils"; import * as Utils from "#app/utils"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather } from "#app/data/weather"; import { CommonAnim } from "#app/data/battle-anims"; -import { Type } from "#enums/type"; -import Move from "#app/data/move"; -import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; -import { BattlerIndex } from "#app/battle"; +import type { Type } from "#enums/type"; +import type Move from "#app/data/move"; +import type { ArenaTag } from "#app/data/arena-tag"; +import { ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; +import type { BattlerIndex } from "#app/battle"; import { Terrain, TerrainType } from "#app/data/terrain"; import { applyAbAttrs, @@ -18,12 +21,12 @@ import { PostWeatherChangeAbAttr, TerrainEventTypeChangeAbAttr } from "#app/data/ability"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import Overrides from "#app/overrides"; import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; -import { ArenaTagType } from "#enums/arena-tag-type"; +import type { ArenaTagType } from "#enums/arena-tag-type"; import { Biome } from "#enums/biome"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; @@ -34,7 +37,6 @@ import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { WeatherType } from "#enums/weather-type"; export class Arena { - public scene: BattleScene; public biomeType: Biome; public weather: Weather | null; public terrain: Terrain | null; @@ -50,8 +52,7 @@ export class Arena { public readonly eventTarget: EventTarget = new EventTarget(); - constructor(scene: BattleScene, biome: Biome, bgm: string) { - this.scene = scene; + constructor(biome: Biome, bgm: string) { this.biomeType = biome; this.tags = []; this.bgm = bgm; @@ -62,12 +63,12 @@ export class Arena { init() { const biomeKey = getBiomeKey(this.biomeType); - this.scene.arenaPlayer.setBiome(this.biomeType); - this.scene.arenaPlayerTransition.setBiome(this.biomeType); - this.scene.arenaEnemy.setBiome(this.biomeType); - this.scene.arenaNextEnemy.setBiome(this.biomeType); - this.scene.arenaBg.setTexture(`${biomeKey}_bg`); - this.scene.arenaBgTransition.setTexture(`${biomeKey}_bg`); + globalScene.arenaPlayer.setBiome(this.biomeType); + globalScene.arenaPlayerTransition.setBiome(this.biomeType); + globalScene.arenaEnemy.setBiome(this.biomeType); + globalScene.arenaNextEnemy.setBiome(this.biomeType); + globalScene.arenaBg.setTexture(`${biomeKey}_bg`); + globalScene.arenaBgTransition.setTexture(`${biomeKey}_bg`); // Redo this on initialize because during save/load the current wave isn't always // set correctly during construction @@ -86,12 +87,12 @@ export class Arena { } randomSpecies(waveIndex: integer, level: integer, attempt?: integer, luckValue?: integer, isBoss?: boolean): PokemonSpecies { - const overrideSpecies = this.scene.gameMode.getOverrideSpecies(waveIndex); + const overrideSpecies = globalScene.gameMode.getOverrideSpecies(waveIndex); if (overrideSpecies) { return overrideSpecies; } - const isBossSpecies = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length - && (this.biomeType !== Biome.END || this.scene.gameMode.isClassic || this.scene.gameMode.isWaveFinal(waveIndex)); + const isBossSpecies = !!globalScene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length + && (this.biomeType !== Biome.END || globalScene.gameMode.isClassic || globalScene.gameMode.isWaveFinal(waveIndex)); const randVal = isBossSpecies ? 64 : 512; // luck influences encounter rarity let luckModifier = 0; @@ -111,7 +112,7 @@ export class Arena { let ret: PokemonSpecies; let regen = false; if (!tierPool.length) { - ret = this.scene.randomSpecies(waveIndex, level); + ret = globalScene.randomSpecies(waveIndex, level); } else { const entry = tierPool[Utils.randSeedInt(tierPool.length)]; let species: Species; @@ -158,7 +159,7 @@ export class Arena { return this.randomSpecies(waveIndex, level, (attempt || 0) + 1); } - const newSpeciesId = ret.getWildSpeciesForLevel(level, true, isBoss ?? isBossSpecies, this.scene.gameMode); + const newSpeciesId = ret.getWildSpeciesForLevel(level, true, isBoss ?? isBossSpecies, globalScene.gameMode); if (newSpeciesId !== ret.speciesId) { console.log("Replaced", Species[ret.speciesId], "with", Species[newSpeciesId]); ret = getPokemonSpecies(newSpeciesId); @@ -168,7 +169,7 @@ export class Arena { randomTrainerType(waveIndex: integer, isBoss: boolean = false): TrainerType { const isTrainerBoss = !!this.trainerPool[BiomePoolTier.BOSS].length - && (this.scene.gameMode.isTrainerBoss(waveIndex, this.biomeType, this.scene.offsetGym) || isBoss); + && (globalScene.gameMode.isTrainerBoss(waveIndex, this.biomeType, globalScene.offsetGym) || isBoss); console.log(isBoss, this.trainerPool); const tierValue = Utils.randSeedInt(!isTrainerBoss ? 512 : 64); let tier = !isTrainerBoss @@ -243,8 +244,8 @@ export class Arena { */ trySetWeatherOverride(weather: WeatherType): boolean { this.weather = new Weather(weather, 0); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1))); - this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? + globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1))); + globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? return true; } @@ -269,13 +270,13 @@ export class Arena { this.eventTarget.dispatchEvent(new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!)); // TODO: is this bang correct? if (this.weather) { - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); - this.scene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? + globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.SUNNY + (weather - 1), true)); + globalScene.queueMessage(getWeatherStartMessage(weather)!); // TODO: is this bang correct? } else { - this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? + globalScene.queueMessage(getWeatherClearMessage(oldWeatherType)!); // TODO: is this bang correct? } - this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + globalScene.getField(true).filter(p => p.isOnField()).map(pokemon => { pokemon.findAndRemoveTags(t => "weatherTypes" in t && !(t.weatherTypes as WeatherType[]).find(t => t === weather)); applyPostWeatherChangeAbAttrs(PostWeatherChangeAbAttr, pokemon, weather); }); @@ -287,13 +288,13 @@ export class Arena { * Function to trigger all weather based form changes */ triggerWeatherBasedFormChanges(): void { - this.scene.getField(true).forEach( p => { + globalScene.getField(true).forEach( p => { const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM); const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM); if (isCastformWithForecast || isCherrimWithFlowerGift) { - new ShowAbilityPhase(this.scene, p.getBattlerIndex()); - this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger); + new ShowAbilityPhase(p.getBattlerIndex()); + globalScene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger); } }); } @@ -302,13 +303,13 @@ export class Arena { * Function to trigger all weather based form changes back into their normal forms */ triggerWeatherBasedFormChangesToNormal(): void { - this.scene.getField(true).forEach( p => { + globalScene.getField(true).forEach( p => { const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM); const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM); if (isCastformWithForecast || isCherrimWithFlowerGift) { - new ShowAbilityPhase(this.scene, p.getBattlerIndex()); - return this.scene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger); + new ShowAbilityPhase(p.getBattlerIndex()); + return globalScene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger); } }); } @@ -325,14 +326,14 @@ export class Arena { if (this.terrain) { if (!ignoreAnim) { - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); + globalScene.unshiftPhase(new CommonAnimPhase(undefined, undefined, CommonAnim.MISTY_TERRAIN + (terrain - 1))); } - this.scene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? + globalScene.queueMessage(getTerrainStartMessage(terrain)!); // TODO: is this bang correct? } else { - this.scene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? + globalScene.queueMessage(getTerrainClearMessage(oldTerrainType)!); // TODO: is this bang correct? } - this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => { + globalScene.getField(true).filter(p => p.isOnField()).map(pokemon => { pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain)); applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain); applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false); @@ -342,7 +343,7 @@ export class Arena { } public isMoveWeatherCancelled(user: Pokemon, move: Move): boolean { - return !!this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(user, move); + return !!this.weather && !this.weather.isEffectSuppressed() && this.weather.isMoveWeatherCancelled(user, move); } public isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { @@ -355,7 +356,7 @@ export class Arena { getAttackTypeMultiplier(attackType: Type, grounded: boolean): number { let weatherMultiplier = 1; - if (this.weather && !this.weather.isEffectSuppressed(this.scene)) { + if (this.weather && !this.weather.isEffectSuppressed()) { weatherMultiplier = this.weather.getAttackTypeMultiplier(attackType); } @@ -421,7 +422,7 @@ export class Arena { return TimeOfDay.NIGHT; } - const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; + const waveCycle = ((globalScene.currentBattle?.waveIndex || 0) + globalScene.waveCycleOffset) % 40; if (waveCycle < 15) { return TimeOfDay.DAY; @@ -691,7 +692,7 @@ export class Arena { } preloadBgm(): void { - this.scene.loadBgm(this.bgm); + globalScene.loadBgm(this.bgm); } getBgmLoopPoint(): number { @@ -815,17 +816,17 @@ export class ArenaBase extends Phaser.GameObjects.Container { public base: Phaser.GameObjects.Sprite; public props: Phaser.GameObjects.Sprite[]; - constructor(scene: BattleScene, player: boolean) { - super(scene, 0, 0); + constructor(player: boolean) { + super(globalScene, 0, 0); this.player = player; - this.base = scene.addFieldSprite(0, 0, "plains_a", undefined, 1); + this.base = globalScene.addFieldSprite(0, 0, "plains_a", undefined, 1); this.base.setOrigin(0, 0); this.props = !player ? new Array(3).fill(null).map(() => { - const ret = scene.addFieldSprite(0, 0, "plains_b", undefined, 1); + const ret = globalScene.addFieldSprite(0, 0, "plains_b", undefined, 1); ret.setOrigin(0, 0); ret.setVisible(false); return ret; @@ -841,9 +842,9 @@ export class ArenaBase extends Phaser.GameObjects.Container { this.base.setTexture(baseKey); if (this.base.texture.frameTotal > 1) { - const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); - if (!(this.scene.anims.exists(baseKey))) { - this.scene.anims.create({ + const baseFrameNames = globalScene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 }); + if (!(globalScene.anims.exists(baseKey))) { + globalScene.anims.create({ key: baseKey, frames: baseFrameNames, frameRate: 12, @@ -859,7 +860,7 @@ export class ArenaBase extends Phaser.GameObjects.Container { } if (!this.player) { - (this.scene as BattleScene).executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { this.propValue = propValue === undefined ? hasProps ? Utils.randSeedInt(8) : 0 : propValue; @@ -868,9 +869,9 @@ export class ArenaBase extends Phaser.GameObjects.Container { prop.setTexture(propKey); if (hasProps && prop.texture.frameTotal > 1) { - const propFrameNames = this.scene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 }); - if (!(this.scene.anims.exists(propKey))) { - this.scene.anims.create({ + const propFrameNames = globalScene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 }); + if (!(globalScene.anims.exists(propKey))) { + globalScene.anims.create({ key: propKey, frames: propFrameNames, frameRate: 12, @@ -885,7 +886,7 @@ export class ArenaBase extends Phaser.GameObjects.Container { prop.setVisible(hasProps && !!(this.propValue & (1 << p))); this.add(prop); }); - }, (this.scene as BattleScene).currentBattle?.waveIndex || 0, (this.scene as BattleScene).waveSeed); + }, globalScene.currentBattle?.waveIndex || 0, globalScene.waveSeed); } } } diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index 4ddcd2d3ee7..57b9d6990ca 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -1,7 +1,10 @@ import { TextStyle, addTextObject } from "../ui/text"; -import Pokemon, { DamageResult, HitResult } from "./pokemon"; +import type { DamageResult } from "./pokemon"; +import type Pokemon from "./pokemon"; +import { HitResult } from "./pokemon"; import * as Utils from "../utils"; -import { BattlerIndex } from "../battle"; +import type { BattlerIndex } from "../battle"; +import { globalScene } from "#app/global-scene"; type TextAndShadowArr = [ string | null, string | null ]; @@ -13,15 +16,13 @@ export default class DamageNumberHandler { } add(target: Pokemon, amount: integer, result: DamageResult | HitResult.HEAL = HitResult.EFFECTIVE, critical: boolean = false): void { - const scene = target.scene; - - if (!scene?.damageNumbersMode) { + if (!globalScene?.damageNumbersMode) { return; } const battlerIndex = target.getBattlerIndex(); const baseScale = target.getSpriteScale() / 6; - const damageNumber = addTextObject(scene, target.x, -(scene.game.canvas.height / 6) + target.y - target.getSprite().height / 2, Utils.formatStat(amount, true), TextStyle.SUMMARY); + const damageNumber = addTextObject(target.x, -(globalScene.game.canvas.height / 6) + target.y - target.getSprite().height / 2, Utils.formatStat(amount, true), TextStyle.SUMMARY); damageNumber.setName("text-damage-number"); damageNumber.setOrigin(0.5, 1); damageNumber.setScale(baseScale); @@ -58,7 +59,7 @@ export default class DamageNumberHandler { } } - scene.fieldUI.add(damageNumber); + globalScene.fieldUI.add(damageNumber); if (!this.damageNumbers.has(battlerIndex)) { this.damageNumbers.set(battlerIndex, []); @@ -71,14 +72,14 @@ export default class DamageNumberHandler { this.damageNumbers.get(battlerIndex)!.push(damageNumber); - if (scene.damageNumbersMode === 1) { - scene.tweens.add({ + if (globalScene.damageNumbersMode === 1) { + globalScene.tweens.add({ targets: damageNumber, duration: Utils.fixedInt(750), alpha: 1, y: "-=32" }); - scene.tweens.add({ + globalScene.tweens.add({ delay: 375, targets: damageNumber, duration: Utils.fixedInt(625), @@ -94,7 +95,7 @@ export default class DamageNumberHandler { damageNumber.setAlpha(0); - scene.tweens.chain({ + globalScene.tweens.chain({ targets: damageNumber, tweens: [ { diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index b1b85de9b29..4fce9b1dfc9 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -1,12 +1,12 @@ -import { GameObjects } from "phaser"; -import BattleScene from "#app/battle-scene"; -import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { Species } from "#enums/species"; +import type { GameObjects } from "phaser"; +import { globalScene } from "#app/global-scene"; +import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import type { Species } from "#enums/species"; import { isNullOrUndefined } from "#app/utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { doShinySparkleAnim } from "#app/field/anims"; +import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig; type KnownFileRoot = | "arenas" @@ -82,8 +82,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con public enterFromRight: boolean; private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[]; - constructor(scene: BattleScene, encounter: MysteryEncounter) { - super(scene, -72, 76); + constructor(encounter: MysteryEncounter) { + super(globalScene, -72, 76); this.encounter = encounter; this.enterFromRight = encounter.enterIntroVisualsFromRight ?? false; // Shallow copy configs to allow visual config updates at runtime without dirtying master copy of Encounter @@ -106,16 +106,16 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con } const getSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => { - const ret = this.scene.addFieldSprite(0, 0, spriteKey); + const ret = globalScene.addFieldSprite(0, 0, spriteKey); ret.setOrigin(0.5, 1); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); return ret; }; const getItemSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => { - const icon = this.scene.add.sprite(-19, 2, "items", spriteKey); + const icon = globalScene.add.sprite(-19, 2, "items", spriteKey); icon.setOrigin(0.5, 1); - icon.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); + icon.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 }); return icon; }; @@ -128,7 +128,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1)); this.shinySparkleSprites = []; - const shinySparkleSprites = scene.add.container(0, 0); + const shinySparkleSprites = globalScene.add.container(0, 0); this.spriteConfigs?.forEach((config) => { const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config; @@ -151,7 +151,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con tintSprite.setPipelineData("shiny", true); tintSprite.setPipelineData("variant", variant); // Create Sprite for shiny Sparkle - pokemonShinySparkle = scene.add.sprite(sprite.x, sprite.y, "shiny"); + pokemonShinySparkle = globalScene.add.sprite(sprite.x, sprite.y, "shiny"); pokemonShinySparkle.setOrigin(0.5, 1); pokemonShinySparkle.setVisible(false); this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 }); @@ -217,18 +217,18 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con this.spriteConfigs.forEach((config) => { if (config.isPokemon) { - this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot); + globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); if (config.isShiny) { - this.scene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant); + globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant); } } else if (config.isItem) { - this.scene.loadAtlas("items", ""); + globalScene.loadAtlas("items", ""); } else { - this.scene.loadAtlas(config.spriteKey, config.fileRoot); + globalScene.loadAtlas(config.spriteKey, config.fileRoot); } }); - this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => { + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { this.spriteConfigs.every((config) => { if (config.isItem) { return true; @@ -239,11 +239,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con // Ignore warnings for missing frames, because there will be a lot console.warn = () => { }; - const frameNames = this.scene.anims.generateFrameNames(config.spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }); + const frameNames = globalScene.anims.generateFrameNames(config.spriteKey, { zeroPad: 4, suffix: ".png", start: 1, end: 128 }); console.warn = originalWarn; - if (!(this.scene.anims.exists(config.spriteKey))) { - this.scene.anims.create({ + if (!(globalScene.anims.exists(config.spriteKey))) { + globalScene.anims.create({ key: config.spriteKey, frames: frameNames, frameRate: 10, @@ -257,8 +257,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con resolve(); }); - if (!this.scene.load.isLoading()) { - this.scene.load.start(); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } }); } @@ -337,8 +337,8 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con */ playShinySparkles() { for (const sparkleConfig of this.shinySparkleSprites) { - this.scene.time.delayedCall(500, () => { - doShinySparkleAnim(this.scene, sparkleConfig.sprite, sparkleConfig.variant); + globalScene.time.delayedCall(500, () => { + doShinySparkleAnim(sparkleConfig.sprite, sparkleConfig.variant); }); } } @@ -429,7 +429,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con if (duration) { sprite.setAlpha(0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: sprite, alpha: alpha || 1, duration: duration, @@ -462,7 +462,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con */ private untint(sprite, duration: integer, ease?: string): void { if (duration) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: sprite, alpha: 0, duration: duration, @@ -502,10 +502,3 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con return super.setVisible(value); } } - -/** - * Interface is required so as not to override {@link Phaser.GameObjects.Container.scene} - */ -export default interface MysteryEncounterIntroVisuals { - scene: BattleScene -} diff --git a/src/field/pokemon-sprite-sparkle-handler.ts b/src/field/pokemon-sprite-sparkle-handler.ts index 2c4c295eaa4..074933f0f00 100644 --- a/src/field/pokemon-sprite-sparkle-handler.ts +++ b/src/field/pokemon-sprite-sparkle-handler.ts @@ -1,14 +1,14 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import Pokemon from "./pokemon"; import * as Utils from "../utils"; export default class PokemonSpriteSparkleHandler { private sprites: Set; - setup(scene: BattleScene): void { + setup(): void { this.sprites = new Set(); - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.fixedInt(200), from: 0, to: 1, @@ -37,7 +37,7 @@ export default class PokemonSpriteSparkleHandler { const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, "__BASE"); if (pixel?.alpha) { const [ xOffset, yOffset ] = [ -s.originX * s.width, -s.originY * s.height ]; - const sparkle = (s.scene as BattleScene).addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle"); + const sparkle = globalScene.addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), "tera_sparkle"); sparkle.pipelineData["ignoreTimeTint"] = s.pipelineData["ignoreTimeTint"]; sparkle.setName("sprite-tera-sparkle"); sparkle.play("tera_sparkle"); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 30856ab416a..d40254c8a6b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1,15 +1,22 @@ import Phaser from "phaser"; -import BattleScene, { AnySound } from "#app/battle-scene"; -import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; +import type { AnySound } from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { Variant, VariantSet } from "#app/data/variant"; +import { variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move"; -import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; +import type Move from "#app/data/move"; +import { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move"; +import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; +import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; -import { Constructor, isNullOrUndefined, randSeedInt, type nil } from "#app/utils"; +import type { Constructor } from "#app/utils"; +import { isNullOrUndefined, randSeedInt, type nil } from "#app/utils"; import * as Utils from "#app/utils"; -import { TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; +import type { TypeDamageMultiplier } from "#app/data/type"; +import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; import { getLevelTotalExp } from "#app/data/exp"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; @@ -18,25 +25,31 @@ import { PokeballType } from "#enums/pokeball"; import { Gender } from "#app/data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { Status, getRandomStatus } from "#app/data/status-effect"; -import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import type { SpeciesFormEvolution, SpeciesEvolutionCondition } from "#app/data/balance/pokemon-evolutions"; +import { pokemonEvolutions, pokemonPrevolutions, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; -import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability"; -import PokemonData from "#app/system/pokemon-data"; +import type { Ability, AbAttr } from "#app/data/ability"; +import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability"; +import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; -import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; +import type { PartyOption } from "#app/ui/party-ui-handler"; +import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { EVOLVE_MOVE, LevelMoves, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; +import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { DamageAchv, achvs } from "#app/system/achv"; -import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data"; +import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; +import { DexAttr } from "#app/system/game-data"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { getNatureStatMultiplier } from "#app/data/nature"; -import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { TerrainType } from "#app/data/terrain"; -import { TrainerSlot } from "#app/data/trainer-config"; +import type { TrainerSlot } from "#app/data/trainer-config"; import Overrides from "#app/overrides"; import i18next from "i18next"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; @@ -46,7 +59,7 @@ import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { BerryType } from "#enums/berry-type"; +import type { BerryType } from "#enums/berry-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; @@ -148,8 +161,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { - super(scene, x, y); + constructor(x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + super(globalScene, x, y); if (!species.isObtainable() && this.isPlayer()) { throw `Cannot create a player Pokemon for species '${species.getName(formIndex)}'`; @@ -157,7 +170,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const hiddenAbilityChance = new Utils.IntegerHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } this.species = species; @@ -235,7 +248,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.formIndex === undefined) { - this.formIndex = this.scene.getSpeciesFormIndex(species, this.gender, this.nature, this.isPlayer()); + this.formIndex = globalScene.getSpeciesFormIndex(species, this.gender, this.nature, this.isPlayer()); } if (this.shiny === undefined) { @@ -256,15 +269,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.friendship = species.baseFriendship; this.metLevel = level; - this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; + this.metBiome = globalScene.currentBattle ? globalScene.arena.biomeType : -1; this.metSpecies = species.speciesId; - this.metWave = scene.currentBattle ? scene.currentBattle.waveIndex : -1; + this.metWave = globalScene.currentBattle ? globalScene.currentBattle.waveIndex : -1; this.pokerus = false; if (level > 1) { - const fused = new Utils.BooleanHolder(scene.gameMode.isSplicedOnly); + const fused = new Utils.BooleanHolder(globalScene.gameMode.isSplicedOnly); if (!fused.value && !this.isPlayer() && !this.hasTrainer()) { - this.scene.applyModifier(EnemyFusionChanceModifier, false, fused); + globalScene.applyModifier(EnemyFusionChanceModifier, false, fused); } if (fused.value) { @@ -305,12 +318,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.initBattleInfo(); - this.scene.fieldUI.addAt(this.battleInfo, 0); + globalScene.fieldUI.addAt(this.battleInfo, 0); const getSprite = (hasShadow?: boolean) => { - const ret = this.scene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true); + const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true); ret.setOrigin(0.5, 1); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()) }); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()) }); return ret; }; @@ -332,13 +345,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { abstract initBattleInfo(): void; isOnField(): boolean { - if (!this.scene) { + if (!globalScene) { return false; } if (this.switchOutStatus) { return false; } - return this.scene.field.getIndex(this) > -1; + return globalScene.field.getIndex(this) > -1; } /** @@ -366,7 +379,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public isAllowedInChallenge(): boolean { const challengeAllowed = new Utils.BooleanHolder(true); - applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); + applyChallenges(globalScene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed); return challengeAllowed.value; } @@ -376,7 +389,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns `true` if the pokemon is "active". Returns `false` if there is no active {@linkcode BattleScene} */ public isActive(onField: boolean = false): boolean { - if (!this.scene) { + if (!globalScene) { return false; } return this.isAllowedInBattle() && (!onField || this.isOnField()); @@ -387,7 +400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ret |= this.gender !== Gender.FEMALE ? DexAttr.MALE : DexAttr.FEMALE; ret |= !this.shiny ? DexAttr.NON_SHINY : DexAttr.SHINY; ret |= this.variant >= 2 ? DexAttr.VARIANT_3 : this.variant === 1 ? DexAttr.VARIANT_2 : DexAttr.DEFAULT_VARIANT; - ret |= this.scene.gameData.getFormAttr(this.formIndex); + ret |= globalScene.gameData.getFormAttr(this.formIndex); return ret; } @@ -418,26 +431,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { loadAssets(ignoreOverride: boolean = true): Promise { return new Promise(resolve => { const moveIds = this.getMoveset().map(m => m!.getMove().id); // TODO: is this bang correct? - Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m))) + Promise.allSettled(moveIds.map(m => initMoveAnim(m))) .then(() => { - loadMoveAnimAssets(this.scene, moveIds); - this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant); + loadMoveAnimAssets(moveIds); + this.getSpeciesForm().loadAssets(this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant); if (this.isPlayer() || this.getFusionSpeciesForm()) { - this.scene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride)); + globalScene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride)); } if (this.getFusionSpeciesForm()) { - this.getFusionSpeciesForm().loadAssets(this.scene, this.getFusionGender() === Gender.FEMALE, this.fusionFormIndex, this.fusionShiny, this.fusionVariant); - this.scene.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride)); + this.getFusionSpeciesForm().loadAssets(this.getFusionGender() === Gender.FEMALE, this.fusionFormIndex, this.fusionShiny, this.fusionVariant); + globalScene.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride)); } - this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => { + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => { if (this.isPlayer()) { const originalWarn = console.warn; // Ignore warnings for missing frames, because there will be a lot console.warn = () => {}; - const battleFrameNames = this.scene.anims.generateFrameNames(this.getBattleSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 400 }); + const battleFrameNames = globalScene.anims.generateFrameNames(this.getBattleSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 400 }); console.warn = originalWarn; - if (!(this.scene.anims.exists(this.getBattleSpriteKey()))) { - this.scene.anims.create({ + if (!(globalScene.anims.exists(this.getBattleSpriteKey()))) { + globalScene.anims.create({ key: this.getBattleSpriteKey(), frames: battleFrameNames, frameRate: 10, @@ -458,7 +471,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return new Promise(async resolve => { const battleSpritePath = this.getBattleSpriteAtlasPath(isBackSprite, ignoreOverride).replace("variant/", "").replace(/_[1-3]$/, ""); let config = variantData; - const useExpSprite = this.scene.experimentalSprites && this.scene.hasExpSprite(this.getBattleSpriteKey(isBackSprite, ignoreOverride)); + const useExpSprite = globalScene.experimentalSprites && globalScene.hasExpSprite(this.getBattleSpriteKey(isBackSprite, ignoreOverride)); battleSpritePath.split("/").map(p => config ? config = config[p] : null); const variantSet: VariantSet = config as VariantSet; if (variantSet && variantSet[this.variant] === 1) { @@ -479,8 +492,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { updateFusionPaletteAndResolve(); } }); - if (!this.scene.load.isLoading()) { - this.scene.load.start(); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } }); }); @@ -512,7 +525,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ async populateVariantColorCache(cacheKey: string, useExpSprite: boolean, battleSpritePath: string) { const spritePath = `./images/pokemon/variant/${useExpSprite ? "exp/" : ""}${battleSpritePath}.json`; - return this.scene.cachedFetch(spritePath).then(res => { + return globalScene.cachedFetch(spritePath).then(res => { // Prevent the JSON from processing if it failed to load if (!res.ok) { return this.fallbackVariantColor(cacheKey, res.url, useExpSprite, battleSpritePath, res.status, res.statusText); @@ -667,10 +680,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getHeldItems(): PokemonHeldItemModifier[] { - if (!this.scene) { + if (!globalScene) { return []; } - return this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, this.isPlayer()) as PokemonHeldItemModifier[]; + return globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, this.isPlayer()) as PokemonHeldItemModifier[]; } updateScale(): void { @@ -683,7 +696,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } initShinySparkle(): void { - const shinySparkle = this.scene.addFieldSprite(0, 0, "shiny"); + const shinySparkle = globalScene.addFieldSprite(0, 0, "shiny"); shinySparkle.setVisible(false); shinySparkle.setOrigin(0.5, 1); this.add(shinySparkle); @@ -751,7 +764,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // During the Pokemon's MoveEffect phase, the offset is removed to put the Pokemon "in focus" - const currentPhase = this.scene.getCurrentPhase(); + const currentPhase = globalScene.getCurrentPhase(); if (currentPhase instanceof MoveEffectPhase && currentPhase.getPokemon() === this) { return false; } @@ -796,7 +809,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (subTag?.sprite) { targets.push(subTag.sprite); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: targets, x: (_target, _key, value: number) => value + relX, y: (_target, _key, value: number) => value + relY, @@ -902,8 +915,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getCritStage(source: Pokemon, move: Move): number { const critStage = new Utils.IntegerHolder(0); applyMoveAttrs(HighCritAttr, source, this, move, critStage); - this.scene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); - this.scene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); + globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); + globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); const bonusCrit = new Utils.BooleanHolder(false); //@ts-ignore if (applyAbAttrs(BonusCritAbAttr, source, null, false, bonusCrit)) { // TODO: resolve ts-ignore. This is a promise. Checking a promise is bogus. @@ -939,11 +952,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, ignoreAbility: boolean = false, ignoreOppAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): integer { const statValue = new Utils.NumberHolder(this.getStat(stat, false)); - this.scene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); + globalScene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue); // The Ruin abilities here are never ignored, but they reveal themselves on summon anyway const fieldApplied = new Utils.BooleanHolder(false); - for (const pokemon of this.scene.getField(true)) { + for (const pokemon of globalScene.getField(true)) { applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied, simulated); if (fieldApplied.value) { break; @@ -962,23 +975,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case Stat.DEF: - if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { + if (this.isOfType(Type.ICE) && globalScene.arena.weather?.weatherType === WeatherType.SNOW) { ret *= 1.5; } break; case Stat.SPATK: break; case Stat.SPDEF: - if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { + if (this.isOfType(Type.ROCK) && globalScene.arena.weather?.weatherType === WeatherType.SANDSTORM) { ret *= 1.5; } break; case Stat.SPD: const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { + if (globalScene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { ret *= 2; } - if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { + if (globalScene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { ret >>= 2; } @@ -1014,7 +1027,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const statHolder = new Utils.IntegerHolder(Math.floor(((2 * baseStats[s] + this.ivs[s]) * this.level) * 0.01)); if (s === Stat.HP) { statHolder.value = statHolder.value + this.level + 10; - this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); if (this.hasAbility(Abilities.WONDER_GUARD, false, true)) { statHolder.value = 1; } @@ -1029,11 +1042,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { statHolder.value += 5; const natureStatMultiplier = new Utils.NumberHolder(getNatureStatMultiplier(this.getNature(), s)); - this.scene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); + globalScene.applyModifier(PokemonNatureWeightModifier, this.isPlayer(), this, natureStatMultiplier); if (natureStatMultiplier.value !== 1) { statHolder.value = Math.max(Math[natureStatMultiplier.value > 1 ? "ceil" : "floor"](statHolder.value * natureStatMultiplier.value), 1); } - this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); + globalScene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); @@ -1045,21 +1058,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); // Shuckle Juice - this.scene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); + globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); // Old Gateau - this.scene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); + globalScene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; for (const s of PERMANENT_STATS) { baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); } - } else if (this.scene.gameMode.isSplicedOnly) { + } else if (globalScene.gameMode.isSplicedOnly) { for (const s of PERMANENT_STATS) { baseStats[s] = Math.ceil(baseStats[s] / 2); } } // Vitamins - this.scene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); + globalScene.applyModifiers(BaseStatModifier, this.isPlayer(), this, baseStats); return baseStats; } @@ -1193,7 +1206,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const species = this.metSpecies in speciesEggMoves ? this.metSpecies : this.getSpeciesForm(true).getRootSpeciesId(true); if (species in speciesEggMoves) { for (let i = 0; i < 4; i++) { - if (this.scene.gameData.starterData[species].eggMoves & (1 << i)) { + if (globalScene.gameData.starterData[species].eggMoves & (1 << i)) { moves.push(speciesEggMoves[species][i]); } } @@ -1212,7 +1225,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getLearnableLevelMoves(): Moves[] { let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]); - if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) { + if (this.metBiome === -1 && !globalScene.gameMode.isFreshStartChallenge() && !globalScene.gameMode.isDaily) { levelMoves = this.getUnlockedEggMoves().concat(levelMoves); } if (Array.isArray(this.usedTMs) && this.usedTMs.length > 0) { @@ -1431,7 +1444,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // Classic Final boss and Endless Minor/Major bosses do not have passive - const { currentBattle, gameMode } = this.scene; + const { currentBattle, gameMode } = globalScene; const waveIndex = currentBattle?.waveIndex; if (this instanceof EnemyPokemon && (currentBattle?.battleSpec === BattleSpec.FINAL_BOSS || @@ -1458,7 +1471,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) { return false; } - const arena = this.scene?.arena; + const arena = globalScene?.arena; if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) { return false; } @@ -1467,7 +1480,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { const suppressed = new Utils.BooleanHolder(false); - this.scene.getField(true).filter(p => p !== this).map(p => { + globalScene.getField(true).filter(p => p !== this).map(p => { if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) { p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ ability ])); } @@ -1541,20 +1554,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * @returns The tera-formed type of the pokemon, or {@linkcode Type.UNKNOWN} if not present + * @returns the pokemon's current tera {@linkcode Type}, or `Type.UNKNOWN` if the pokemon is not terastallized */ public getTeraType(): Type { - // this.scene can be undefined for a fainted mon in doubles - if (this.scene !== undefined) { - const teraModifier = this.scene.findModifier(m => m instanceof TerastallizeModifier - && m.pokemonId === this.id && !!m.getBattlesLeft(), this.isPlayer()) as TerastallizeModifier; - // return teraType - if (teraModifier) { - return teraModifier.teraType; - } + // I don't think this should be possible anymore, please report if you encounter this. --NightKev + if (globalScene === undefined) { + console.warn("Pokemon.getTeraType(): Global scene is not defined!"); + return Type.UNKNOWN; } - // if scene is undefined, or if teraModifier is considered false, then return unknown type - return Type.UNKNOWN; + const teraModifier = globalScene.findModifier(m => + m instanceof TerastallizeModifier + && m.pokemonId === this.id + && m.getBattlesLeft() > 0, this.isPlayer()) as TerastallizeModifier; + return teraModifier?.teraType ?? Type.UNKNOWN; } public isTerastallized(): boolean { @@ -1575,7 +1587,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean { const commandedTag = this.getTag(BattlerTagType.COMMANDED); - if (commandedTag?.getSourcePokemon(this.scene)?.isActive(true)) { + if (commandedTag?.getSourcePokemon()?.isActive(true)) { return true; } @@ -1588,7 +1600,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective * Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger */ - const opposingFieldUnfiltered = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + const opposingFieldUnfiltered = this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField(); const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false); opposingField.forEach((opponent) => @@ -1596,7 +1608,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { ); const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - return (trappedByAbility.value || !!this.getTag(TrappedTag) || !!this.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)); + return (trappedByAbility.value || !!this.getTag(TrappedTag) || !!globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, side)); } /** @@ -1612,7 +1624,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); - this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder); + globalScene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder); if (this.getTag(BattlerTagType.ELECTRIFIED)) { moveTypeHolder.value = Type.ELECTRIC; } @@ -1664,7 +1676,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (!cancelledHolder.value) { - const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); + const defendingSidePlayField = this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, source, move, cancelledHolder)); } } @@ -1703,7 +1715,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return this.isTerastallized() ? 2 : 1; } const types = this.getTypes(true, true); - const arena = this.scene.arena; + const arena = globalScene.arena; // Handle flying v ground type immunity without removing flying type so effective types are still effective // Related to https://github.com/pagefaultgames/pokerogue/issues/524 @@ -1716,7 +1728,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let multiplier = types.map(defType => { const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType)); - applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); + applyChallenges(globalScene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); if (move) { applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); } @@ -1742,12 +1754,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; const typeMultiplierAgainstFlying = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, Type.FLYING)); - applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying); + applyChallenges(globalScene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying); // Handle strong winds lowering effectiveness of types super effective against pure flying - if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && typeMultiplierAgainstFlying.value === 2) { + if (!ignoreStrongWinds && arena.weather?.weatherType === WeatherType.STRONG_WINDS && !arena.weather.isEffectSuppressed() && this.isOfType(Type.FLYING) && typeMultiplierAgainstFlying.value === 2) { multiplier /= 2; if (!simulated) { - this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); + globalScene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); } } return multiplier as TypeDamageMultiplier; @@ -1944,23 +1956,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ trySetShiny(thresholdOverride?: integer): boolean { // Shiny Pokemon should not spawn in the end biome in endless - if (this.scene.gameMode.isEndless && this.scene.arena.biomeType === Biome.END) { + if (globalScene.gameMode.isEndless && globalScene.arena.biomeType === Biome.END) { return false; } const rand1 = (this.id & 0xFFFF0000) >>> 16; const rand2 = (this.id & 0x0000FFFF); - const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId; + const E = globalScene.gameData.trainerId ^ globalScene.gameData.secretId; const F = rand1 ^ rand2; const shinyThreshold = new Utils.IntegerHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined) { - if (this.scene.eventManager.isEventActive()) { - shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier(); + if (globalScene.eventManager.isEventActive()) { + shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); } if (!this.hasTrainer()) { - this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); } } else { shinyThreshold.value = thresholdOverride; @@ -1991,11 +2003,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (thresholdOverride !== undefined && applyModifiersToOverride) { shinyThreshold.value = thresholdOverride; } - if (this.scene.eventManager.isEventActive()) { - shinyThreshold.value *= this.scene.eventManager.getShinyMultiplier(); + if (globalScene.eventManager.isEventActive()) { + shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); } if (!this.hasTrainer()) { - this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + globalScene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); } } else { shinyThreshold.value = thresholdOverride; @@ -2033,9 +2045,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return 0; } const rand = new Utils.NumberHolder(0); - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { rand.value = Utils.randSeedInt(10); - }, this.id, this.scene.waveSeed); + }, this.id, globalScene.waveSeed); if (rand.value >= SHINY_VARIANT_CHANCE) { return 0; // 6/10 } else if (rand.value >= SHINY_EPIC_CHANCE) { @@ -2048,7 +2060,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public generateFusionSpecies(forStarter?: boolean): void { const hiddenAbilityChance = new Utils.NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { - this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); } const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value); @@ -2075,7 +2087,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { fusionOverride = getPokemonSpecies(Overrides.OPP_FUSION_SPECIES_OVERRIDE); } - this.fusionSpecies = fusionOverride ?? this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true); + this.fusionSpecies = fusionOverride ?? globalScene.randomSpecies(globalScene.currentBattle?.waveIndex || 0, this.level, false, filter, true); this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0); this.fusionShiny = this.shiny; this.fusionVariant = this.variant; @@ -2091,7 +2103,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - this.fusionFormIndex = this.scene.getSpeciesFormIndex(this.fusionSpecies, this.fusionGender, this.getNature(), true); + this.fusionFormIndex = globalScene.getSpeciesFormIndex(this.fusionSpecies, this.fusionGender, this.getNature(), true); this.fusionLuck = this.luck; this.generateName(); @@ -2292,8 +2304,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes - if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) { - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + if (this.isPlayer() || !globalScene.currentBattle?.isBattleMysteryEncounter() || !globalScene.currentBattle?.mysteryEncounter) { + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); } } @@ -2306,19 +2318,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { showInfo(): void { if (!this.battleInfo.visible) { - const otherBattleInfo = this.scene.fieldUI.getAll().slice(0, 4).filter(ui => ui instanceof BattleInfo && ((ui as BattleInfo) instanceof PlayerBattleInfo) === this.isPlayer()).find(() => true); + const otherBattleInfo = globalScene.fieldUI.getAll().slice(0, 4).filter(ui => ui instanceof BattleInfo && ((ui as BattleInfo) instanceof PlayerBattleInfo) === this.isPlayer()).find(() => true); if (!otherBattleInfo || !this.getFieldIndex()) { - this.scene.fieldUI.sendToBack(this.battleInfo); - this.scene.sendTextToBack(); // Push the top right text objects behind everything else + globalScene.fieldUI.sendToBack(this.battleInfo); + globalScene.sendTextToBack(); // Push the top right text objects behind everything else } else { - this.scene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo); + globalScene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo); } this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : !this.isBoss() ? -150 : -198)); this.battleInfo.setVisible(true); if (this.isPlayer()) { this.battleInfo.expMaskRect.x += 150; } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.battleInfo, this.battleInfo.expMaskRect ], x: this.isPlayer() ? "-=150" : `+=${!this.isBoss() ? 150 : 246}`, duration: 1000, @@ -2330,7 +2342,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { hideInfo(): Promise { return new Promise(resolve => { if (this.battleInfo && this.battleInfo.visible) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.battleInfo, this.battleInfo.expMaskRect ], x: this.isPlayer() ? "+=150" : `-=${!this.isBoss() ? 150 : 246}`, duration: 500, @@ -2379,7 +2391,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } addExp(exp: integer) { - const maxExpLevel = this.scene.getMaxExpLevel(); + const maxExpLevel = globalScene.getMaxExpLevel(); const initialExp = this.exp; this.exp += exp; while (this.level < maxExpLevel && this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate)) { @@ -2410,7 +2422,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getOpponents(): Pokemon[] { - return ((this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField()) as Pokemon[]).filter(p => p.isActive()); + return ((this.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField()) as Pokemon[]).filter(p => p.isActive()); } getOpponentDescriptor(): string { @@ -2422,7 +2434,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getAlly(): Pokemon { - return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; + return (this.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.getFieldIndex() ? 0 : 1]; } /** @@ -2431,7 +2443,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns An array of Pokémon on the allied field. */ getAlliedField(): Pokemon[] { - return this instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField(); + return this instanceof PlayerPokemon ? globalScene.getPlayerField() : globalScene.getEnemyField(); } /** @@ -2474,7 +2486,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!ignoreStatStage.value) { const statStageMultiplier = new Utils.NumberHolder(Math.max(2, 2 + statStage.value) / Math.max(2, 2 - statStage.value)); - this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), stat, statStageMultiplier); return Math.min(statStageMultiplier.value, 4); } return 1; @@ -2506,7 +2518,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage); - this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); + globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); userAccStage.value = ignoreAccStatStage.value ? 0 : Math.min(userAccStage.value, 6); targetEvaStage.value = ignoreEvaStatStage.value ? 0 : targetEvaStage.value; @@ -2618,7 +2630,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isPhysical = moveCategory === MoveCategory.PHYSICAL; /** Combined damage multiplier from field effects such as weather, terrain, etc. */ - const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(moveType, source.isGrounded())); + const arenaAttackTypeMultiplier = new Utils.NumberHolder(globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded())); applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); const isTypeImmune = (typeMultiplier * arenaAttackTypeMultiplier.value) === 0; @@ -2636,7 +2648,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); if (fixedDamage.value) { const multiLensMultiplier = new Utils.NumberHolder(1); - source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier); + globalScene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiLensMultiplier); fixedDamage.value = Utils.toDmgValue(fixedDamage.value * multiLensMultiplier.value); return { @@ -2670,7 +2682,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Multiplier for moves enhanced by Multi-Lens and/or Parental Bond */ const multiStrikeEnhancementMultiplier = new Utils.NumberHolder(1); - source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiStrikeEnhancementMultiplier); + globalScene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, move.id, null, multiStrikeEnhancementMultiplier); if (!ignoreSourceAbility) { applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, simulated, null, multiStrikeEnhancementMultiplier); } @@ -2726,7 +2738,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */ const screenMultiplier = new Utils.NumberHolder(1); - this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier); + globalScene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, source, moveCategory, screenMultiplier); /** * For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if: @@ -2742,7 +2754,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); /** Halves damage if this Pokemon is grounded in Misty Terrain against a Dragon-type attack */ - const mistyTerrainMultiplier = (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && moveType === Type.DRAGON) + const mistyTerrainMultiplier = (globalScene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && moveType === Type.DRAGON) ? 0.5 : 1; @@ -2769,10 +2781,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** Apply the enemy's Damage and Resistance tokens */ if (!source.isPlayer()) { - this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage); + globalScene.applyModifiers(EnemyDamageBoosterModifier, false, damage); } if (!this.isPlayer()) { - this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage); + globalScene.applyModifiers(EnemyDamageReducerModifier, false, damage); } @@ -2781,7 +2793,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage); /** Additionally apply friend guard damage reduction if ally has it. */ - if (this.scene.currentBattle.double && this.getAlly()?.isActive(true)) { + if (globalScene.currentBattle.double && this.getAlly()?.isActive(true)) { applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, this.getAlly(), source, move, cancelled, simulated, damage); } } @@ -2829,7 +2841,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled); if (!cancelled.value && typeMultiplier === 0) { - this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) })); + globalScene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) })); } return (typeMultiplier === 0) ? HitResult.NO_EFFECT : HitResult.STATUS; } else { @@ -2843,10 +2855,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { isCritical = true; } else { const critChance = [ 24, 8, 2, 1 ][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; - isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance); + isCritical = critChance === 1 || !globalScene.randBattleSeedInt(critChance); } - const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide); + const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); const blockCrit = new Utils.BooleanHolder(false); applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit); if (noCritTag || blockCrit.value || Overrides.NEVER_CRIT_OVERRIDE) { @@ -2865,9 +2877,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!cancelled) { if (result === HitResult.IMMUNE) { - this.scene.queueMessage(i18next.t("battle:hitResultImmune", { pokemonName: getPokemonNameWithAffix(this) })); + globalScene.queueMessage(i18next.t("battle:hitResultImmune", { pokemonName: getPokemonNameWithAffix(this) })); } else { - this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) })); + globalScene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(this) })); } } return result; @@ -2888,7 +2900,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { substitute.hp -= dmg; } if (!this.isPlayer() && dmg >= this.hp) { - this.scene.applyModifiers(EnemyEndureChanceModifier, false, this); + globalScene.applyModifiers(EnemyEndureChanceModifier, false, this); } /** @@ -2899,9 +2911,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (damage > 0) { if (source.isPlayer()) { - this.scene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage)); - if (damage > this.scene.gameData.gameStats.highestDamage) { - this.scene.gameData.gameStats.highestDamage = damage; + globalScene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage)); + if (damage > globalScene.gameData.gameStats.highestDamage) { + globalScene.gameData.gameStats.highestDamage = damage; } } source.turnData.totalDamageDealt += damage; @@ -2912,34 +2924,34 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() }; this.turnData.attacksReceived.unshift(attackResult); if (source.isPlayer() && !this.isPlayer()) { - this.scene.applyModifiers(DamageMoneyRewardModifier, true, source, new Utils.NumberHolder(damage)); + globalScene.applyModifiers(DamageMoneyRewardModifier, true, source, new Utils.NumberHolder(damage)); } } } if (isCritical) { - this.scene.queueMessage(i18next.t("battle:hitResultCriticalHit")); + globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit")); } // want to include is.Fainted() in case multi hit move ends early, still want to render message if (source.turnData.hitsLeft === 1 || this.isFainted()) { switch (result) { case HitResult.SUPER_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); + globalScene.queueMessage(i18next.t("battle:hitResultSuperEffective")); break; case HitResult.NOT_VERY_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); + globalScene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); break; case HitResult.ONE_HIT_KO: - this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); + globalScene.queueMessage(i18next.t("battle:hitResultOneHitKO")); break; } } if (this.isFainted()) { // set splice index here, so future scene queues happen before FaintedPhase - this.scene.setPhaseQueueSplice(); - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, grudgeTag, source)); + globalScene.setPhaseQueueSplice(); + globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), isOneHitKo, destinyTag, grudgeTag, source)); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); @@ -2973,7 +2985,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { surviveDamage.value = this.lapseTag(BattlerTagType.ENDURE_TOKEN); } if (!surviveDamage.value) { - this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); + globalScene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage); } if (surviveDamage.value) { damage = this.hp - 1; @@ -2990,8 +3002,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * Once the MoveEffectPhase is over (and calls it's .end() function, shiftPhase() will reset the PhaseQueueSplice via clearPhaseQueueSplice() ) */ - this.scene.setPhaseQueueSplice(); - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure)); + globalScene.setPhaseQueueSplice(); + globalScene.unshiftPhase(new FaintPhase(this.getBattlerIndex(), preventEndure)); this.destroySubstitute(); this.lapseTag(BattlerTagType.COMMANDED); this.resetSummonData(); @@ -3010,8 +3022,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns integer of damage done */ damageAndUpdate(damage: number, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): number { - const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical); - this.scene.unshiftPhase(damagePhase); + const damagePhase = new DamageAnimPhase(this.getBattlerIndex(), damage, result as DamageResult, critical); + globalScene.unshiftPhase(damagePhase); if (this.switchOutStatus && source) { damage = 0; } @@ -3186,7 +3198,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { for (const s of BATTLE_STATS) { const sourceStage = source.getStatStage(s); if ((this instanceof PlayerPokemon) && (sourceStage === 6)) { - this.scene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE); + globalScene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE); } this.setStatStage(s, sourceStage); } @@ -3265,7 +3277,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.isOnField()) { return; } - turnMove.turn = this.scene.currentBattle?.turn; + turnMove.turn = globalScene.currentBattle?.turn; this.getMoveHistory().push(turnMove); } @@ -3295,7 +3307,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param {Pokemon} target If specified, this only cancels subsequent strikes against the given target */ stopMultiHit(target?: Pokemon): void { - const effectPhase = this.scene.getCurrentPhase(); + const effectPhase = globalScene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.getUserPokemon() === this) { effectPhase.stopMultiHit(target); } @@ -3309,28 +3321,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.abilityIndex >= abilityCount) {// Shouldn't happen this.abilityIndex = abilityCount - 1; } - this.scene.gameData.setPokemonSeen(this, false); + globalScene.gameData.setPokemonSeen(this, false); this.setScale(this.getSpriteScale()); this.loadAssets().then(() => { this.calculateStats(); - this.scene.updateModifiers(this.isPlayer(), true); - Promise.all([ this.updateInfo(), this.scene.updateFieldScale() ]).then(() => resolve()); + globalScene.updateModifiers(this.isPlayer(), true); + Promise.all([ this.updateInfo(), globalScene.updateFieldScale() ]).then(() => resolve()); }); }); } cry(soundConfig?: Phaser.Types.Sound.SoundConfig, sceneOverride?: BattleScene): AnySound { - const scene = sceneOverride || this.scene; - const cry = this.getSpeciesForm().cry(scene, soundConfig); + const scene = sceneOverride ?? globalScene; // TODO: is `sceneOverride` needed? + const cry = this.getSpeciesForm().cry(soundConfig); let duration = cry.totalDuration * 1000; if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) { - let fusionCry = this.getFusionSpeciesForm().cry(scene, soundConfig, true); + let fusionCry = this.getFusionSpeciesForm().cry(soundConfig, true); duration = Math.min(duration, fusionCry.totalDuration * 1000); fusionCry.destroy(); scene.time.delayedCall(Utils.fixedInt(Math.ceil(duration * 0.4)), () => { try { SoundFade.fadeOut(scene, cry, Utils.fixedInt(Math.ceil(duration * 0.2))); - fusionCry = this.getFusionSpeciesForm().cry(scene, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig)); + fusionCry = this.getFusionSpeciesForm().cry(Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig)); SoundFade.fadeIn(scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), scene.masterVolume * scene.fieldVolume, 0); } catch (err) { console.error(err); @@ -3348,13 +3360,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const key = this.species.getCryKey(this.formIndex); let rate = 0.85; - const cry = this.scene.playSound(key, { rate: rate }) as AnySound; - if (!cry || this.scene.fieldVolume === 0) { + const cry = globalScene.playSound(key, { rate: rate }) as AnySound; + if (!cry || globalScene.fieldVolume === 0) { return callback(); } const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); - const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25); + const delay = Math.max(globalScene.sound.get(key).totalDuration * 50, 25); let frameProgress = 0; let frameThreshold: number; @@ -3362,7 +3374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer : Phaser.Time.TimerEvent | null = this.scene.time.addEvent({ + let faintCryTimer : Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ delay: Utils.fixedInt(delay), repeat: -1, callback: () => { @@ -3389,8 +3401,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); // Failsafe - this.scene.time.delayedCall(Utils.fixedInt(3000), () => { - if (!faintCryTimer || !this.scene) { + globalScene.time.delayedCall(Utils.fixedInt(3000), () => { + if (!faintCryTimer || !globalScene) { return; } if (cry?.isPlaying) { @@ -3407,14 +3419,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const key = this.species.getCryKey(this.formIndex); let i = 0; let rate = 0.85; - const cry = this.scene.playSound(key, { rate: rate }) as AnySound; + const cry = globalScene.playSound(key, { rate: rate }) as AnySound; const sprite = this.getSprite(); const tintSprite = this.getTintSprite(); let duration = cry.totalDuration * 1000; const fusionCryKey = this.fusionSpecies!.getCryKey(this.fusionFormIndex); - let fusionCry = this.scene.playSound(fusionCryKey, { rate: rate }) as AnySound; - if (!cry || !fusionCry || this.scene.fieldVolume === 0) { + let fusionCry = globalScene.playSound(fusionCryKey, { rate: rate }) as AnySound; + if (!cry || !fusionCry || globalScene.fieldVolume === 0) { return callback(); } fusionCry.stop(); @@ -3443,7 +3455,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sprite.anims.pause(); tintSprite?.anims.pause(); - let faintCryTimer: Phaser.Time.TimerEvent | null = this.scene.time.addEvent({ + let faintCryTimer: Phaser.Time.TimerEvent | null = globalScene.time.addEvent({ delay: Utils.fixedInt(delay), repeat: -1, callback: () => { @@ -3458,9 +3470,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { frameProgress -= frameThreshold; } if (i === transitionIndex && fusionCryKey) { - SoundFade.fadeOut(this.scene, cry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2))); - fusionCry = this.scene.playSound(fusionCryKey, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0), rate: rate })); - SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), this.scene.masterVolume * this.scene.fieldVolume, 0); + SoundFade.fadeOut(globalScene, cry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2))); + fusionCry = globalScene.playSound(fusionCryKey, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0), rate: rate })); + SoundFade.fadeIn(globalScene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), globalScene.masterVolume * globalScene.fieldVolume, 0); } rate *= 0.99; if (cry && !cry.pendingRemove) { @@ -3480,8 +3492,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); // Failsafe - this.scene.time.delayedCall(Utils.fixedInt(3000), () => { - if (!faintCryTimer || !this.scene) { + globalScene.time.delayedCall(Utils.fixedInt(3000), () => { + if (!faintCryTimer || !globalScene) { return; } if (cry?.isPlaying) { @@ -3515,7 +3527,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (overrideStatus ? this.status?.effect === effect : this.status) { return false; } - if (this.isGrounded() && (!ignoreField && this.scene.arena.terrain?.terrainType === TerrainType.MISTY)) { + if (this.isGrounded() && (!ignoreField && globalScene.arena.terrain?.terrainType === TerrainType.MISTY)) { return false; } } @@ -3560,12 +3572,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } break; case StatusEffect.SLEEP: - if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { + if (this.isGrounded() && globalScene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { return false; } break; case StatusEffect.FREEZE: - if (this.isOfType(Type.ICE) || (!ignoreField && (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType)))) { + if (this.isOfType(Type.ICE) || (!ignoreField && (globalScene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(globalScene.arena.weather.weatherType)))) { return false; } break; @@ -3603,7 +3615,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (asPhase) { - this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon)); + globalScene.unshiftPhase(new ObtainStatusEffectPhase(this.getBattlerIndex(), effect, turnsRemaining, sourceText, sourcePokemon)); return true; } @@ -3635,7 +3647,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.status = new Status(effect, 0, sleepTurnsRemaining?.value); if (effect !== StatusEffect.FAINT) { - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true); applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon); } @@ -3677,7 +3689,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ isSafeguarded(attacker: Pokemon): boolean { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (this.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { + if (globalScene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, defendingSide)) { const bypassed = new Utils.BooleanHolder(false); if (attacker) { applyAbAttrs(InfiltratorAbAttr, attacker, null, false, bypassed); @@ -3710,13 +3722,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } // If this Pokemon has a Substitute when loading in, play an animation to add its sprite if (this.getTag(SubstituteTag)) { - this.scene.triggerPokemonBattleAnim(this, PokemonAnimType.SUBSTITUTE_ADD); + globalScene.triggerPokemonBattleAnim(this, PokemonAnimType.SUBSTITUTE_ADD); this.getTag(SubstituteTag)!.sourceInFocus = false; } // If this Pokemon has Commander and Dondozo as an active ally, hide this Pokemon's sprite. if (this.hasAbilityWithAttr(CommanderAbAttr) - && this.scene.currentBattle.double + && globalScene.currentBattle.double && this.getAlly()?.species.speciesId === Species.DONDOZO) { this.setVisible(false); } @@ -3734,8 +3746,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.getTag(BattlerTagType.SEEDED)) { this.lapseTag(BattlerTagType.SEEDED); } - if (this.scene) { - this.scene.triggerPokemonFormChange(this, SpeciesFormChangePostMoveTrigger, true); + if (globalScene) { + globalScene.triggerPokemonFormChange(this, SpeciesFormChangePostMoveTrigger, true); } } @@ -3749,7 +3761,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } setFrameRate(frameRate: integer) { - this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; + globalScene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate; try { this.getSprite().play(this.getBattleSpriteKey()); } catch (err: unknown) { @@ -3770,7 +3782,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (duration) { tintSprite?.setAlpha(0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: tintSprite, alpha: alpha || 1, duration: duration, @@ -3785,7 +3797,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const tintSprite = this.getTintSprite(); if (duration) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: tintSprite, alpha: 0, duration: duration, @@ -3824,7 +3836,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { sparkle(): void { if (this.shinySparkle) { - doShinySparkleAnim(this.scene, this.shinySparkle, this.variant); + doShinySparkleAnim(this.shinySparkle, this.variant); } } @@ -3845,10 +3857,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const fusionSpriteKey = fusionSpeciesForm.getSpriteKey(this.getFusionGender(ignoreOveride) === Gender.FEMALE, fusionSpeciesForm.formIndex, this.fusionShiny, this.fusionVariant); const fusionBackSpriteKey = fusionSpeciesForm.getSpriteKey(this.getFusionGender(ignoreOveride) === Gender.FEMALE, fusionSpeciesForm.formIndex, this.fusionShiny, this.fusionVariant).replace("pkmn__", "pkmn__back__"); - const sourceTexture = this.scene.textures.get(spriteKey); - const sourceBackTexture = this.scene.textures.get(backSpriteKey); - const fusionTexture = this.scene.textures.get(fusionSpriteKey); - const fusionBackTexture = this.scene.textures.get(fusionBackSpriteKey); + const sourceTexture = globalScene.textures.get(spriteKey); + const sourceBackTexture = globalScene.textures.get(backSpriteKey); + const fusionTexture = globalScene.textures.get(fusionSpriteKey); + const fusionBackTexture = globalScene.textures.get(fusionBackSpriteKey); const [ sourceFrame, sourceBackFrame, fusionFrame, fusionBackFrame ] = [ sourceTexture, sourceBackTexture, fusionTexture, fusionBackTexture ].map(texture => texture.frames[texture.firstFrame]); const [ sourceImage, sourceBackImage, fusionImage, fusionBackImage ] = [ sourceTexture, sourceBackTexture, fusionTexture, fusionBackTexture ].map(i => i.getSourceImage() as HTMLImageElement); @@ -3950,7 +3962,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const originalRandom = Math.random; Math.random = () => Phaser.Math.RND.realInRange(0, 1); - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { paletteColors = QuantizerCelebi.quantize(pixelColors, 4); fusionPaletteColors = QuantizerCelebi.quantize(fusionPixelColors, 4); }, 0, "This result should not vary"); @@ -4071,10 +4083,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } /** - * Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy + * Generates a random number using the current battle's seed, or the global seed if `globalScene.currentBattle` is falsy * * This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts` - * which calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts` + * which calls {@linkcode Battle.randSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle.ts` * which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`, * or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle * @@ -4083,13 +4095,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1) */ randSeedInt(range: integer, min: integer = 0): integer { - return this.scene.currentBattle - ? this.scene.randBattleSeedInt(range, min) + return globalScene.currentBattle + ? globalScene.randBattleSeedInt(range, min) : Utils.randSeedInt(range, min); } /** - * Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy + * Generates a random number using the current battle's seed, or the global seed if `globalScene.currentBattle` is falsy * @param min The minimum integer to generate * @param max The maximum integer to generate * @returns a random integer between {@linkcode min} and {@linkcode max} inclusive @@ -4115,9 +4127,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (hideInfo) { this.hideInfo(); } - this.scene.field.remove(this); + globalScene.field.remove(this); this.setSwitchOutStatus(true); - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); + globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true); } destroy(): void { @@ -4171,7 +4183,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (heldItem.pokemonId === -1 || heldItem.pokemonId === this.id) { heldItem.stackCount--; if (heldItem.stackCount <= 0) { - this.scene.removeModifier(heldItem, !this.isPlayer()); + globalScene.removeModifier(heldItem, !this.isPlayer()); } if (forBattle) { applyPostItemLostAbAttrs(PostItemLostAbAttr, this, false); @@ -4183,15 +4195,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } -export default interface Pokemon { - scene: BattleScene -} - export class PlayerPokemon extends Pokemon { public compatibleTms: Moves[]; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { - super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); + constructor(species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { + super(106, 148, species, level, abilityIndex, formIndex, gender, shiny, variant, ivs, nature, dataSource); if (Overrides.STATUS_OVERRIDE) { this.status = new Status(Overrides.STATUS_OVERRIDE, 0, 4); @@ -4209,7 +4217,7 @@ export class PlayerPokemon extends Pokemon { } if (!dataSource) { - if (this.scene.gameMode.isDaily) { + if (globalScene.gameMode.isDaily) { this.generateAndPopulateMoveset(); } else { this.moveset = []; @@ -4219,7 +4227,7 @@ export class PlayerPokemon extends Pokemon { } initBattleInfo(): void { - this.battleInfo = new PlayerBattleInfo(this.scene); + this.battleInfo = new PlayerBattleInfo(); this.battleInfo.initInfo(this); } @@ -4236,7 +4244,7 @@ export class PlayerPokemon extends Pokemon { } getFieldIndex(): integer { - return this.scene.getPlayerField().indexOf(this); + return globalScene.getPlayerField().indexOf(this); } getBattlerIndex(): BattlerIndex { @@ -4272,7 +4280,7 @@ export class PlayerPokemon extends Pokemon { } tryPopulateMoveset(moveset: StarterMoveset): boolean { - if (!this.getSpeciesForm().validateStarterMoveset(moveset, this.scene.gameData.starterData[this.species.getRootSpeciesId()].eggMoves)) { + if (!this.getSpeciesForm().validateStarterMoveset(moveset, globalScene.gameData.starterData[this.species.getRootSpeciesId()].eggMoves)) { return false; } @@ -4292,11 +4300,11 @@ export class PlayerPokemon extends Pokemon { return new Promise(resolve => { this.leaveField(switchType === SwitchType.SWITCH); - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => { - if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { - this.scene.prependToPhase(new SwitchSummonPhase(this.scene, switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase); + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => { + if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { + globalScene.prependToPhase(new SwitchSummonPhase(switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase); } - this.scene.ui.setMode(Mode.MESSAGE).then(resolve); + globalScene.ui.setMode(Mode.MESSAGE).then(resolve); }, PartyUiHandler.FilterNonFainted); }); } @@ -4306,28 +4314,28 @@ export class PlayerPokemon extends Pokemon { const starterSpeciesId = this.species.getRootSpeciesId(); const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0; const starterData = [ - this.scene.gameData.starterData[starterSpeciesId], - fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null + globalScene.gameData.starterData[starterSpeciesId], + fusionStarterSpeciesId ? globalScene.gameData.starterData[fusionStarterSpeciesId] : null ].filter(d => !!d); const amount = new Utils.NumberHolder(friendship); - this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); + globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; - if (this.scene.eventManager.isEventActive()) { - candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier(); + if (globalScene.eventManager.isEventActive()) { + candyFriendshipMultiplier *= globalScene.eventManager.getFriendshipMultiplier(); } - const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (this.scene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); + const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (globalScene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); // Add friendship to this PlayerPokemon this.friendship = Math.min(this.friendship + amount.value, 255); if (this.friendship === 255) { - this.scene.validateAchv(achvs.MAX_FRIENDSHIP); + globalScene.validateAchv(achvs.MAX_FRIENDSHIP); } // Add to candy progress for this mon's starter species and its fused species (if it has one) starterData.forEach((sd: StarterDataEntry, i: number) => { const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species; sd.friendship = (sd.friendship || 0) + starterAmount.value; if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) { - this.scene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); + globalScene.gameData.addStarterCandy(getPokemonSpecies(speciesId), 1); sd.friendship = 0; } }); @@ -4343,9 +4351,9 @@ export class PlayerPokemon extends Pokemon { */ revivalBlessing(): Promise { return new Promise(resolve => { - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:integer, option: PartyOption) => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:integer, option: PartyOption) => { if (slotIndex >= 0 && slotIndex < 6) { - const pokemon = this.scene.getPlayerParty()[slotIndex]; + const pokemon = globalScene.getPlayerParty()[slotIndex]; if (!pokemon || !pokemon.isFainted()) { resolve(); } @@ -4353,23 +4361,23 @@ export class PlayerPokemon extends Pokemon { pokemon.resetTurnData(); pokemon.resetStatus(); pokemon.heal(Math.min(Utils.toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp())); - this.scene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true); + globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: pokemon.name }), 0, true); - if (this.scene.currentBattle.double && this.scene.getPlayerParty().length > 1) { + if (globalScene.currentBattle.double && globalScene.getPlayerParty().length > 1) { const allyPokemon = this.getAlly(); if (slotIndex <= 1) { // Revived ally pokemon - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true)); - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true)); + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); } else if (allyPokemon.isFainted()) { // Revived party pokemon, and ally pokemon is fainted - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true)); - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + globalScene.unshiftPhase(new SwitchSummonPhase(SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true)); + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); } } } - this.scene.ui.setMode(Mode.MESSAGE).then(() => resolve()); + globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve()); }, PartyUiHandler.FilterFainted); }); } @@ -4387,12 +4395,12 @@ export class PlayerPokemon extends Pokemon { const originalFusionFormIndex = this.fusionFormIndex; this.fusionSpecies = evolutionSpecies; this.fusionFormIndex = evolution.evoFormKey !== null ? Math.max(evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0) : this.fusionFormIndex; - ret = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); + ret = globalScene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); this.fusionSpecies = originalFusionSpecies; this.fusionFormIndex = originalFusionFormIndex; } else { const formIndex = evolution.evoFormKey !== null && !isFusion ? Math.max(evolutionSpecies.forms.findIndex(f => f.formKey === evolution.evoFormKey), 0) : this.formIndex; - ret = this.scene.addPlayerPokemon(!isFusion ? evolutionSpecies : this.species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); + ret = globalScene.addPlayerPokemon(!isFusion ? evolutionSpecies : this.species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); } ret.loadAssets().then(() => resolve(ret)); }); @@ -4458,13 +4466,13 @@ export class PlayerPokemon extends Pokemon { if (preEvolution.speciesId === Species.GIMMIGHOUL) { const evotracker = this.getHeldItems().filter(m => m instanceof EvoTrackerModifier)[0] ?? null; if (evotracker) { - this.scene.removeModifier(evotracker); + globalScene.removeModifier(evotracker); } } - if (!this.scene.gameMode.isDaily || this.metBiome > -1) { - this.scene.gameData.updateSpeciesDexIvs(this.species.speciesId, this.ivs); - this.scene.gameData.setPokemonSeen(this, false); - this.scene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); + if (!globalScene.gameMode.isDaily || this.metBiome > -1) { + globalScene.gameData.updateSpeciesDexIvs(this.species.speciesId, this.ivs); + globalScene.gameData.setPokemonSeen(this, false); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -4479,7 +4487,7 @@ export class PlayerPokemon extends Pokemon { const newEvolution = pokemonEvolutions[evoSpecies.speciesId][1]; if (newEvolution.condition?.predicate(this)) { - const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); + const newPokemon = globalScene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); newPokemon.passive = this.passive; newPokemon.moveset = this.moveset.slice(); newPokemon.moveset = this.copyMoveset(); @@ -4498,16 +4506,16 @@ export class PlayerPokemon extends Pokemon { newPokemon.fusionLuck = this.fusionLuck; newPokemon.usedTMs = this.usedTMs; - this.scene.getPlayerParty().push(newPokemon); + globalScene.getPlayerParty().push(newPokemon); newPokemon.evolve((!isFusion ? newEvolution : new FusionSpeciesFormEvolution(this.id, newEvolution)), evoSpecies); - const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.id, true) as PokemonHeldItemModifier[]; modifiers.forEach(m => { const clonedModifier = m.clone() as PokemonHeldItemModifier; clonedModifier.pokemonId = newPokemon.id; - this.scene.addModifier(clonedModifier, true); + globalScene.addModifier(clonedModifier, true); }); - this.scene.updateModifiers(true); + globalScene.updateModifiers(true); } } } @@ -4515,7 +4523,7 @@ export class PlayerPokemon extends Pokemon { getPossibleForm(formChange: SpeciesFormChange): Promise { return new Promise(resolve => { const formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0); - const ret = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); + const ret = globalScene.addPlayerPokemon(this.species, this.level, this.abilityIndex, formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); ret.loadAssets().then(() => resolve(ret)); }); } @@ -4554,13 +4562,13 @@ export class PlayerPokemon extends Pokemon { const updateAndResolve = () => { this.loadAssets().then(() => { this.calculateStats(); - this.scene.updateModifiers(true, true); + globalScene.updateModifiers(true, true); this.updateInfo(true).then(() => resolve()); }); }; - if (!this.scene.gameMode.isDaily || this.metBiome > -1) { - this.scene.gameData.setPokemonSeen(this, false); - this.scene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); + if (!globalScene.gameMode.isDaily || this.metBiome > -1) { + globalScene.gameData.setPokemonSeen(this, false); + globalScene.gameData.setPokemonCaught(this, false).then(() => updateAndResolve()); } else { updateAndResolve(); } @@ -4590,8 +4598,8 @@ export class PlayerPokemon extends Pokemon { this.pauseEvolutions = true; } - this.scene.validateAchv(achvs.SPLICE); - this.scene.gameData.gameStats.pokemonFused++; + globalScene.validateAchv(achvs.SPLICE); + globalScene.gameData.gameStats.pokemonFused++; // Store the average HP% that each Pokemon has const maxHp = this.getMaxHp(); @@ -4614,23 +4622,23 @@ export class PlayerPokemon extends Pokemon { this.generateCompatibleTms(); this.updateInfo(true); - const fusedPartyMemberIndex = this.scene.getPlayerParty().indexOf(pokemon); - let partyMemberIndex = this.scene.getPlayerParty().indexOf(this); + const fusedPartyMemberIndex = globalScene.getPlayerParty().indexOf(pokemon); + let partyMemberIndex = globalScene.getPlayerParty().indexOf(this); if (partyMemberIndex > fusedPartyMemberIndex) { partyMemberIndex--; } - const fusedPartyMemberHeldModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const fusedPartyMemberHeldModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id, true) as PokemonHeldItemModifier[]; const transferModifiers: Promise[] = []; for (const modifier of fusedPartyMemberHeldModifiers) { - transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false)); + transferModifiers.push(globalScene.tryTransferHeldItemModifier(modifier, this, false, modifier.getStackCount(), true, true, false)); } Promise.allSettled(transferModifiers).then(() => { - this.scene.updateModifiers(true, true).then(() => { - this.scene.removePartyMemberModifiers(fusedPartyMemberIndex); - this.scene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; - const newPartyMemberIndex = this.scene.getPlayerParty().indexOf(this); - pokemon.getMoveset(true).map((m: PokemonMove) => this.scene.unshiftPhase(new LearnMovePhase(this.scene, newPartyMemberIndex, m.getMove().id))); + globalScene.updateModifiers(true, true).then(() => { + globalScene.removePartyMemberModifiers(fusedPartyMemberIndex); + globalScene.getPlayerParty().splice(fusedPartyMemberIndex, 1)[0]; + const newPartyMemberIndex = globalScene.getPlayerParty().indexOf(this); + pokemon.getMoveset(true).map((m: PokemonMove) => globalScene.unshiftPhase(new LearnMovePhase(newPartyMemberIndex, m.getMove().id))); pokemon.destroy(); this.updateFusionPalette(); resolve(); @@ -4670,8 +4678,8 @@ export class EnemyPokemon extends Pokemon { /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ public readonly isPopulatedFromDataSource: boolean; - constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) { - super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender, + constructor(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) { + super(236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender, (!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource); @@ -4738,7 +4746,7 @@ export class EnemyPokemon extends Pokemon { initBattleInfo(): void { if (!this.battleInfo) { - this.battleInfo = new EnemyBattleInfo(this.scene); + this.battleInfo = new EnemyBattleInfo(); this.battleInfo.updateBossSegments(this); this.battleInfo.initInfo(this); } else { @@ -4755,7 +4763,7 @@ export class EnemyPokemon extends Pokemon { */ setBoss(boss: boolean = true, bossSegments: integer = 0): void { if (boss) { - this.bossSegments = bossSegments || this.scene.getEncounterBossSegments(this.scene.currentBattle.waveIndex, this.level, this.species, true); + this.bossSegments = bossSegments || globalScene.getEncounterBossSegments(globalScene.currentBattle.waveIndex, this.level, this.species, true); this.bossSegmentIndex = this.bossSegments - 1; } else { this.bossSegments = 0; @@ -4787,7 +4795,7 @@ export class EnemyPokemon extends Pokemon { new PokemonMove(Moves.FLAMETHROWER), new PokemonMove(Moves.COSMIC_POWER) ]; - if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { + if (globalScene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); } break; @@ -4834,7 +4842,7 @@ export class EnemyPokemon extends Pokemon { } switch (this.aiType) { case AiType.RANDOM: // No enemy should spawn with this AI type in-game - const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? + const moveId = movePool[globalScene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? return { move: moveId, targets: this.getNextTargets(moveId) }; case AiType.SMART_RANDOM: case AiType.SMART: @@ -4853,7 +4861,7 @@ export class EnemyPokemon extends Pokemon { return false; } - const fieldPokemon = this.scene.getField(); + const fieldPokemon = globalScene.getField(); const moveTargets = getMoveTargets(this, move.id).targets .map(ind => fieldPokemon[ind]) .filter(p => this.isPlayer() !== p.isPlayer()); @@ -4891,7 +4899,7 @@ export class EnemyPokemon extends Pokemon { break; } - const target = this.scene.getField()[mt]; + const target = globalScene.getField()[mt]; /** * The "target score" of a move is given by the move's user benefit score + the move's target benefit score. * If the target is an ally, the target benefit score is multiplied by -1. @@ -4950,13 +4958,13 @@ export class EnemyPokemon extends Pokemon { let r = 0; if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { + while (r < sortedMovePool.length - 1 && globalScene.randBattleSeedInt(8) >= 5) { r++; } } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 - && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { + && globalScene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { r++; } } @@ -4975,7 +4983,7 @@ export class EnemyPokemon extends Pokemon { */ getNextTargets(moveId: Moves): BattlerIndex[] { const moveTargets = getMoveTargets(this, moveId); - const targets = this.scene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); + const targets = globalScene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1); // If the move is multi-target, return all targets' indexes if (moveTargets.multiple) { return targets.map(p => p.getBattlerIndex()); @@ -5037,7 +5045,7 @@ export class EnemyPokemon extends Pokemon { * then select the first target whose cumulative weight (with all previous targets' weights) * is greater than that random number. */ - const randValue = this.scene.randBattleSeedInt(totalWeight); + const randValue = globalScene.randBattleSeedInt(totalWeight); let targetIndex: integer = 0; thresholds.every((t, i) => { @@ -5108,7 +5116,7 @@ export class EnemyPokemon extends Pokemon { } } - switch (this.scene.currentBattle.battleSpec) { + switch (globalScene.currentBattle.battleSpec) { case BattleSpec.FINAL_BOSS: if (!this.formIndex && this.bossSegmentIndex < 1) { damage = Math.min(damage, this.hp - 1); @@ -5132,7 +5140,7 @@ export class EnemyPokemon extends Pokemon { } canBypassBossSegments(segmentCount: integer = 1): boolean { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { if (!this.formIndex && (this.bossSegmentIndex - segmentCount) < 1) { return false; } @@ -5183,13 +5191,13 @@ export class EnemyPokemon extends Pokemon { stages++; } - this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat! ], stages, true, true)); + globalScene.unshiftPhase(new StatStageChangePhase(this.getBattlerIndex(), true, [ boostedStat! ], stages, true, true)); this.bossSegmentIndex--; } } getFieldIndex(): integer { - return this.scene.getEnemyField().indexOf(this); + return globalScene.getEnemyField().indexOf(this); } getBattlerIndex(): BattlerIndex { @@ -5204,16 +5212,16 @@ export class EnemyPokemon extends Pokemon { * @returns the pokemon that was added or null if the pokemon could not be added */ addToParty(pokeballType: PokeballType, slotIndex: number = -1) { - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); let ret: PlayerPokemon | null = null; if (party.length < PLAYER_PARTY_MAX_SIZE) { this.pokeball = pokeballType; this.metLevel = this.level; - this.metBiome = this.scene.arena.biomeType; - this.metWave = this.scene.currentBattle.waveIndex; + this.metBiome = globalScene.arena.biomeType; + this.metWave = globalScene.currentBattle.waveIndex; this.metSpecies = this.species.speciesId; - const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); + const newPokemon = globalScene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this); if (Utils.isBetween(slotIndex, 0, PLAYER_PARTY_MAX_SIZE - 1)) { party.splice(slotIndex, 0, newPokemon); @@ -5225,7 +5233,7 @@ export class EnemyPokemon extends Pokemon { newPokemon.setVisible(false); ret = newPokemon; - this.scene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); + globalScene.triggerPokemonFormChange(newPokemon, SpeciesFormChangeActiveTrigger, true); } return ret; diff --git a/src/field/trainer.ts b/src/field/trainer.ts index b77a156f401..fc12eb57abe 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -1,19 +1,21 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; -import { +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import type { TrainerConfig, + TrainerPartyTemplate } from "#app/data/trainer-config"; +import { TrainerPartyCompoundTemplate, - TrainerPartyTemplate, TrainerPoolTier, TrainerSlot, trainerConfigs, trainerPartyTemplates, signatureSpecies } from "#app/data/trainer-config"; -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import * as Utils from "#app/utils"; -import { PersistentModifier } from "#app/modifier/modifier"; +import type { PersistentModifier } from "#app/modifier/modifier"; import { trainerNamePools } from "#app/data/trainer-names"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { getIsInitialized, initI18n } from "#app/plugins/i18n"; @@ -35,8 +37,8 @@ export default class Trainer extends Phaser.GameObjects.Container { public name: string; public partnerName: string; - constructor(scene: BattleScene, trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { - super(scene, -72, 80); + constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { + super(globalScene, -72, 80); this.config = trainerConfigs.hasOwnProperty(trainerType) ? trainerConfigs[trainerType] : trainerConfigs[TrainerType.ACE_TRAINER]; @@ -80,9 +82,9 @@ export default class Trainer extends Phaser.GameObjects.Container { console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); const getSprite = (hasShadow?: boolean, forceFemale?: boolean) => { - const ret = this.scene.addFieldSprite(0, 0, this.config.getSpriteKey(variant === TrainerVariant.FEMALE || forceFemale, this.isDouble())); + const ret = globalScene.addFieldSprite(0, 0, this.config.getSpriteKey(variant === TrainerVariant.FEMALE || forceFemale, this.isDouble())); ret.setOrigin(0.5, 1); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow }); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow }); return ret; }; @@ -207,7 +209,7 @@ export default class Trainer extends Phaser.GameObjects.Container { getPartyTemplate(): TrainerPartyTemplate { if (this.config.partyTemplateFunc) { - return this.config.partyTemplateFunc(this.scene); + return this.config.partyTemplateFunc(); } return this.config.partyTemplates[this.partyTemplateIndex]; } @@ -216,7 +218,7 @@ export default class Trainer extends Phaser.GameObjects.Container { const ret: number[] = []; const partyTemplate = this.getPartyTemplate(); - const difficultyWaveIndex = this.scene.gameMode.getWaveForDifficulty(waveIndex); + const difficultyWaveIndex = globalScene.gameMode.getWaveForDifficulty(waveIndex); const baseLevel = 1 + difficultyWaveIndex / 2 + Math.pow(difficultyWaveIndex / 25, 2); if (this.isDouble() && partyTemplate.size < 2) { @@ -261,12 +263,12 @@ export default class Trainer extends Phaser.GameObjects.Container { } genPartyMember(index: integer): EnemyPokemon { - const battle = this.scene.currentBattle; + const battle = globalScene.currentBattle; const level = battle.enemyLevels?.[index]!; // TODO: is this bang correct? let ret: EnemyPokemon; - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const template = this.getPartyTemplate(); const strength: PartyMemberStrength = template.getStrength(index); @@ -275,11 +277,11 @@ export default class Trainer extends Phaser.GameObjects.Container { if (!(this.config.trainerTypeDouble && this.isDouble() && !this.config.doubleOnly)) { if (this.config.partyMemberFuncs.hasOwnProperty(index)) { - ret = this.config.partyMemberFuncs[index](this.scene, level, strength); + ret = this.config.partyMemberFuncs[index](level, strength); return; } if (this.config.partyMemberFuncs.hasOwnProperty(index - template.size)) { - ret = this.config.partyMemberFuncs[index - template.size](this.scene, level, template.getStrength(index)); + ret = this.config.partyMemberFuncs[index - template.size](level, template.getStrength(index)); return; } } @@ -364,23 +366,23 @@ export default class Trainer extends Phaser.GameObjects.Container { let species = useNewSpeciesPool ? getPokemonSpecies(newSpeciesPool[Math.floor(Utils.randSeedInt(newSpeciesPool.length))]) : template.isSameSpecies(index) && index > offset - ? getPokemonSpecies(battle.enemyParty[offset].species.getTrainerSpeciesForLevel(level, false, template.getStrength(offset), this.scene.currentBattle.waveIndex)) + ? getPokemonSpecies(battle.enemyParty[offset].species.getTrainerSpeciesForLevel(level, false, template.getStrength(offset), globalScene.currentBattle.waveIndex)) : this.genNewPartyMemberSpecies(level, strength); // If the species is from newSpeciesPool, we need to adjust it based on the level and strength if (newSpeciesPool) { - species = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, strength, this.scene.currentBattle.waveIndex)); + species = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, strength, globalScene.currentBattle.waveIndex)); } - ret = this.scene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - }, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8)); + ret = globalScene.addEnemyPokemon(species, level, !this.isDouble() || !(index % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + }, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : globalScene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8)); return ret!; // TODO: is this bang correct? } genNewPartyMemberSpecies(level: integer, strength: PartyMemberStrength, attempt?: integer): PokemonSpecies { - const battle = this.scene.currentBattle; + const battle = globalScene.currentBattle; const template = this.getPartyTemplate(); let baseSpecies: PokemonSpecies; @@ -395,10 +397,10 @@ export default class Trainer extends Phaser.GameObjects.Container { const tierPool = this.config.speciesPools[tier]; baseSpecies = getPokemonSpecies(Utils.randSeedItem(tierPool)); } else { - baseSpecies = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); + baseSpecies = globalScene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); } - let ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + let ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, globalScene.currentBattle.waveIndex)); let retry = false; console.log(ret.getName()); @@ -417,7 +419,7 @@ export default class Trainer extends Phaser.GameObjects.Container { console.log("Attempting reroll of species evolution to fit specialty type..."); let evoAttempt = 0; while (retry && evoAttempt++ < 10) { - ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, globalScene.currentBattle.waveIndex)); console.log(ret.name); if (this.config.specialtyTypes.find(t => ret.isOfType(t))) { retry = false; @@ -448,7 +450,7 @@ export default class Trainer extends Phaser.GameObjects.Container { checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean { const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1); - const currentPartySpecies = this.scene.getEnemyParty().map(p => { + const currentPartySpecies = globalScene.getEnemyParty().map(p => { return p.species.speciesId; }); return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId); @@ -459,10 +461,10 @@ export default class Trainer extends Phaser.GameObjects.Container { trainerSlot = TrainerSlot.NONE; } - const party = this.scene.getEnemyParty(); - const nonFaintedLegalPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => p.isAllowedInBattle()).filter(p => !trainerSlot || p.trainerSlot === trainerSlot); + const party = globalScene.getEnemyParty(); + const nonFaintedLegalPartyMembers = party.slice(globalScene.currentBattle.getBattlerCount()).filter(p => p.isAllowedInBattle()).filter(p => !trainerSlot || p.trainerSlot === trainerSlot); const partyMemberScores = nonFaintedLegalPartyMembers.map(p => { - const playerField = this.scene.getPlayerField().filter(p => p.isAllowedInBattle()); + const playerField = globalScene.getPlayerField().filter(p => p.isAllowedInBattle()); let score = 0; if (playerField.length > 0) { @@ -474,7 +476,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } score /= playerField.length; if (forSwitch && !p.isOnField()) { - this.scene.arena.findTagsOnSide(t => t instanceof ArenaTrapTag, ArenaTagSide.ENEMY).map(t => score *= (t as ArenaTrapTag).getMatchupScoreMultiplier(p)); + globalScene.arena.findTagsOnSide(t => t instanceof ArenaTrapTag, ArenaTagSide.ENEMY).map(t => score *= (t as ArenaTrapTag).getMatchupScoreMultiplier(p)); } } @@ -506,7 +508,7 @@ export default class Trainer extends Phaser.GameObjects.Container { if (maxScorePartyMemberIndexes.length > 1) { let rand: integer; - this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2); + globalScene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), globalScene.currentBattle.turn << 2); return maxScorePartyMemberIndexes[rand!]; } @@ -539,7 +541,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } loadAssets(): Promise { - return this.config.loadAssets(this.scene, this.variant); + return this.config.loadAssets(this.variant); } initSprite(): void { @@ -627,7 +629,7 @@ export default class Trainer extends Phaser.GameObjects.Container { if (duration) { tintSprite.setAlpha(0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: tintSprite, alpha: alpha || 1, duration: duration, @@ -643,7 +645,7 @@ export default class Trainer extends Phaser.GameObjects.Container { const tintSprites = this.getTintSprites(); tintSprites.map(tintSprite => { if (duration) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: tintSprite, alpha: 0, duration: duration, @@ -660,7 +662,3 @@ export default class Trainer extends Phaser.GameObjects.Container { }); } } - -export default interface Trainer { - scene: BattleScene -} diff --git a/src/game-mode.ts b/src/game-mode.ts index 3f12c5b056e..4e0f5715851 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -1,14 +1,17 @@ import i18next from "i18next"; -import { classicFixedBattles, FixedBattleConfig, FixedBattleConfigs } from "./battle"; -import BattleScene from "./battle-scene"; -import { allChallenges, applyChallenges, Challenge, ChallengeType, copyChallenge } from "./data/challenge"; -import PokemonSpecies, { allSpecies } from "./data/pokemon-species"; -import { Arena } from "./field/arena"; +import type { FixedBattleConfigs } from "./battle"; +import { classicFixedBattles, FixedBattleConfig } from "./battle"; +import type { Challenge } from "./data/challenge"; +import { allChallenges, applyChallenges, ChallengeType, copyChallenge } from "./data/challenge"; +import type PokemonSpecies from "./data/pokemon-species"; +import { allSpecies } from "./data/pokemon-species"; +import type { Arena } from "./field/arena"; import Overrides from "#app/overrides"; import * as Utils from "./utils"; import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; +import { globalScene } from "#app/global-scene"; export enum GameModes { CLASSIC, @@ -109,16 +112,15 @@ export class GameMode implements GameModeConfig { } /** - * @param scene current BattleScene * @returns either: * - random biome for Daily mode * - override from overrides.ts * - Town */ - getStartingBiome(scene: BattleScene): Biome { + getStartingBiome(): Biome { switch (this.modeId) { case GameModes.DAILY: - return scene.generateRandomBiome(this.getWaveForDifficulty(1)); + return globalScene.generateRandomBiome(this.getWaveForDifficulty(1)); default: return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } @@ -136,8 +138,8 @@ export class GameMode implements GameModeConfig { /** * Determines whether or not to generate a trainer * @param waveIndex the current floor the player is on (trainer sprites fail to generate on X1 floors) - * @param arena the arena that contains the scene and functions - * @returns true if a trainer should be generated, false otherwise + * @param arena the current {@linkcode Arena} + * @returns `true` if a trainer should be generated, `false` otherwise */ isWaveTrainer(waveIndex: integer, arena: Arena): boolean { /** @@ -146,14 +148,13 @@ export class GameMode implements GameModeConfig { if (this.isDaily) { return waveIndex % 10 === 5 || (!(waveIndex % 10) && waveIndex > 10 && !this.isWaveFinal(waveIndex)); } - if ((waveIndex % 30) === (arena.scene.offsetGym ? 0 : 20) && !this.isWaveFinal(waveIndex)) { + if ((waveIndex % 30) === (globalScene.offsetGym ? 0 : 20) && !this.isWaveFinal(waveIndex)) { return true; } else if (waveIndex % 10 !== 1 && waveIndex % 10) { /** * Do not check X1 floors since there's a bug that stops trainer sprites from appearing * after a X0 full party heal */ - const trainerChance = arena.getTrainerChance(); let allowTrainerBattle = true; if (trainerChance) { @@ -163,11 +164,11 @@ export class GameMode implements GameModeConfig { if (w === waveIndex) { continue; } - if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(w)) { + if ((w % 30) === (globalScene.offsetGym ? 0 : 20) || this.isFixedBattle(w)) { allowTrainerBattle = false; break; } else if (w < waveIndex) { - arena.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const waveTrainerChance = arena.getTrainerChance(); if (!Utils.randSeedInt(waveTrainerChance)) { allowTrainerBattle = false; diff --git a/src/global-scene.ts b/src/global-scene.ts new file mode 100644 index 00000000000..76071bd7fac --- /dev/null +++ b/src/global-scene.ts @@ -0,0 +1,7 @@ +import type BattleScene from "#app/battle-scene"; + +export let globalScene: BattleScene; + +export function initGlobalScene(scene: BattleScene): void { + globalScene = scene; +} diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 537d2870259..d382caf6cb6 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -7,17 +7,17 @@ import pad_xbox360 from "./configs/inputs/pad_xbox360"; import pad_dualshock from "./configs/inputs/pad_dualshock"; import pad_procon from "./configs/inputs/pad_procon"; import { Mode } from "./ui/ui"; -import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; -import SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; +import type SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; +import type SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty"; import { assign, getButtonWithKeycode, getIconForLatestInput, swap, } from "#app/configs/inputs/configHandler"; -import BattleScene from "./battle-scene"; -import { SettingGamepad } from "#app/system/settings/settings-gamepad"; -import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { globalScene } from "#app/global-scene"; +import type { SettingGamepad } from "#app/system/settings/settings-gamepad"; +import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import TouchControl from "#app/touch-controls"; import { Button } from "#enums/buttons"; import { Device } from "#enums/devices"; @@ -75,7 +75,6 @@ const repeatInputDelayMillis = 250; */ export class InputsController { private gamepads: Array = new Array(); - private scene: BattleScene; public events: Phaser.Events.EventEmitter; private buttonLock: Button[] = new Array(); @@ -96,17 +95,13 @@ export class InputsController { /** * Initializes a new instance of the game control system, setting up initial state and configurations. * - * @param scene - The Phaser scene associated with this instance. - * * @remarks * This constructor initializes the game control system with necessary setups for handling inputs. * It prepares an interactions array indexed by button identifiers and configures default states for each button. * Specific buttons like MENU and STATS are set not to repeat their actions. * It concludes by calling the `init` method to complete the setup. */ - - constructor(scene: BattleScene) { - this.scene = scene; + constructor() { this.selectedDevice = { [Device.GAMEPAD]: null, [Device.KEYBOARD]: "default" @@ -134,14 +129,14 @@ export class InputsController { * Additionally, it manages the game's behavior when it loses focus to prevent unwanted game actions during this state. */ init(): void { - this.events = this.scene.game.events; + this.events = globalScene.game.events; - this.scene.game.events.on(Phaser.Core.Events.BLUR, () => { + globalScene.game.events.on(Phaser.Core.Events.BLUR, () => { this.loseFocus(); }); - if (typeof this.scene.input.gamepad !== "undefined") { - this.scene.input.gamepad?.on("connected", function (thisGamepad) { + if (typeof globalScene.input.gamepad !== "undefined") { + globalScene.input.gamepad?.on("connected", function (thisGamepad) { if (!thisGamepad) { return; } @@ -150,25 +145,25 @@ export class InputsController { this.onReconnect(thisGamepad); }, this); - this.scene.input.gamepad?.on("disconnected", function (thisGamepad) { + globalScene.input.gamepad?.on("disconnected", function (thisGamepad) { this.onDisconnect(thisGamepad); // when a gamepad is disconnected }, this); // Check to see if the gamepad has already been setup by the browser - this.scene.input.gamepad?.refreshPads(); - if (this.scene.input.gamepad?.total) { + globalScene.input.gamepad?.refreshPads(); + if (globalScene.input.gamepad?.total) { this.refreshGamepads(); for (const thisGamepad of this.gamepads) { - this.scene.input.gamepad.emit("connected", thisGamepad); + globalScene.input.gamepad.emit("connected", thisGamepad); } } - this.scene.input.gamepad?.on("down", this.gamepadButtonDown, this); - this.scene.input.gamepad?.on("up", this.gamepadButtonUp, this); - this.scene.input.keyboard?.on("keydown", this.keyboardKeyDown, this); - this.scene.input.keyboard?.on("keyup", this.keyboardKeyUp, this); + globalScene.input.gamepad?.on("down", this.gamepadButtonDown, this); + globalScene.input.gamepad?.on("up", this.gamepadButtonUp, this); + globalScene.input.keyboard?.on("keydown", this.keyboardKeyDown, this); + globalScene.input.keyboard?.on("keyup", this.keyboardKeyUp, this); } - this.touchControls = new TouchControl(this.scene); + this.touchControls = new TouchControl(); this.moveTouchControlsHandler = new MoveTouchControlsHandler(this.touchControls); } @@ -238,7 +233,7 @@ export class InputsController { if (gamepadName) { this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase(); } - const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; handler && handler.updateChosenGamepadDisplay(); } @@ -251,7 +246,7 @@ export class InputsController { if (layoutKeyboard) { this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase(); } - const handler = this.scene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler; + const handler = globalScene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler; handler && handler.updateChosenKeyboardDisplay(); } @@ -296,10 +291,10 @@ export class InputsController { const config = deepCopy(this.getConfig(gamepadID)) as InterfaceConfig; config.custom = this.configs[gamepadID]?.custom || { ...config.default }; this.configs[gamepadID] = config; - this.scene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]); + globalScene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]); } this.lastSource = "gamepad"; - const handler = this.scene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; + const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler; handler && handler.updateChosenGamepadDisplay(); } @@ -311,7 +306,7 @@ export class InputsController { const config = deepCopy(this.getConfigKeyboard(layout)) as InterfaceConfig; config.custom = this.configs[layout]?.custom || { ...config.default }; this.configs[layout] = config; - this.scene.gameData?.saveMappingConfigs(this.selectedDevice[Device.KEYBOARD], this.configs[layout]); + globalScene.gameData?.saveMappingConfigs(this.selectedDevice[Device.KEYBOARD], this.configs[layout]); } this.initChosenLayoutKeyboard(this.selectedDevice[Device.KEYBOARD]); } @@ -326,7 +321,7 @@ export class InputsController { */ refreshGamepads(): void { // Sometimes, gamepads are undefined. For some reason. - this.gamepads = this.scene.input.gamepad?.gamepads.filter(function (el) { + this.gamepads = globalScene.input.gamepad?.gamepads.filter(function (el) { return el !== null; }) ?? []; @@ -409,7 +404,7 @@ export class InputsController { return; } this.lastSource = "gamepad"; - if (!this.selectedDevice[Device.GAMEPAD] || (this.scene.ui.getMode() !== Mode.GAMEPAD_BINDING && this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())) { + if (!this.selectedDevice[Device.GAMEPAD] || (globalScene.ui.getMode() !== Mode.GAMEPAD_BINDING && this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())) { this.setChosenGamepad(pad.id); } if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD].toLowerCase()) { diff --git a/src/interfaces/held-modifier-config.ts b/src/interfaces/held-modifier-config.ts index 2285babdbfd..5617cf2446a 100644 --- a/src/interfaces/held-modifier-config.ts +++ b/src/interfaces/held-modifier-config.ts @@ -1,5 +1,5 @@ -import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; export default interface HeldModifierConfig { modifier: PokemonHeldItemModifierType | PokemonHeldItemModifier; diff --git a/src/messages.ts b/src/messages.ts index 91f550918e5..26fd3353cc6 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -1,5 +1,6 @@ +import { globalScene } from "#app/global-scene"; import { BattleSpec } from "#enums/battle-spec"; -import Pokemon from "./field/pokemon"; +import type Pokemon from "./field/pokemon"; import i18next from "i18next"; /** @@ -12,7 +13,7 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { return "Missigno"; } - switch (pokemon.scene.currentBattle.battleSpec) { + switch (globalScene.currentBattle.battleSpec) { case BattleSpec.DEFAULT: return !pokemon.isPlayer() ? pokemon.hasTrainer() diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index b6cf78fb414..9c844596abc 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; @@ -8,14 +8,16 @@ import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } f import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { Type } from "#enums/type"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { ModifierTier } from "#app/modifier/modifier-tier"; import Overrides from "#app/overrides"; import { Unlockables } from "#app/system/unlockables"; import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; -import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import PartyUiHandler from "#app/ui/party-ui-handler"; import { getModifierTierTextTint } from "#app/ui/text"; import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { Abilities } from "#enums/abilities"; @@ -26,7 +28,8 @@ import { Nature } from "#enums/nature"; import { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; import { SpeciesFormKey } from "#enums/species-form-key"; -import { getStatKey, PermanentStat, Stat, TEMP_BATTLE_STATS, TempBattleStat } from "#enums/stat"; +import type { PermanentStat, TempBattleStat } from "#enums/stat"; +import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; @@ -64,7 +67,7 @@ export class ModifierType { return i18next.t(`${this.localeKey}.name` as any); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t(`${this.localeKey}.description` as any); } @@ -206,12 +209,12 @@ class AddPokeballModifierType extends ModifierType { }); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { "modifierCount": this.count, "pokeballName": getPokeballName(this.pokeballType), "catchRate": getPokeballCatchMultiplier(this.pokeballType) > -1 ? `${getPokeballCatchMultiplier(this.pokeballType)}x` : "100%", - "pokeballAmount": `${scene.pokeballCounts[this.pokeballType]}`, + "pokeballAmount": `${globalScene.pokeballCounts[this.pokeballType]}`, }); } } @@ -233,7 +236,7 @@ class AddVoucherModifierType extends ModifierType { }); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.AddVoucherModifierType.description", { "modifierCount": this.count, "voucherTypeName": getVoucherTypeName(this.voucherType), @@ -255,8 +258,8 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, group?: string, soundName?: string) { super(localeKey, iconImage, newModifierFunc, (pokemon: PlayerPokemon) => { const dummyModifier = this.newModifier(pokemon); - const matchingModifier = pokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier)) as PokemonHeldItemModifier; - const maxStackCount = dummyModifier.getMaxStackCount(pokemon.scene); + const matchingModifier = globalScene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(dummyModifier)) as PokemonHeldItemModifier; + const maxStackCount = dummyModifier.getMaxStackCount(); if (!maxStackCount) { return i18next.t("modifierType:ModifierType.PokemonHeldItemModifierType.extra.inoperable", { "pokemonName": getPokemonNameWithAffix(pokemon) }); } @@ -291,7 +294,7 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { this.healStatus = healStatus; } - getDescription(scene: BattleScene): string { + getDescription(): string { return this.restorePoints ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { restorePoints: this.restorePoints, @@ -321,7 +324,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { }; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { restorePercent: this.restorePercent }); } } @@ -337,7 +340,7 @@ export class PokemonStatusHealModifierType extends PokemonModifierType { })); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); } } @@ -369,7 +372,7 @@ export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { this.restorePoints = restorePoints; } - getDescription(scene: BattleScene): string { + getDescription(): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { restorePoints: this.restorePoints }) : i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.extra.fully") @@ -392,7 +395,7 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { this.restorePoints = restorePoints; } - getDescription(scene: BattleScene): string { + getDescription(): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { restorePoints: this.restorePoints }) : i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.extra.fully") @@ -417,7 +420,7 @@ export class PokemonPpUpModifierType extends PokemonMoveModifierType { this.upPoints = upPoints; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); } } @@ -441,7 +444,7 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { natureName: getNatureName(this.nature) }); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { natureName: getNatureName(this.nature, true, true, true) }); } } @@ -467,7 +470,7 @@ export class DoubleBattleChanceBoosterModifierType extends ModifierType { this.maxBattles = maxBattles; } - getDescription(_scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { battleCount: this.maxBattles }); @@ -492,7 +495,7 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge return i18next.t(`modifierType:TempStatStageBoosterItem.${this.nameKey}`); } - getDescription(_scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)), amount: i18next.t(`modifierType:ModifierType.TempStatStageBoosterModifierType.extra.${this.quantityKey}`) @@ -517,7 +520,7 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge return getBerryName(this.berryType); } - getDescription(scene: BattleScene): string { + getDescription(): string { return getBerryEffectDescription(this.berryType); } @@ -563,7 +566,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`); } - getDescription(scene: BattleScene): string { + getDescription(): string { // TODO: Need getTypeName? return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { moveType: i18next.t(`pokemonInfo:Type.${Type[this.moveType]}`) }); } @@ -600,9 +603,9 @@ export class PokemonLevelIncrementModifierType extends PokemonModifierType { super(localeKey, iconImage, (_type, args) => new PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null); } - getDescription(scene: BattleScene): string { + getDescription(): string { let levels = 1; - const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); + const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); if (hasCandyJar) { levels += hasCandyJar.stackCount; } @@ -615,9 +618,9 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType { super(localeKey, iconImage, (_type, _args) => new PokemonLevelIncrementModifier(this, -1)); } - getDescription(scene: BattleScene): string { + getDescription(): string { let levels = 1; - const hasCandyJar = scene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); + const hasCandyJar = globalScene.modifiers.find(modifier => modifier instanceof LevelIncrementBoosterModifier); if (hasCandyJar) { levels += hasCandyJar.stackCount; } @@ -641,7 +644,7 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp return i18next.t(`modifierType:BaseStatBoosterItem.${this.key}`); } - getDescription(_scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) }); } @@ -661,7 +664,7 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp this.statModifier = statModifier; } - override getDescription(scene: BattleScene): string { + override getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", { increaseDecrease: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease"), blessCurse: i18next.t(this.statModifier >= 0 ? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed" : "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed"), @@ -687,7 +690,7 @@ export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType this.stats = stats; } - override getDescription(scene: BattleScene): string { + override getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", { stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"), statValue: this.statModifier, @@ -708,7 +711,7 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType { this.descriptionKey = descriptionKey!; // TODO: is this bang correct? } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t(`${this.descriptionKey || "modifierType:ModifierType.AllPokemonFullHpRestoreModifierType"}.description` as any); } } @@ -730,10 +733,10 @@ export class MoneyRewardModifierType extends ModifierType { this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; } - getDescription(scene: BattleScene): string { - const moneyAmount = new NumberHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); - scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - const formattedMoney = formatMoney(scene.moneyFormat, moneyAmount.value); + getDescription(): string { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + const formattedMoney = formatMoney(globalScene.moneyFormat, moneyAmount.value); return i18next.t("modifierType:ModifierType.MoneyRewardModifierType.description", { moneyMultiplier: i18next.t(this.moneyMultiplierDescriptorKey as any), @@ -751,7 +754,7 @@ export class ExpBoosterModifierType extends ModifierType { this.boostPercent = boostPercent; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } } @@ -765,7 +768,7 @@ export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { this.boostPercent = boostPercent; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } } @@ -775,7 +778,7 @@ export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifie super(localeKey, iconImage, (_type, args) => new PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); } } @@ -789,7 +792,7 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif this.amount = amount; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { accuracyAmount: this.amount }); } } @@ -799,7 +802,7 @@ export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { super(localeKey, iconImage, (type, args) => new PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id)); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); } } @@ -826,8 +829,8 @@ export class TmModifierType extends PokemonModifierType { }); } - getDescription(scene: BattleScene): string { - return i18next.t(scene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); + getDescription(): string { + return i18next.t(globalScene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); } } @@ -855,7 +858,7 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); } @@ -894,7 +897,7 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); } @@ -914,7 +917,7 @@ export class FusePokemonModifierType extends PokemonModifierType { }); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); } } @@ -1144,12 +1147,12 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { const formChangeItemPool = [ ...new Set(party.filter(p => pokemonFormChanges.hasOwnProperty(p.species.speciesId)).map(p => { const formChanges = pokemonFormChanges[p.species.speciesId]; - let formChangeItemTriggers = formChanges.filter(fc => ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || party[0].scene.getModifiers(MegaEvolutionAccessModifier).length) - && ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || party[0].scene.getModifiers(GigantamaxAccessModifier).length) + let formChangeItemTriggers = formChanges.filter(fc => ((fc.formKey.indexOf(SpeciesFormKey.MEGA) === -1 && fc.formKey.indexOf(SpeciesFormKey.PRIMAL) === -1) || globalScene.getModifiers(MegaEvolutionAccessModifier).length) + && ((fc.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) === -1 && fc.formKey.indexOf(SpeciesFormKey.ETERNAMAX) === -1) || globalScene.getModifiers(GigantamaxAccessModifier).length) && (!fc.conditions.length || fc.conditions.filter(cond => cond instanceof SpeciesFormChangeCondition && cond.predicate(p)).length) && (fc.preFormKey === p.getFormKey())) .map(fc => fc.findTrigger(SpeciesFormChangeItemTrigger) as SpeciesFormChangeItemTrigger) - .filter(t => t && t.active && !p.scene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === p.id && m.formChangeItem === t.item)); + .filter(t => t && t.active && !globalScene.findModifier(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === p.id && m.formChangeItem === t.item)); if (p.species.speciesId === Species.NECROZMA) { // technically we could use a simplified version and check for formChanges.length > 3, but in case any code changes later, this might break... @@ -1157,7 +1160,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { let foundULTRA_Z = false, foundN_LUNA = false, foundN_SOLAR = false; - formChangeItemTriggers.forEach((fc, i) => { + formChangeItemTriggers.forEach((fc, _i) => { switch (fc.item) { case FormChangeItem.ULTRANECROZIUM_Z: foundULTRA_Z = true; @@ -1202,7 +1205,7 @@ export class TerastallizeModifierType extends PokemonHeldItemModifierType implem return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } @@ -1220,7 +1223,7 @@ export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemMo this.chancePercent = chancePercent; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.ContactHeldItemTransferChanceModifierType.description", { chancePercent: this.chancePercent }); } } @@ -1230,7 +1233,7 @@ export class TurnHeldItemTransferModifierType extends PokemonHeldItemModifierTyp super(localeKey, iconImage, (type, args) => new TurnHeldItemTransferModifier(type, (args[0] as Pokemon).id), group, soundName); } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.TurnHeldItemTransferModifierType.description"); } } @@ -1240,13 +1243,13 @@ export class EnemyAttackStatusEffectChanceModifierType extends ModifierType { private effect: StatusEffect; constructor(localeKey: string, iconImage: string, chancePercent: integer, effect: StatusEffect, stackCount?: integer) { - super(localeKey, iconImage, (type, args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); + super(localeKey, iconImage, (type, _args) => new EnemyAttackStatusEffectChanceModifier(type, effect, chancePercent, stackCount), "enemy_status_chance"); this.chancePercent = chancePercent; this.effect = effect; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.EnemyAttackStatusEffectChanceModifierType.description", { chancePercent: this.chancePercent, statusEffect: getStatusEffectDescriptor(this.effect), @@ -1263,7 +1266,7 @@ export class EnemyEndureChanceModifierType extends ModifierType { this.chancePercent = chancePercent; } - getDescription(scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.EnemyEndureChanceModifierType.description", { chancePercent: this.chancePercent }); } } @@ -1279,9 +1282,9 @@ type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: integer) * @returns A WeightedModifierTypeWeightFunc */ function skipInClassicAfterWave(wave: integer, defaultWeight: integer): WeightedModifierTypeWeightFunc { - return (party: Pokemon[]) => { - const gameMode = party[0].scene.gameMode; - const currentWave = party[0].scene.currentBattle.waveIndex; + return () => { + const gameMode = globalScene.gameMode; + const currentWave = globalScene.currentBattle.waveIndex; return gameMode.isClassic && currentWave >= wave ? 0 : defaultWeight; }; } @@ -1304,9 +1307,9 @@ function skipInLastClassicWaveOrDefault(defaultWeight: integer) : WeightedModifi * @returns A WeightedModifierTypeWeightFunc */ function lureWeightFunc(maxBattles: number, weight: number): WeightedModifierTypeWeightFunc { - return (party: Pokemon[]) => { - const lures = party[0].scene.getModifiers(DoubleBattleChanceBoosterModifier); - return !(party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0; + return () => { + const lures = globalScene.getModifiers(DoubleBattleChanceBoosterModifier); + return !(globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === 199) && (lures.length === 0 || lures.filter(m => m.getMaxBattles() === maxBattles && m.getBattleCount() >= maxBattles * 0.6).length === 0) ? weight : 0; }; } class WeightedModifierType { @@ -1440,7 +1443,7 @@ export const modifierTypes = { TEMP_STAT_STAGE_BOOSTER: () => new TempStatStageBoosterModifierTypeGenerator(), DIRE_HIT: () => new class extends ModifierType { - getDescription(_scene: BattleScene): string { + getDescription(): string { return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"), amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage") @@ -1452,7 +1455,7 @@ export const modifierTypes = { ATTACK_TYPE_BOOSTER: () => new AttackTypeBoosterModifierTypeGenerator(), - MINT: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + MINT: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Nature)) { return new PokemonNatureChangeModifierType(pregenArgs[0] as Nature); } @@ -1463,7 +1466,7 @@ export const modifierTypes = { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in Type)) { return new TerastallizeModifierType(pregenArgs[0] as Type); } - if (!party[0].scene.getModifiers(TerastallizeAccessModifier).length) { + if (!globalScene.getModifiers(TerastallizeAccessModifier).length) { return null; } let type: Type; @@ -1476,7 +1479,7 @@ export const modifierTypes = { return new TerastallizeModifierType(type); }), - BERRY: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in BerryType)) { return new BerryModifierType(pregenArgs[0] as BerryType); } @@ -1584,19 +1587,19 @@ export const modifierTypes = { ENEMY_ENDURE_CHANCE: () => new EnemyEndureChanceModifierType("modifierType:ModifierType.ENEMY_ENDURE_CHANCE", "wl_reset_urge", 2), ENEMY_FUSED_CHANCE: () => new ModifierType("modifierType:ModifierType.ENEMY_FUSED_CHANCE", "wl_custom_spliced", (type, _args) => new EnemyFusionChanceModifier(type, 1)), - MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); } return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1)); }), - MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); } return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [ Stat.HP, Stat.ATK, Stat.DEF ]); }), - MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new HealShopCostModifier(type, pregenArgs[0] as number)); } @@ -1612,17 +1615,16 @@ interface ModifierPool { /** * Used to check if the player has max of a given ball type in Classic - * @param party The player's party, just used to access the scene * @param ballType The {@linkcode PokeballType} being checked * @returns boolean: true if the player has the maximum of a given ball type */ -function hasMaximumBalls(party: Pokemon[], ballType: PokeballType): boolean { - return (party[0].scene.gameMode.isClassic && party[0].scene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS); +function hasMaximumBalls(ballType: PokeballType): boolean { + return (globalScene.gameMode.isClassic && globalScene.pokeballCounts[ballType] >= MAX_PER_TYPE_POKEBALLS); } const modifierPool: ModifierPool = { [ModifierTier.COMMON]: [ - new WeightedModifierType(modifierTypes.POKEBALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.POKEBALL)) ? 0 : 6, 6), + new WeightedModifierType(modifierTypes.POKEBALL, () => (hasMaximumBalls(PokeballType.POKEBALL)) ? 0 : 6, 6), new WeightedModifierType(modifierTypes.RARE_CANDY, 2), new WeightedModifierType(modifierTypes.POTION, (party: Pokemon[]) => { const thresholdPartyMemberCount = Math.min(party.filter(p => (p.getInverseHp() >= 10 && p.getHpRatio() <= 0.875) && !p.isFainted()).length, 3); @@ -1650,7 +1652,7 @@ const modifierPool: ModifierPool = { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ - new WeightedModifierType(modifierTypes.GREAT_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.GREAT_BALL)) ? 0 : 6, 6), + new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL)) ? 0 : 6, 6), new WeightedModifierType(modifierTypes.PP_UP, 2), new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => { const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => { @@ -1703,10 +1705,10 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.DIRE_HIT, 4), new WeightedModifierType(modifierTypes.SUPER_LURE, lureWeightFunc(15, 4)), new WeightedModifierType(modifierTypes.NUGGET, skipInLastClassicWaveOrDefault(5)), - new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => { - return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8); + new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, () => { + return Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15), 8); }, 8), - new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 2 : 0, 2), + new WeightedModifierType(modifierTypes.MAP, () => globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex < 180 ? 2 : 0, 2), new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), new WeightedModifierType(modifierTypes.TM_GREAT, 3), new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { @@ -1718,22 +1720,22 @@ const modifierPool: ModifierPool = { }, 4), new WeightedModifierType(modifierTypes.BASE_STAT_BOOSTER, 3), new WeightedModifierType(modifierTypes.TERA_SHARD, 1), - new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0), - new WeightedModifierType(modifierTypes.VOUCHER, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), + new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 4 : 0), + new WeightedModifierType(modifierTypes.VOUCHER, (_party: Pokemon[], rerollCount: integer) => !globalScene.gameMode.isDaily ? Math.max(1 - rerollCount, 0) : 0, 1), ].map(m => { m.setTier(ModifierTier.GREAT); return m; }), [ModifierTier.ULTRA]: [ - new WeightedModifierType(modifierTypes.ULTRA_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.ULTRA_BALL)) ? 0 : 15, 15), + new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL)) ? 0 : 15, 15), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), new WeightedModifierType(modifierTypes.PP_MAX, 3), new WeightedModifierType(modifierTypes.MINT, 4), - new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32), - new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24), + new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 15) * 4, 32), 32), + new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, 24), new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { - const { gameMode, gameData } = party[0].scene; + const { gameMode, gameData } = globalScene; if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { return party.some(p => { // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd @@ -1840,14 +1842,14 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.EXP_CHARM, skipInLastClassicWaveOrDefault(8)), new WeightedModifierType(modifierTypes.EXP_SHARE, skipInLastClassicWaveOrDefault(10)), new WeightedModifierType(modifierTypes.EXP_BALANCE, skipInLastClassicWaveOrDefault(3)), - new WeightedModifierType(modifierTypes.TERA_ORB, (party: Pokemon[]) => Math.min(Math.max(Math.floor(party[0].scene.currentBattle.waveIndex / 50) * 2, 1), 4), 4), + new WeightedModifierType(modifierTypes.TERA_ORB, () => Math.min(Math.max(Math.floor(globalScene.currentBattle.waveIndex / 50) * 2, 1), 4), 4), new WeightedModifierType(modifierTypes.QUICK_CLAW, 3), new WeightedModifierType(modifierTypes.WIDE_LENS, 4), ].map(m => { m.setTier(ModifierTier.ULTRA); return m; }), [ModifierTier.ROGUE]: [ - new WeightedModifierType(modifierTypes.ROGUE_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.ROGUE_BALL)) ? 0 : 16, 16), + new WeightedModifierType(modifierTypes.ROGUE_BALL, () => (hasMaximumBalls(PokeballType.ROGUE_BALL)) ? 0 : 16, 16), new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.LEFTOVERS, 3), new WeightedModifierType(modifierTypes.SHELL_BELL, 3), @@ -1857,28 +1859,28 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100) ? 4 : 0, 4), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, () => globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100) ? 4 : 0, 4), new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), - new WeightedModifierType(modifierTypes.LOCK_CAPSULE, (party: Pokemon[]) => party[0].scene.gameMode.isClassic ? 0 : 3), + new WeightedModifierType(modifierTypes.LOCK_CAPSULE, () => globalScene.gameMode.isClassic ? 0 : 3), new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, skipInLastClassicWaveOrDefault(8)), - new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24), - new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36), - new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36), - new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3), + new WeightedModifierType(modifierTypes.RARE_FORM_CHANGE_ITEM, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 6, 24), + new WeightedModifierType(modifierTypes.MEGA_BRACELET, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36), + new WeightedModifierType(modifierTypes.DYNAMAX_BAND, () => Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 50), 4) * 9, 36), + new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (_party: Pokemon[], rerollCount: integer) => !globalScene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3), ].map(m => { m.setTier(ModifierTier.ROGUE); return m; }), [ModifierTier.MASTER]: [ - new WeightedModifierType(modifierTypes.MASTER_BALL, (party: Pokemon[]) => (hasMaximumBalls(party, PokeballType.MASTER_BALL)) ? 0 : 24, 24), + new WeightedModifierType(modifierTypes.MASTER_BALL, () => (hasMaximumBalls(PokeballType.MASTER_BALL)) ? 0 : 24, 24), new WeightedModifierType(modifierTypes.SHINY_CHARM, 14), new WeightedModifierType(modifierTypes.HEALING_CHARM, 18), new WeightedModifierType(modifierTypes.MULTI_LENS, 18), - new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => - !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5), - new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), - new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => (party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))) ? 1 : 0, 1), + new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (_party: Pokemon[], rerollCount: integer) => + !globalScene.gameMode.isDaily && !globalScene.gameMode.isEndless && !globalScene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5), + new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !globalScene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24), + new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, () => (globalScene.gameMode.isDaily || (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE))) ? 1 : 0, 1), ].map(m => { m.setTier(ModifierTier.MASTER); return m; }) @@ -2077,7 +2079,7 @@ export const itemPoolChecks: Map = new Ma export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount: integer = 0) { const pool = getModifierPoolForType(poolType); - itemPoolChecks.forEach((v, k) => { + itemPoolChecks.forEach((_v, k) => { itemPoolChecks.set(k, false); }); @@ -2091,14 +2093,14 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod let i = 0; pool[t].reduce((total: integer, modifierType: WeightedModifierType) => { const weightedModifierType = modifierType as WeightedModifierType; - const existingModifiers = party[0].scene.findModifiers(m => m.type.id === weightedModifierType.modifierType.id, poolType === ModifierPoolType.PLAYER); + const existingModifiers = globalScene.findModifiers(m => m.type.id === weightedModifierType.modifierType.id, poolType === ModifierPoolType.PLAYER); const itemModifierType = weightedModifierType.modifierType instanceof ModifierTypeGenerator ? weightedModifierType.modifierType.generateType(party) : weightedModifierType.modifierType; const weight = !existingModifiers.length || itemModifierType instanceof PokemonHeldItemModifierType || itemModifierType instanceof FormChangeItemModifierType - || existingModifiers.find(m => m.stackCount < m.getMaxStackCount(party[0].scene, true)) + || existingModifiers.find(m => m.stackCount < m.getMaxStackCount(true)) ? weightedModifierType.weight instanceof Function ? (weightedModifierType.weight as Function)(party, rerollCount) : weightedModifierType.weight as integer @@ -2200,7 +2202,7 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo // Guaranteed mod functions second if (customModifierSettings.guaranteedModifierTypeFuncs && customModifierSettings.guaranteedModifierTypeFuncs.length > 0) { - customModifierSettings.guaranteedModifierTypeFuncs!.forEach((mod, i) => { + customModifierSettings.guaranteedModifierTypeFuncs!.forEach((mod, _i) => { const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === mod) as string; let guaranteedMod: ModifierType = modifierTypes[modifierId]?.(); @@ -2319,7 +2321,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); } -export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { +export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[]): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { case ModifierTier.ULTRA: @@ -2337,7 +2339,7 @@ export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: let candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); let r = 0; let matchingModifier: PersistentModifier | undefined; - while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { + while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate?.type?.id)) && matchingModifier.getMaxStackCount() < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { candidate = getNewModifierTypeOption([], ModifierPoolType.ENEMY_BUFF, tier); } @@ -2528,11 +2530,11 @@ export class ModifierTypeOption { * @returns A number between 0 and 14 based on the party's total luck value, or a random number between 0 and 14 if the player is in Daily Run mode. */ export function getPartyLuckValue(party: Pokemon[]): integer { - if (party[0].scene.gameMode.isDaily) { + if (globalScene.gameMode.isDaily) { const DailyLuck = new NumberHolder(0); - party[0].scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { DailyLuck.value = randSeedInt(15); // Random number between 0 and 14 - }, 0, party[0].scene.seed); + }, 0, globalScene.seed); return DailyLuck.value; } const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() : 0) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 05d9e8b9897..37f88deea7f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1,4 +1,3 @@ -import type BattleScene from "#app/battle-scene"; import { FusionSpeciesFormEvolution, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; @@ -20,7 +19,7 @@ import { addTextObject, TextStyle } from "#app/ui/text"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import type { Nature } from "#enums/nature"; import type { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; @@ -32,6 +31,7 @@ import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierT import { Color, ShadowColor } from "#enums/color"; import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { applyAbAttrs, CommanderAbAttr } from "#app/data/ability"; +import { globalScene } from "#app/global-scene"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -65,8 +65,8 @@ export class ModifierBar extends Phaser.GameObjects.Container { private player: boolean; private modifierCache: PersistentModifier[]; - constructor(scene: BattleScene, enemy?: boolean) { - super(scene, 1 + (enemy ? 302 : 0), 2); + constructor(enemy?: boolean) { + super(globalScene, 1 + (enemy ? 302 : 0), 2); this.player = !enemy; this.setScale(0.5); @@ -80,7 +80,7 @@ export class ModifierBar extends Phaser.GameObjects.Container { updateModifiers(modifiers: PersistentModifier[], hideHeldItems: boolean = false) { this.removeAll(true); - const visibleIconModifiers = modifiers.filter(m => m.isIconVisible(this.scene as BattleScene)); + const visibleIconModifiers = modifiers.filter(m => m.isIconVisible()); const nonPokemonSpecificModifiers = visibleIconModifiers.filter(m => !(m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc); const pokemonSpecificModifiers = visibleIconModifiers.filter(m => (m as PokemonHeldItemModifier).pokemonId).sort(modifierSortFunc); @@ -89,7 +89,7 @@ export class ModifierBar extends Phaser.GameObjects.Container { const thisArg = this; sortedVisibleIconModifiers.forEach((modifier: PersistentModifier, i: number) => { - const icon = modifier.getIcon(this.scene as BattleScene); + const icon = modifier.getIcon(); if (i >= iconOverflowIndex) { icon.setVisible(false); } @@ -97,13 +97,13 @@ export class ModifierBar extends Phaser.GameObjects.Container { this.setModifierIconPosition(icon, sortedVisibleIconModifiers.length); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 24), Phaser.Geom.Rectangle.Contains); icon.on("pointerover", () => { - (this.scene as BattleScene).ui.showTooltip(modifier.type.name, modifier.type.getDescription(this.scene as BattleScene)); + globalScene.ui.showTooltip(modifier.type.name, modifier.type.getDescription()); if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { thisArg.updateModifierOverflowVisibility(true); } }); icon.on("pointerout", () => { - (this.scene as BattleScene).ui.hideTooltip(); + globalScene.ui.hideTooltip(); if (this.modifierCache && this.modifierCache.length > iconOverflowIndex) { thisArg.updateModifierOverflowVisibility(false); } @@ -171,10 +171,10 @@ export abstract class PersistentModifier extends Modifier { this.virtualStackCount = 0; } - add(modifiers: PersistentModifier[], virtual: boolean, scene: BattleScene): boolean { + add(modifiers: PersistentModifier[], virtual: boolean): boolean { for (const modifier of modifiers) { if (this.match(modifier)) { - return modifier.incrementStack(scene, this.stackCount, virtual); + return modifier.incrementStack(this.stackCount, virtual); } } @@ -192,8 +192,8 @@ export abstract class PersistentModifier extends Modifier { return []; } - incrementStack(scene: BattleScene, amount: number, virtual: boolean): boolean { - if (this.getStackCount() + amount <= this.getMaxStackCount(scene)) { + incrementStack(amount: number, virtual: boolean): boolean { + if (this.getStackCount() + amount <= this.getMaxStackCount()) { if (!virtual) { this.stackCount += amount; } else { @@ -209,26 +209,26 @@ export abstract class PersistentModifier extends Modifier { return this.stackCount + this.virtualStackCount; } - abstract getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number; + abstract getMaxStackCount(forThreshold?: boolean): number; - isIconVisible(scene: BattleScene): boolean { + isIconVisible(): boolean { return true; } - getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { - const container = scene.add.container(0, 0); + getIcon(forSummary?: boolean): Phaser.GameObjects.Container { + const container = globalScene.add.container(0, 0); - const item = scene.add.sprite(0, 12, "items"); + const item = globalScene.add.sprite(0, 12, "items"); item.setFrame(this.type.iconImage); item.setOrigin(0, 0.5); container.add(item); - const stackText = this.getIconStackText(scene); + const stackText = this.getIconStackText(); if (stackText) { container.add(stackText); } - const virtualStackText = this.getIconStackText(scene, true); + const virtualStackText = this.getIconStackText(true); if (virtualStackText) { container.add(virtualStackText); } @@ -236,14 +236,14 @@ export abstract class PersistentModifier extends Modifier { return container; } - getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount(scene) === 1 || (virtual && !this.virtualStackCount)) { + getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { return null; } - const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); + const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); text.letterSpacing = -0.5; - if (this.getStackCount() >= this.getMaxStackCount(scene)) { + if (this.getStackCount() >= this.getMaxStackCount()) { text.setTint(0xf89890); } text.setOrigin(0, 0); @@ -278,8 +278,8 @@ export class AddPokeballModifier extends ConsumableModifier { * @param battleScene {@linkcode BattleScene} * @returns always `true` */ - override apply(battleScene: BattleScene): boolean { - const pokeballCounts = battleScene.pokeballCounts; + override apply(): boolean { + const pokeballCounts = globalScene.pokeballCounts; pokeballCounts[this.pokeballType] = Math.min(pokeballCounts[this.pokeballType] + this.count, MAX_PER_TYPE_POKEBALLS); return true; @@ -302,8 +302,8 @@ export class AddVoucherModifier extends ConsumableModifier { * @param battleScene {@linkcode BattleScene} * @returns always `true` */ - override apply(battleScene: BattleScene): boolean { - const voucherCounts = battleScene.gameData.voucherCounts; + override apply(): boolean { + const voucherCounts = globalScene.gameData.voucherCounts; voucherCounts[this.voucherType] += this.count; return true; @@ -343,13 +343,13 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { * @param _scene N/A * @returns `true` if the modifier was successfully added or applied, false otherwise */ - add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean { + add(modifiers: PersistentModifier[], _virtual: boolean): boolean { for (const modifier of modifiers) { if (this.match(modifier)) { const modifierInstance = modifier as LapsingPersistentModifier; if (modifierInstance.getBattleCount() < modifierInstance.getMaxBattles()) { modifierInstance.resetBattleCount(); - scene.playSound("se/restore"); + globalScene.playSound("se/restore"); return true; } // should never get here @@ -371,8 +371,8 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { return this.battleCount > 0; } - getIcon(scene: BattleScene): Phaser.GameObjects.Container { - const container = super.getIcon(scene); + getIcon(): Phaser.GameObjects.Container { + const container = super.getIcon(); // Linear interpolation on hue const hue = Math.floor(120 * (this.battleCount / this.maxBattles) + 5); @@ -381,7 +381,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { const typeHex = hslToHex(hue, 0.5, 0.9); const strokeHex = hslToHex(hue, 0.7, 0.3); - const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { + const battleCountText = addTextObject(27, 0, this.battleCount.toString(), TextStyle.PARTY, { fontSize: "66px", color: typeHex, }); @@ -393,7 +393,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { return container; } - getIconStackText(_scene: BattleScene, _virtual?: boolean): Phaser.GameObjects.BitmapText | null { + getIconStackText(_virtual?: boolean): Phaser.GameObjects.BitmapText | null { return null; } @@ -421,7 +421,7 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { return [ this.maxBattles, this.battleCount ]; } - getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number { + getMaxStackCount(_forThreshold?: boolean): number { // Must be an abitrary number greater than 1 return 2; } @@ -574,7 +574,7 @@ export class MapModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -592,7 +592,7 @@ export class MegaEvolutionAccessModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -615,7 +615,7 @@ export class GigantamaxAccessModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -638,7 +638,7 @@ export class TerastallizeAccessModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -680,33 +680,33 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return !!pokemon && (this.pokemonId === -1 || pokemon.id === this.pokemonId); } - isIconVisible(scene: BattleScene): boolean { - return !!(this.getPokemon(scene)?.isOnField()); + isIconVisible(): boolean { + return !!(this.getPokemon()?.isOnField()); } - getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { - const container = !forSummary ? scene.add.container(0, 0) : super.getIcon(scene); + getIcon(forSummary?: boolean): Phaser.GameObjects.Container { + const container = !forSummary ? globalScene.add.container(0, 0) : super.getIcon(); if (!forSummary) { - const pokemon = this.getPokemon(scene); + const pokemon = this.getPokemon(); if (pokemon) { - const pokemonIcon = scene.addPokemonIcon(pokemon, -2, 10, 0, 0.5); + const pokemonIcon = globalScene.addPokemonIcon(pokemon, -2, 10, 0, 0.5); container.add(pokemonIcon); container.setName(pokemon.id.toString()); } - const item = scene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); + const item = globalScene.add.sprite(16, this.virtualStackCount ? 8 : 16, "items"); item.setScale(0.5); item.setOrigin(0, 0.5); item.setTexture("items", this.type.iconImage); container.add(item); - const stackText = this.getIconStackText(scene); + const stackText = this.getIconStackText(); if (stackText) { container.add(stackText); } - const virtualStackText = this.getIconStackText(scene, true); + const virtualStackText = this.getIconStackText(true); if (virtualStackText) { container.add(virtualStackText); } @@ -717,21 +717,21 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return container; } - getPokemon(scene: BattleScene): Pokemon | undefined { - return this.pokemonId ? scene.getPokemonById(this.pokemonId) ?? undefined : undefined; + getPokemon(): Pokemon | undefined { + return this.pokemonId ? globalScene.getPokemonById(this.pokemonId) ?? undefined : undefined; } getScoreMultiplier(): number { return 1; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { - const pokemon = this.getPokemon(scene); + getMaxStackCount(forThreshold?: boolean): number { + const pokemon = this.getPokemon(); if (!pokemon) { return 0; } if (pokemon.isPlayer() && forThreshold) { - return scene.getPlayerParty().map(p => this.getMaxHeldItemCount(p)).reduce((stackCount: number, maxStackCount: number) => Math.max(stackCount, maxStackCount), 0); + return globalScene.getPlayerParty().map(p => this.getMaxHeldItemCount(p)).reduce((stackCount: number, maxStackCount: number) => Math.max(stackCount, maxStackCount), 0); } return this.getMaxHeldItemCount(pokemon); } @@ -760,15 +760,14 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi /** * Retrieve the {@linkcode Modifier | Modifiers} icon as a {@linkcode Phaser.GameObjects.Container | Container} - * @param scene The {@linkcode BattleScene} * @param forSummary `true` if the icon is for the summary screen * @returns the icon as a {@linkcode Phaser.GameObjects.Container | Container} */ - public getIcon(scene: BattleScene, forSummary?: boolean): Phaser.GameObjects.Container { - const container = super.getIcon(scene, forSummary); + public getIcon(forSummary?: boolean): Phaser.GameObjects.Container { + const container = super.getIcon(forSummary); - if (this.getPokemon(scene)?.isPlayer()) { - const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: Color.PINK }); + if (this.getPokemon()?.isPlayer()) { + const battleCountText = addTextObject(27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: Color.PINK }); battleCountText.setShadow(0, 0); battleCountText.setStroke(ShadowColor.RED, 16); battleCountText.setOrigin(1, 0); @@ -782,7 +781,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi return this.battlesLeft; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { + getMaxStackCount(forThreshold?: boolean): number { return 1; } } @@ -820,10 +819,10 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { if (pokemon.isPlayer()) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger); - pokemon.scene.validateAchv(achvs.TERASTALLIZE); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger); + globalScene.validateAchv(achvs.TERASTALLIZE); if (this.teraType === Type.STELLAR) { - pokemon.scene.validateAchv(achvs.STELLAR_TERASTALLIZE); + globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE); } } pokemon.updateSpritePipelineData(); @@ -838,7 +837,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { public override lapse(pokemon: Pokemon): boolean { const ret = super.lapse(pokemon); if (!ret) { - pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger); pokemon.updateSpritePipelineData(); } return ret; @@ -944,19 +943,19 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { return true; } - getIconStackText(scene: BattleScene, virtual?: boolean): Phaser.GameObjects.BitmapText | null { - if (this.getMaxStackCount(scene) === 1 || (virtual && !this.virtualStackCount)) { + getIconStackText(virtual?: boolean): Phaser.GameObjects.BitmapText | null { + if (this.getMaxStackCount() === 1 || (virtual && !this.virtualStackCount)) { return null; } - const pokemon = scene.getPokemonById(this.pokemonId); + const pokemon = globalScene.getPokemonById(this.pokemonId); this.stackCount = pokemon ? pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length + + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length : this.stackCount; - const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); + const text = globalScene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); text.letterSpacing = -0.5; if (this.getStackCount() >= this.required) { text.setTint(0xf89890); @@ -968,7 +967,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { getMaxHeldItemCount(pokemon: Pokemon): number { this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length; + + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length; return 999; } } @@ -1536,7 +1535,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; - pokemon.scene.queueMessage(i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); + globalScene.queueMessage(i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); return true; } @@ -1580,11 +1579,11 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier { override apply(pokemon: Pokemon, doBypassSpeed: BooleanHolder): boolean { if (!doBypassSpeed.value && pokemon.randSeedInt(10) < this.getStackCount()) { doBypassSpeed.value = true; - const isCommandFight = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; + const isCommandFight = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]?.command === Command.FIGHT; const hasQuickClaw = this.type instanceof PokemonHeldItemModifierType && this.type.id === "QUICK_CLAW"; if (isCommandFight && hasQuickClaw) { - pokemon.scene.queueMessage(i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name") })); + globalScene.queueMessage(i18next.t("modifier:bypassSpeedChanceApply", { pokemonName: getPokemonNameWithAffix(pokemon), itemName: i18next.t("modifierType:ModifierType.QUICK_CLAW.name") })); } return true; } @@ -1668,8 +1667,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { if (!pokemon.isFullHp()) { - const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 16) * this.stackCount, i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); return true; } @@ -1761,8 +1759,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { if (pokemon.turnData.totalDamageDealt && !pokemon.isFullHp()) { - const scene = pokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.turnData.totalDamageDealt / 8) * this.stackCount, i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); } @@ -1807,7 +1804,7 @@ export class LevelIncrementBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { + getMaxStackCount(forThreshold?: boolean): number { return 99; } } @@ -1851,7 +1848,7 @@ export class BerryModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { const preserve = new BooleanHolder(false); - pokemon.scene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); + globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve); getBerryEffectFunc(this.berryType)(pokemon); if (!preserve.value) { @@ -1906,7 +1903,7 @@ export class PreserveBerryModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 3; } } @@ -1931,14 +1928,14 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { */ override apply(pokemon: Pokemon): boolean { // Restore the Pokemon to half HP - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), toDmgValue(pokemon.getMaxHp() / 2), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); // Remove the Pokemon's FAINT status pokemon.resetStatus(true, false, true); // Reapply Commander on the Pokemon's side of the field, if applicable - const field = pokemon.isPlayer() ? pokemon.scene.getPlayerField() : pokemon.scene.getEnemyField(); + const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false)); return true; } @@ -1984,7 +1981,7 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier { } if (statRestored) { - pokemon.scene.queueMessage(i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); + globalScene.queueMessage(i18next.t("modifier:resetNegativeStatStageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); } return statRestored; } @@ -2020,8 +2017,8 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier { */ abstract override apply(playerPokemon: PlayerPokemon, ...args: unknown[]): boolean | Promise; - getPokemon(scene: BattleScene) { - return scene.getPlayerParty().find(p => p.id === this.pokemonId); + getPokemon() { + return globalScene.getPlayerParty().find(p => p.id === this.pokemonId); } } @@ -2189,7 +2186,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { */ override apply(playerPokemon: PlayerPokemon): boolean { playerPokemon.setCustomNature(this.nature); - playerPokemon.scene.gameData.unlockSpeciesNature(playerPokemon.species, this.nature); + globalScene.gameData.unlockSpeciesNature(playerPokemon.species, this.nature); return true; } @@ -2207,17 +2204,17 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { * @returns always `true` */ override apply(playerPokemon: PlayerPokemon, levelCount: NumberHolder = new NumberHolder(1)): boolean { - playerPokemon.scene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); + globalScene.applyModifiers(LevelIncrementBoosterModifier, true, levelCount); playerPokemon.level += levelCount.value; - if (playerPokemon.level <= playerPokemon.scene.getMaxExpLevel(true)) { + if (playerPokemon.level <= globalScene.getMaxExpLevel(true)) { playerPokemon.exp = getLevelTotalExp(playerPokemon.level, playerPokemon.species.growthRate); playerPokemon.levelExp = 0; } playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); - playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getPlayerParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level)); + globalScene.unshiftPhase(new LevelUpPhase(globalScene.getPlayerParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level)); return true; } @@ -2237,7 +2234,7 @@ export class TmModifier extends ConsumablePokemonModifier { */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM)); + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM)); return true; } @@ -2259,7 +2256,7 @@ export class RememberMoveModifier extends ConsumablePokemonModifier { */ override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getPlayerParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost)); + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost)); return true; } @@ -2294,7 +2291,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { } if (matchingEvolution) { - playerPokemon.scene.unshiftPhase(new EvolutionPhase(playerPokemon.scene, playerPokemon, matchingEvolution, playerPokemon.level - 1)); + globalScene.unshiftPhase(new EvolutionPhase(playerPokemon, matchingEvolution, playerPokemon.level - 1)); return true; } @@ -2354,7 +2351,7 @@ export class MultipleParticipantExpBonusModifier extends PersistentModifier { return new MultipleParticipantExpBonusModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -2391,7 +2388,7 @@ export class HealingBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -2432,7 +2429,7 @@ export class ExpBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number { + getMaxStackCount(forThreshold?: boolean): number { return this.boostMultiplier < 1 ? this.boostMultiplier < 0.6 ? 99 : 30 : 10; } } @@ -2511,7 +2508,7 @@ export class ExpShareModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -2537,7 +2534,7 @@ export class ExpBalanceModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 4; } } @@ -2774,7 +2771,7 @@ export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier { this.active = false; } - const ret = pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); + const ret = globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger); if (switchActive) { this.active = true; @@ -2799,21 +2796,20 @@ export class MoneyRewardModifier extends ConsumableModifier { /** * Applies {@linkcode MoneyRewardModifier} - * @param battleScene The current {@linkcode BattleScene} * @returns always `true` */ - override apply(battleScene: BattleScene): boolean { - const moneyAmount = new NumberHolder(battleScene.getWaveMoneyAmount(this.moneyMultiplier)); + override apply(): boolean { + const moneyAmount = new NumberHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - battleScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - battleScene.addMoney(moneyAmount.value); + globalScene.addMoney(moneyAmount.value); - battleScene.getPlayerParty().map(p => { + globalScene.getPlayerParty().map(p => { if (p.species?.speciesId === Species.GIMMIGHOUL || p.fusionSpecies?.speciesId === Species.GIMMIGHOUL) { p.evoCounter ? p.evoCounter += Math.min(Math.floor(this.moneyMultiplier), 3) : p.evoCounter = Math.min(Math.floor(this.moneyMultiplier), 3); const modifier = getModifierType(modifierTypes.EVOLUTION_TRACKER_GIMMIGHOUL).newModifier(p) as EvoTrackerModifier; - battleScene.addModifier(modifier); + globalScene.addModifier(modifier); } }); @@ -2845,7 +2841,7 @@ export class MoneyMultiplierModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -2870,10 +2866,9 @@ export class DamageMoneyRewardModifier extends PokemonHeldItemModifier { * @returns always `true` */ override apply(pokemon: Pokemon, multiplier: NumberHolder): boolean { - const battleScene = pokemon.scene; const moneyAmount = new NumberHolder(Math.floor(multiplier.value * (0.5 * this.getStackCount()))); - battleScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - battleScene.addMoney(moneyAmount.value); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.addMoney(moneyAmount.value); return true; } @@ -2894,17 +2889,16 @@ export class MoneyInterestModifier extends PersistentModifier { /** * Applies {@linkcode MoneyInterestModifier} - * @param battleScene The current {@linkcode BattleScene} * @returns always `true` */ - override apply(battleScene: BattleScene): boolean { - const interestAmount = Math.floor(battleScene.money * 0.1 * this.getStackCount()); - battleScene.addMoney(interestAmount); + override apply(): boolean { + const interestAmount = Math.floor(globalScene.money * 0.1 * this.getStackCount()); + globalScene.addMoney(interestAmount); const userLocale = navigator.language || "en-US"; const formattedMoneyAmount = interestAmount.toLocaleString(userLocale); const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name }); - battleScene.queueMessage(message, undefined, true); + globalScene.queueMessage(message, undefined, true); return true; } @@ -2913,7 +2907,7 @@ export class MoneyInterestModifier extends PersistentModifier { return new MoneyInterestModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -2942,7 +2936,7 @@ export class HiddenAbilityRateBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 4; } } @@ -2971,7 +2965,7 @@ export class ShinyRateBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 4; } } @@ -3003,7 +2997,7 @@ export class CriticalCatchChanceBoosterModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 3; } } @@ -3029,7 +3023,7 @@ export class LockModifierTiersModifier extends PersistentModifier { return new LockModifierTiersModifier(this.type, this.stackCount); } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -3069,7 +3063,7 @@ export class HealShopCostModifier extends PersistentModifier { return super.getArgs().concat(this.shopMultiplier); } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -3095,7 +3089,7 @@ export class BoostBugSpawnModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 1; } } @@ -3173,7 +3167,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { const poolType = pokemon.isPlayer() ? ModifierPoolType.PLAYER : pokemon.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD; const transferredModifierTypes: ModifierType[] = []; - const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const itemModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === targetPokemon.id && m.isTransferable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[]; let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct? let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier); @@ -3191,7 +3185,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { } const randItemIndex = pokemon.randSeedInt(itemModifiers.length); const randItem = itemModifiers[randItemIndex]; - heldItemTransferPromises.push(pokemon.scene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { + heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { if (success) { transferredModifierTypes.push(randItem.type); itemModifiers.splice(randItemIndex, 1); @@ -3201,7 +3195,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { Promise.all(heldItemTransferPromises).then(() => { for (const mt of transferredModifierTypes) { - pokemon.scene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); + globalScene.queueMessage(this.getTransferMessage(pokemon, targetPokemon, mt)); } }); @@ -3320,7 +3314,7 @@ export class IvScannerModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 3; } } @@ -3349,7 +3343,7 @@ export class ExtraModifierModifier extends PersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 3; } } @@ -3370,17 +3364,16 @@ export class TempExtraModifierModifier extends LapsingPersistentModifier { * If no existing Silver Pokeballs are found, will add a new one. * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers * @param _virtual N/A - * @param scene * @returns true if the modifier was successfully added or applied, false otherwise */ - add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean { + add(modifiers: PersistentModifier[], _virtual: boolean): boolean { for (const modifier of modifiers) { if (this.match(modifier)) { const modifierInstance = modifier as TempExtraModifierModifier; const newBattleCount = this.getMaxBattles() + modifierInstance.getBattleCount(); modifierInstance.setNewBattleCount(newBattleCount); - scene.playSound("se/restore"); + globalScene.playSound("se/restore"); return true; } } @@ -3413,7 +3406,7 @@ export abstract class EnemyPersistentModifier extends PersistentModifier { super(type, stackCount); } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 5; } } @@ -3438,7 +3431,7 @@ abstract class EnemyDamageMultiplierModifier extends EnemyPersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 99; } } @@ -3461,7 +3454,7 @@ export class EnemyDamageBoosterModifier extends EnemyDamageMultiplierModifier { return [ (this.damageMultiplier - 1) * 100 ]; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 999; } } @@ -3484,8 +3477,8 @@ export class EnemyDamageReducerModifier extends EnemyDamageMultiplierModifier { return [ (1 - this.damageMultiplier) * 100 ]; } - getMaxStackCount(scene: BattleScene): number { - return scene.currentBattle.waveIndex < 2000 ? super.getMaxStackCount(scene) : 999; + getMaxStackCount(): number { + return globalScene.currentBattle.waveIndex < 2000 ? super.getMaxStackCount() : 999; } } @@ -3518,8 +3511,7 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { */ override apply(enemyPokemon: Pokemon): boolean { if (!enemyPokemon.isFullHp()) { - const scene = enemyPokemon.scene; - scene.unshiftPhase(new PokemonHealPhase(scene, enemyPokemon.getBattlerIndex(), + globalScene.unshiftPhase(new PokemonHealPhase(enemyPokemon.getBattlerIndex(), Math.max(Math.floor(enemyPokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(enemyPokemon) }), true, false, false, false, true)); return true; } @@ -3527,7 +3519,7 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { return false; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 10; } } @@ -3569,7 +3561,7 @@ export class EnemyAttackStatusEffectChanceModifier extends EnemyPersistentModifi return false; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 10; } } @@ -3603,7 +3595,7 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier */ override apply(enemyPokemon: Pokemon): boolean { if (enemyPokemon.status && Phaser.Math.RND.realInRange(0, 1) < (this.chance * this.getStackCount())) { - enemyPokemon.scene.queueMessage(getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon))); + globalScene.queueMessage(getStatusEffectHealText(enemyPokemon.status.effect, getPokemonNameWithAffix(enemyPokemon))); enemyPokemon.resetStatus(); enemyPokemon.updateInfo(); return true; @@ -3612,7 +3604,7 @@ export class EnemyStatusEffectHealChanceModifier extends EnemyPersistentModifier return false; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 10; } } @@ -3656,7 +3648,7 @@ export class EnemyEndureChanceModifier extends EnemyPersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 10; } } @@ -3697,7 +3689,7 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { return true; } - getMaxStackCount(scene: BattleScene): number { + getMaxStackCount(): number { return 10; } } @@ -3706,18 +3698,17 @@ export class EnemyFusionChanceModifier extends EnemyPersistentModifier { * Uses either `MODIFIER_OVERRIDE` in overrides.ts to set {@linkcode PersistentModifier}s for either: * - The player * - The enemy - * @param scene current {@linkcode BattleScene} * @param isPlayer {@linkcode boolean} for whether the player (`true`) or enemy (`false`) is being overridden */ -export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): void { +export function overrideModifiers(isPlayer: boolean = true): void { const modifiersOverride: ModifierOverride[] = isPlayer ? Overrides.STARTING_MODIFIER_OVERRIDE : Overrides.OPP_MODIFIER_OVERRIDE; - if (!modifiersOverride || modifiersOverride.length === 0 || !scene) { + if (!modifiersOverride || modifiersOverride.length === 0 || !globalScene) { return; } // If it's the opponent, clear all of their current modifiers to avoid stacking if (!isPlayer) { - scene.clearEnemyModifiers(); + globalScene.clearEnemyModifiers(); } modifiersOverride.forEach(item => { @@ -3734,9 +3725,9 @@ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): modifier.stackCount = item.count || 1; if (isPlayer) { - scene.addModifier(modifier, true, false, false, true); + globalScene.addModifier(modifier, true, false, false, true); } else { - scene.addEnemyModifier(modifier, true, true); + globalScene.addEnemyModifier(modifier, true, true); } } }); @@ -3746,18 +3737,17 @@ export function overrideModifiers(scene: BattleScene, isPlayer: boolean = true): * Uses either `HELD_ITEMS_OVERRIDE` in overrides.ts to set {@linkcode PokemonHeldItemModifier}s for either: * - The first member of the player's team when starting a new game * - An enemy {@linkcode Pokemon} being spawned in - * @param scene current {@linkcode BattleScene} * @param pokemon {@linkcode Pokemon} whose held items are being overridden * @param isPlayer {@linkcode boolean} for whether the {@linkcode pokemon} is the player's (`true`) or an enemy (`false`) */ -export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer: boolean = true): void { +export function overrideHeldItems(pokemon: Pokemon, isPlayer: boolean = true): void { const heldItemsOverride: ModifierOverride[] = isPlayer ? Overrides.STARTING_HELD_ITEMS_OVERRIDE : Overrides.OPP_HELD_ITEMS_OVERRIDE; - if (!heldItemsOverride || heldItemsOverride.length === 0 || !scene) { + if (!heldItemsOverride || heldItemsOverride.length === 0 || !globalScene) { return; } if (!isPlayer) { - scene.clearEnemyHeldItemModifiers(pokemon); + globalScene.clearEnemyHeldItemModifiers(pokemon); } heldItemsOverride.forEach(item => { @@ -3775,9 +3765,9 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer heldItemModifier.pokemonId = pokemon.id; heldItemModifier.stackCount = qty; if (isPlayer) { - scene.addModifier(heldItemModifier, true, false, false, true); + globalScene.addModifier(heldItemModifier, true, false, false, true); } else { - scene.addEnemyModifier(heldItemModifier, true, true); + globalScene.addEnemyModifier(heldItemModifier, true, true); } } }); diff --git a/src/overrides.ts b/src/overrides.ts index 85be47d95cc..db54095a75a 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,19 +1,19 @@ import { type PokeballCounts } from "#app/battle-scene"; -import { Gender } from "#app/data/gender"; -import { Variant } from "#app/data/variant"; +import type { Gender } from "#app/data/gender"; +import type { Variant } from "#app/data/variant"; import { type ModifierOverride } from "#app/modifier/modifier-type"; -import { Unlockables } from "#app/system/unlockables"; +import type { Unlockables } from "#app/system/unlockables"; import { Abilities } from "#enums/abilities"; import { Biome } from "#enums/biome"; -import { EggTier } from "#enums/egg-type"; -import { Moves } from "#enums/moves"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { EggTier } from "#enums/egg-type"; +import type { Moves } from "#enums/moves"; +import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokeballType } from "#enums/pokeball"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; import { StatusEffect } from "#enums/status-effect"; -import { TimeOfDay } from "#enums/time-of-day"; -import { VariantTier } from "#enums/variant-tier"; +import type { TimeOfDay } from "#enums/time-of-day"; +import type { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; /** diff --git a/src/phase.ts b/src/phase.ts index 5cf91f2c478..8da00d78b61 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -1,19 +1,13 @@ -import BattleScene from "./battle-scene"; +import { globalScene } from "#app/global-scene"; export class Phase { - protected scene: BattleScene; - - constructor(scene: BattleScene) { - this.scene = scene; - } - start() { - if (this.scene.abilityBar.shown) { - this.scene.abilityBar.resetAutoHideTimer(); + if (globalScene.abilityBar.shown) { + globalScene.abilityBar.resetAutoHideTimer(); } } end() { - this.scene.shiftPhase(); + globalScene.shiftPhase(); } } diff --git a/src/phases/add-enemy-buff-modifier-phase.ts b/src/phases/add-enemy-buff-modifier-phase.ts index 451e6e2662c..d79b4f6eca5 100644 --- a/src/phases/add-enemy-buff-modifier-phase.ts +++ b/src/phases/add-enemy-buff-modifier-phase.ts @@ -1,26 +1,26 @@ -import BattleScene from "#app/battle-scene"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { regenerateModifierPoolThresholds, ModifierPoolType, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type"; import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { Phase } from "#app/phase"; +import { globalScene } from "#app/global-scene"; export class AddEnemyBuffModifierPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - const waveIndex = this.scene.currentBattle.waveIndex; + const waveIndex = globalScene.currentBattle.waveIndex; const tier = !(waveIndex % 1000) ? ModifierTier.ULTRA : !(waveIndex % 250) ? ModifierTier.GREAT : ModifierTier.COMMON; - regenerateModifierPoolThresholds(this.scene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); + regenerateModifierPoolThresholds(globalScene.getEnemyParty(), ModifierPoolType.ENEMY_BUFF); const count = Math.ceil(waveIndex / 250); for (let i = 0; i < count; i++) { - this.scene.addEnemyModifier(getEnemyBuffModifierForWave(tier, this.scene.findModifiers(m => m instanceof EnemyPersistentModifier, false), this.scene), true, true); + globalScene.addEnemyModifier(getEnemyBuffModifierForWave(tier, globalScene.findModifiers(m => m instanceof EnemyPersistentModifier, false)), true, true); } - this.scene.updateModifiers(false, true).then(() => this.end()); + globalScene.updateModifiers(false, true).then(() => this.end()); } } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index de10d1eca45..6f354d7c74a 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -1,30 +1,31 @@ import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { SubstituteTag } from "#app/data/battler-tags"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, getCriticalCaptureChance } from "#app/data/pokeball"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonPhase } from "#app/phases/pokemon-phase"; import { VictoryPhase } from "#app/phases/victory-phase"; import { achvs } from "#app/system/achv"; -import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; +import type { PartyOption } from "#app/ui/party-ui-handler"; +import { PartyUiMode } from "#app/ui/party-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { Mode } from "#app/ui/ui"; -import { PokeballType } from "#enums/pokeball"; +import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; export class AttemptCapturePhase extends PokemonPhase { private pokeballType: PokeballType; private pokeball: Phaser.GameObjects.Sprite; private originalY: number; - constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { - super(scene, BattlerIndex.ENEMY + targetIndex); + constructor(targetIndex: integer, pokeballType: PokeballType) { + super(BattlerIndex.ENEMY + targetIndex); this.pokeballType = pokeballType; } @@ -43,7 +44,7 @@ export class AttemptCapturePhase extends PokemonPhase { substitute.sprite.setVisible(false); } - this.scene.pokeballCounts[this.pokeballType]--; + globalScene.pokeballCounts[this.pokeballType]--; this.originalY = pokemon.y; @@ -54,21 +55,21 @@ export class AttemptCapturePhase extends PokemonPhase { const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; const modifiedCatchRate = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier); const shakeProbability = Math.round(65536 / Math.pow((255 / modifiedCatchRate), 0.1875)); // Formula taken from gen 6 - const criticalCaptureChance = getCriticalCaptureChance(this.scene, modifiedCatchRate); + const criticalCaptureChance = getCriticalCaptureChance(modifiedCatchRate); const isCritical = pokemon.randSeedInt(256) < criticalCaptureChance; const fpOffset = pokemon.getFieldPositionOffset(); const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); - this.pokeball = this.scene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); + this.pokeball = globalScene.addFieldSprite(16, 80, "pb", pokeballAtlasKey); this.pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(this.pokeball); + globalScene.field.add(this.pokeball); - this.scene.playSound("se/pb_throw", isCritical ? { rate: 0.2 } : undefined); // Crit catch throws are higher pitched - this.scene.time.delayedCall(300, () => { - this.scene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); + globalScene.playSound("se/pb_throw", isCritical ? { rate: 0.2 } : undefined); // Crit catch throws are higher pitched + globalScene.time.delayedCall(300, () => { + globalScene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); }); - this.scene.tweens.add({ + globalScene.tweens.add({ // Throw animation targets: this.pokeball, x: { value: 236 + fpOffset[0], ease: "Linear" }, @@ -77,13 +78,13 @@ export class AttemptCapturePhase extends PokemonPhase { onComplete: () => { // Ball opens this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - this.scene.playSound("se/pb_rel"); + globalScene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + globalScene.playSound("se/pb_rel"); pokemon.tint(getPokeballTintColor(this.pokeballType)); - addPokeballOpenParticles(this.scene, this.pokeball.x, this.pokeball.y, this.pokeballType); + addPokeballOpenParticles(this.pokeball.x, this.pokeball.y, this.pokeballType); - this.scene.tweens.add({ + globalScene.tweens.add({ // Mon enters ball targets: pokemon, duration: 500, @@ -94,14 +95,14 @@ export class AttemptCapturePhase extends PokemonPhase { // Ball closes this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); pokemon.setVisible(false); - this.scene.playSound("se/pb_catch"); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); + globalScene.playSound("se/pb_catch"); + globalScene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}`)); const doShake = () => { // After the overall catch rate check, the game does 3 shake checks before confirming the catch. let shakeCount = 0; const pbX = this.pokeball.x; - const shakeCounter = this.scene.tweens.addCounter({ + const shakeCounter = globalScene.tweens.addCounter({ from: 0, to: 1, repeat: isCritical ? 2 : 4, // Critical captures only perform 1 shake check @@ -124,7 +125,7 @@ export class AttemptCapturePhase extends PokemonPhase { } else if (shakeCount++ < (isCritical ? 1 : 3)) { // Shake check (skip check for critical or guaranteed captures, but still play the sound) if (pokeballMultiplier === -1 || isCritical || modifiedCatchRate >= 255 || pokemon.randSeedInt(65536) < shakeProbability) { - this.scene.playSound("se/pb_move"); + globalScene.playSound("se/pb_move"); } else { shakeCounter.stop(); this.failCatch(shakeCount); @@ -134,21 +135,21 @@ export class AttemptCapturePhase extends PokemonPhase { shakeCounter.stop(); this.failCatch(shakeCount); } else { - this.scene.playSound("se/pb_lock"); - addPokeballCaptureStars(this.scene, this.pokeball); + globalScene.playSound("se/pb_lock"); + addPokeballCaptureStars(this.pokeball); - const pbTint = this.scene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); + const pbTint = globalScene.add.sprite(this.pokeball.x, this.pokeball.y, "pb", "pb"); pbTint.setOrigin(this.pokeball.originX, this.pokeball.originY); pbTint.setTintFill(0); pbTint.setAlpha(0); - this.scene.field.add(pbTint); - this.scene.tweens.add({ + globalScene.field.add(pbTint); + globalScene.tweens.add({ targets: pbTint, alpha: 0.375, duration: 200, easing: "Sine.easeOut", onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pbTint, alpha: 0, duration: 200, @@ -166,7 +167,7 @@ export class AttemptCapturePhase extends PokemonPhase { }; // Ball bounces (handled in pokemon.ts) - this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake, isCritical)); + globalScene.time.delayedCall(250, () => doPokeballBounceAnim(this.pokeball, 16, 72, 350, doShake, isCritical)); } }); } @@ -176,7 +177,7 @@ export class AttemptCapturePhase extends PokemonPhase { failCatch(shakeCount: integer) { const pokemon = this.getPokemon(); - this.scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokemon.setY(this.originalY); if (pokemon.status?.effect !== StatusEffect.SLEEP) { pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); @@ -192,16 +193,16 @@ export class AttemptCapturePhase extends PokemonPhase { const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType); this.pokeball.setTexture("pb", `${pokeballAtlasKey}_opening`); - this.scene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); + globalScene.time.delayedCall(17, () => this.pokeball.setTexture("pb", `${pokeballAtlasKey}_open`)); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeOut", scale: 1 }); - this.scene.currentBattle.lastUsedPokeball = this.pokeballType; + globalScene.currentBattle.lastUsedPokeball = this.pokeballType; this.removePb(); this.end(); } @@ -212,48 +213,48 @@ export class AttemptCapturePhase extends PokemonPhase { const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { - this.scene.validateAchv(achvs.HIDDEN_ABILITY); + globalScene.validateAchv(achvs.HIDDEN_ABILITY); } if (pokemon.species.subLegendary) { - this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_SUB_LEGENDARY); } if (pokemon.species.legendary) { - this.scene.validateAchv(achvs.CATCH_LEGENDARY); + globalScene.validateAchv(achvs.CATCH_LEGENDARY); } if (pokemon.species.mythical) { - this.scene.validateAchv(achvs.CATCH_MYTHICAL); + globalScene.validateAchv(achvs.CATCH_MYTHICAL); } - this.scene.pokemonInfoContainer.show(pokemon, true); + globalScene.pokemonInfoContainer.show(pokemon, true); - this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + globalScene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); - this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + globalScene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { const end = () => { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - this.scene.pokemonInfoContainer.hide(); + globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); + globalScene.pokemonInfoContainer.hide(); this.removePb(); this.end(); }; const removePokemon = () => { - this.scene.addFaintedEnemyScore(pokemon); - this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); + globalScene.addFaintedEnemyScore(pokemon); + globalScene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); - this.scene.clearEnemyHeldItemModifiers(); - this.scene.field.remove(pokemon, true); + globalScene.clearEnemyHeldItemModifiers(); + globalScene.field.remove(pokemon, true); }; const addToParty = (slotIndex?: number) => { const newPokemon = pokemon.addToParty(this.pokeballType, slotIndex); - const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); - if (this.scene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { - this.scene.validateAchv(achvs.SHINY_PARTY); + const modifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier, false); + if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { + globalScene.validateAchv(achvs.SHINY_PARTY); } - Promise.all(modifiers.map(m => this.scene.addModifier(m, true))).then(() => { - this.scene.updateModifiers(true); + Promise.all(modifiers.map(m => globalScene.addModifier(m, true))).then(() => { + globalScene.updateModifiers(true); removePokemon(); if (newPokemon) { newPokemon.loadAssets().then(end); @@ -262,21 +263,21 @@ export class AttemptCapturePhase extends PokemonPhase { } }); }; - Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => { - if (this.scene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { + Promise.all([ pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon) ]).then(() => { + if (globalScene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { const promptRelease = () => { - this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { - this.scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); - this.scene.ui.setMode(Mode.CONFIRM, () => { - const newPokemon = this.scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); - this.scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { + globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true); + globalScene.ui.setMode(Mode.CONFIRM, () => { + const newPokemon = globalScene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon); + globalScene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { promptRelease(); }); }, false); }, () => { - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { addToParty(slotIndex); } else { @@ -285,7 +286,7 @@ export class AttemptCapturePhase extends PokemonPhase { }); }); }, () => { - this.scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { removePokemon(); end(); }); @@ -301,7 +302,7 @@ export class AttemptCapturePhase extends PokemonPhase { } removePb() { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokeball, duration: 250, delay: 250, diff --git a/src/phases/attempt-run-phase.ts b/src/phases/attempt-run-phase.ts index 109fc5b582d..72a108c1991 100644 --- a/src/phases/attempt-run-phase.ts +++ b/src/phases/attempt-run-phase.ts @@ -1,28 +1,29 @@ -import BattleScene from "#app/battle-scene"; import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability"; import { Stat } from "#app/enums/stat"; import { StatusEffect } from "#app/enums/status-effect"; -import Pokemon, { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import i18next from "i18next"; import * as Utils from "#app/utils"; import { BattleEndPhase } from "./battle-end-phase"; import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; +import { globalScene } from "#app/global-scene"; export class AttemptRunPhase extends PokemonPhase { /** For testing purposes: this is to force the pokemon to fail and escape */ public forceFailEscape = false; - constructor(scene: BattleScene, fieldIndex: number) { - super(scene, fieldIndex); + constructor(fieldIndex: number) { + super(fieldIndex); } start() { super.start(); - const playerField = this.scene.getPlayerField(); - const enemyField = this.scene.getEnemyField(); + const playerField = globalScene.getPlayerField(); + const enemyField = globalScene.getEnemyField(); const playerPokemon = this.getPokemon(); @@ -33,18 +34,18 @@ export class AttemptRunPhase extends PokemonPhase { applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance); if (playerPokemon.randSeedInt(100) < escapeChance.value && !this.forceFailEscape) { - this.scene.playSound("se/flee"); - this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); + globalScene.playSound("se/flee"); + globalScene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); - this.scene.tweens.add({ - targets: [ this.scene.arenaEnemy, enemyField ].flat(), + globalScene.tweens.add({ + targets: [ globalScene.arenaEnemy, enemyField ].flat(), alpha: 0, duration: 250, ease: "Sine.easeIn", onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy()) }); - this.scene.clearEnemyHeldItemModifiers(); + globalScene.clearEnemyHeldItemModifiers(); enemyField.forEach(enemyPokemon => { enemyPokemon.hideInfo().then(() => enemyPokemon.destroy()); @@ -52,11 +53,11 @@ export class AttemptRunPhase extends PokemonPhase { enemyPokemon.trySetStatus(StatusEffect.FAINT); }); - this.scene.pushPhase(new BattleEndPhase(this.scene, false)); - this.scene.pushPhase(new NewBattlePhase(this.scene)); + globalScene.pushPhase(new BattleEndPhase(false)); + globalScene.pushPhase(new NewBattlePhase()); } else { playerPokemon.turnData.failedRunAway = true; - this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); + globalScene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500); } this.end(); @@ -103,6 +104,6 @@ export class AttemptRunPhase extends PokemonPhase { const escapeSlope = (maxChance - minChance) / speedCap; // This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`] - escapeChance.value = Phaser.Math.Clamp(Math.round((escapeSlope * speedRatio) + minChance + (escapeBonus * this.scene.currentBattle.escapeAttempts++)), minChance, maxChance); + escapeChance.value = Phaser.Math.Clamp(Math.round((escapeSlope * speedRatio) + minChance + (escapeBonus * globalScene.currentBattle.escapeAttempts++)), minChance, maxChance); } } diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 8926a2211e0..edffd8498d6 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/ability"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; @@ -8,8 +8,8 @@ export class BattleEndPhase extends BattlePhase { /** If true, will increment battles won */ isVictory: boolean; - constructor(scene: BattleScene, isVictory: boolean) { - super(scene); + constructor(isVictory: boolean) { + super(); this.isVictory = isVictory; } @@ -17,52 +17,52 @@ export class BattleEndPhase extends BattlePhase { start() { super.start(); - this.scene.gameData.gameStats.battles++; - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex + 1 > this.scene.gameData.gameStats.highestEndlessWave) { - this.scene.gameData.gameStats.highestEndlessWave = this.scene.currentBattle.waveIndex + 1; + globalScene.gameData.gameStats.battles++; + if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex + 1 > globalScene.gameData.gameStats.highestEndlessWave) { + globalScene.gameData.gameStats.highestEndlessWave = globalScene.currentBattle.waveIndex + 1; } if (this.isVictory) { - this.scene.currentBattle.addBattleScore(this.scene); + globalScene.currentBattle.addBattleScore(); - if (this.scene.currentBattle.trainer) { - this.scene.gameData.gameStats.trainersDefeated++; + if (globalScene.currentBattle.trainer) { + globalScene.gameData.gameStats.trainersDefeated++; } } // Endless graceful end - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex >= 5850) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene, true)); + if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex >= 5850) { + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase(true)); } - for (const pokemon of this.scene.getField()) { + for (const pokemon of globalScene.getField()) { if (pokemon && pokemon.battleSummonData) { pokemon.battleSummonData.waveTurnCount = 1; } } - for (const pokemon of this.scene.getPokemonAllowedInBattle()) { + for (const pokemon of globalScene.getPokemonAllowedInBattle()) { applyPostBattleAbAttrs(PostBattleAbAttr, pokemon, false, this.isVictory); } - if (this.scene.currentBattle.moneyScattered) { - this.scene.currentBattle.pickUpScatteredMoney(this.scene); + if (globalScene.currentBattle.moneyScattered) { + globalScene.currentBattle.pickUpScatteredMoney(); } - this.scene.clearEnemyHeldItemModifiers(); + globalScene.clearEnemyHeldItemModifiers(); - const lapsingModifiers = this.scene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; + const lapsingModifiers = globalScene.findModifiers(m => m instanceof LapsingPersistentModifier || m instanceof LapsingPokemonHeldItemModifier) as (LapsingPersistentModifier | LapsingPokemonHeldItemModifier)[]; for (const m of lapsingModifiers) { const args: any[] = []; if (m instanceof LapsingPokemonHeldItemModifier) { - args.push(this.scene.getPokemonById(m.pokemonId)); + args.push(globalScene.getPokemonById(m.pokemonId)); } if (!m.lapse(...args)) { - this.scene.removeModifier(m); + globalScene.removeModifier(m); } } - this.scene.updateModifiers().then(() => this.end()); + globalScene.updateModifiers().then(() => this.end()); } } diff --git a/src/phases/battle-phase.ts b/src/phases/battle-phase.ts index 11807fdc714..4fc826b7957 100644 --- a/src/phases/battle-phase.ts +++ b/src/phases/battle-phase.ts @@ -1,15 +1,15 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { TrainerSlot } from "#app/data/trainer-config"; import { Phase } from "#app/phase"; export class BattlePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void { - const sprites = this.scene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? - const tintSprites = this.scene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? + const sprites = globalScene.currentBattle.trainer?.getSprites()!; // TODO: is this bang correct? + const tintSprites = globalScene.currentBattle.trainer?.getTintSprites()!; // TODO: is this bang correct? for (let i = 0; i < sprites.length; i++) { const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; [ sprites[i], tintSprites[i] ].map(sprite => { @@ -24,8 +24,8 @@ export class BattlePhase extends Phase { sprites[i].clearTint(); tintSprites[i].clearTint(); } - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, + globalScene.tweens.add({ + targets: globalScene.currentBattle.trainer, x: "-=16", y: "+=16", alpha: 1, @@ -35,8 +35,8 @@ export class BattlePhase extends Phase { } hideEnemyTrainer(): void { - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, + globalScene.tweens.add({ + targets: globalScene.currentBattle.trainer, x: "+=16", y: "-=16", alpha: 0, diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 5c33ae4b343..dd56e8b37d7 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -7,6 +7,7 @@ import i18next from "i18next"; import * as Utils from "#app/utils"; import { FieldPhase } from "./field-phase"; import { CommonAnimPhase } from "./common-anim-phase"; +import { globalScene } from "#app/global-scene"; /** The phase after attacks where the pokemon eat berries */ export class BerryPhase extends FieldPhase { @@ -14,7 +15,7 @@ export class BerryPhase extends FieldPhase { super.start(); this.executeForAll((pokemon) => { - const hasUsableBerry = !!this.scene.findModifier((m) => { + const hasUsableBerry = !!globalScene.findModifier((m) => { return m instanceof BerryModifier && m.shouldApply(pokemon); }, pokemon.isPlayer()); @@ -23,21 +24,21 @@ export class BerryPhase extends FieldPhase { pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled)); if (cancelled.value) { - pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + globalScene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); } else { - this.scene.unshiftPhase( - new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) + globalScene.unshiftPhase( + new CommonAnimPhase(pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM) ); - for (const berryModifier of this.scene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { + for (const berryModifier of globalScene.applyModifiers(BerryModifier, pokemon.isPlayer(), pokemon)) { if (berryModifier.consumed) { berryModifier.consumed = false; pokemon.loseHeldItem(berryModifier); } - this.scene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used + globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); // Announce a berry was used } - this.scene.updateModifiers(pokemon.isPlayer()); + globalScene.updateModifiers(pokemon.isPlayer()); applyAbAttrs(HealFromBerryUseAbAttr, pokemon, new Utils.BooleanHolder(false)); } diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts index 44918b54966..683c1ea1cd2 100644 --- a/src/phases/check-status-effect-phase.ts +++ b/src/phases/check-status-effect-phase.ts @@ -1,21 +1,20 @@ import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; import { Phase } from "#app/phase"; -import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import type { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; export class CheckStatusEffectPhase extends Phase { private order : BattlerIndex[]; - constructor(scene : BattleScene, order : BattlerIndex[]) { - super(scene); - this.scene = scene; + constructor(order : BattlerIndex[]) { + super(); this.order = order; } start() { - const field = this.scene.getField(); + const field = globalScene.getField(); for (const o of this.order) { if (field[o].status && field[o].status.isPostTurn()) { - this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o)); + globalScene.unshiftPhase(new PostTurnStatusEffectPhase(o)); } } this.end(); diff --git a/src/phases/check-switch-phase.ts b/src/phases/check-switch-phase.ts index 18b999ed210..06e08f7f30d 100644 --- a/src/phases/check-switch-phase.ts +++ b/src/phases/check-switch-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { BattleStyle } from "#app/enums/battle-style"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -13,8 +13,8 @@ export class CheckSwitchPhase extends BattlePhase { protected fieldIndex: integer; protected useName: boolean; - constructor(scene: BattleScene, fieldIndex: integer, useName: boolean) { - super(scene); + constructor(fieldIndex: integer, useName: boolean) { + super(); this.fieldIndex = fieldIndex; this.useName = useName; @@ -23,40 +23,40 @@ export class CheckSwitchPhase extends BattlePhase { start() { super.start(); - const pokemon = this.scene.getPlayerField()[this.fieldIndex]; + const pokemon = globalScene.getPlayerField()[this.fieldIndex]; // End this phase early... // ...if the user is playing in Set Mode - if (this.scene.battleStyle === BattleStyle.SET) { + if (globalScene.battleStyle === BattleStyle.SET) { return super.end(); } // ...if the checked Pokemon is somehow not on the field - if (this.scene.field.getAll().indexOf(pokemon) === -1) { - this.scene.unshiftPhase(new SummonMissingPhase(this.scene, this.fieldIndex)); + if (globalScene.field.getAll().indexOf(pokemon) === -1) { + globalScene.unshiftPhase(new SummonMissingPhase(this.fieldIndex)); return super.end(); } // ...if there are no other allowed Pokemon in the player's party to switch with - if (!this.scene.getPlayerParty().slice(1).filter(p => p.isActive()).length) { + if (!globalScene.getPlayerParty().slice(1).filter(p => p.isActive()).length) { return super.end(); } // ...or if any player Pokemon has an effect that prevents the checked Pokemon from switching if (pokemon.getTag(BattlerTagType.FRENZY) || pokemon.isTrapped() - || this.scene.getPlayerField().some(p => p.getTag(BattlerTagType.COMMANDED))) { + || globalScene.getPlayerField().some(p => p.getTag(BattlerTagType.COMMANDED))) { return super.end(); } - this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true)); + globalScene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => { + globalScene.ui.setMode(Mode.CONFIRM, () => { + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.unshiftPhase(new SwitchPhase(SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true)); this.end(); }, () => { - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); this.end(); }); }); diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index fa85f2427e5..d7293ec02fe 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -1,14 +1,18 @@ -import BattleScene from "#app/battle-scene"; -import { TurnCommand, BattleType } from "#app/battle"; -import { TrappedTag, EncoreTag } from "#app/data/battler-tags"; -import { MoveTargetSet, getMoveTargets } from "#app/data/move"; +import { globalScene } from "#app/global-scene"; +import type { TurnCommand } from "#app/battle"; +import { BattleType } from "#app/battle"; +import type { EncoreTag } from "#app/data/battler-tags"; +import { TrappedTag } from "#app/data/battler-tags"; +import type { MoveTargetSet } from "#app/data/move"; +import { getMoveTargets } from "#app/data/move"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; import { PokeballType } from "#enums/pokeball"; -import { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -23,8 +27,8 @@ import { ArenaTagType } from "#app/enums/arena-tag-type"; export class CommandPhase extends FieldPhase { protected fieldIndex: integer; - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); + constructor(fieldIndex: integer) { + super(); this.fieldIndex = fieldIndex; } @@ -32,17 +36,17 @@ export class CommandPhase extends FieldPhase { start() { super.start(); - this.scene.updateGameInfo(); + globalScene.updateGameInfo(); - const commandUiHandler = this.scene.ui.handlers[Mode.COMMAND]; + const commandUiHandler = globalScene.ui.handlers[Mode.COMMAND]; // If one of these conditions is true, we always reset the cursor to Command.FIGHT - const cursorResetEvent = this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || - this.scene.currentBattle.battleType === BattleType.TRAINER || - this.scene.arena.biomeType === Biome.END; + const cursorResetEvent = globalScene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER || + globalScene.currentBattle.battleType === BattleType.TRAINER || + globalScene.arena.biomeType === Biome.END; if (commandUiHandler) { - if ((this.scene.currentBattle.turn === 1 && (!this.scene.commandCursorMemory || cursorResetEvent)) || commandUiHandler.getCursor() === Command.POKEMON) { + if ((globalScene.currentBattle.turn === 1 && (!globalScene.commandCursorMemory || cursorResetEvent)) || commandUiHandler.getCursor() === Command.POKEMON) { commandUiHandler.setCursor(Command.FIGHT); } else { commandUiHandler.setCursor(commandUiHandler.getCursor()); @@ -52,19 +56,19 @@ export class CommandPhase extends FieldPhase { if (this.fieldIndex) { // If we somehow are attempting to check the right pokemon but there's only one pokemon out // Switch back to the center pokemon. This can happen rarely in double battles with mid turn switching - if (this.scene.getPlayerField().filter(p => p.isActive()).length === 1) { + if (globalScene.getPlayerField().filter(p => p.isActive()).length === 1) { this.fieldIndex = FieldPosition.CENTER; } else { - const allyCommand = this.scene.currentBattle.turnCommands[this.fieldIndex - 1]; + const allyCommand = globalScene.currentBattle.turnCommands[this.fieldIndex - 1]; if (allyCommand?.command === Command.BALL || allyCommand?.command === Command.RUN) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; + globalScene.currentBattle.turnCommands[this.fieldIndex] = { command: allyCommand?.command, skip: true }; } } } // If the Pokemon has applied Commander's effects to its ally, skip this command - if (this.scene.currentBattle?.double && this.getPokemon().getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(this.scene) === this.getPokemon()) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, move: { move: Moves.NONE, targets: []}, skip: true }; + if (globalScene.currentBattle?.double && this.getPokemon().getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === this.getPokemon()) { + globalScene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, move: { move: Moves.NONE, targets: []}, skip: true }; } // Checks if the Pokemon is under the effects of Encore. If so, Encore can end early if the encored move has no more PP. @@ -73,11 +77,11 @@ export class CommandPhase extends FieldPhase { this.getPokemon().lapseTag(BattlerTagType.ENCORE); } - if (this.scene.currentBattle.turnCommands[this.fieldIndex]?.skip) { + if (globalScene.currentBattle.turnCommands[this.fieldIndex]?.skip) { return this.end(); } - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; const moveQueue = playerPokemon.getMoveQueue(); @@ -96,21 +100,21 @@ export class CommandPhase extends FieldPhase { if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } } } else { - if (this.scene.currentBattle.isBattleMysteryEncounter() && this.scene.currentBattle.mysteryEncounter?.skipToFightInput) { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + if (globalScene.currentBattle.isBattleMysteryEncounter() && globalScene.currentBattle.mysteryEncounter?.skipToFightInput) { + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.FIGHT, this.fieldIndex); } else { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } } } handleCommand(command: Command, cursor: integer, ...args: any[]): boolean { - const playerPokemon = this.scene.getPlayerField()[this.fieldIndex]; + const playerPokemon = globalScene.getPlayerField()[this.fieldIndex]; let success: boolean = false; switch (command) { @@ -127,20 +131,20 @@ export class CommandPhase extends FieldPhase { } console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); } if (moveTargets.targets.length <= 1 || moveTargets.multiple) { turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; success = true; } else if (cursor < playerPokemon.getMoveset().length) { const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); // Decides between a Disabled, Not Implemented, or No PP translation message const errorMessage = @@ -149,58 +153,58 @@ export class CommandPhase extends FieldPhase { : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + globalScene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.FIGHT, this.fieldIndex); }, null, true); } break; case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + const notInDex = (globalScene.getEnemyField().filter(p => p.isActive(true)).some(p => !globalScene.gameData.dexData[p.species.speciesId].caughtAttr) && globalScene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); + if (globalScene.arena.biomeType === Biome.END && (!globalScene.gameMode.isClassic || globalScene.gameMode.isFreshStartChallenge() || notInDex )) { + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } else if (globalScene.currentBattle.battleType === BattleType.TRAINER) { + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } else if (globalScene.currentBattle.isBattleMysteryEncounter() && !globalScene.currentBattle.mysteryEncounter!.catchAllowed) { + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + const targets = globalScene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); if (targets.length > 1) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + const targetPokemon = globalScene.getEnemyField().find(p => p.isActive(true)); if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; - this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; + globalScene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + globalScene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; if (this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } success = true; } @@ -210,21 +214,21 @@ export class CommandPhase extends FieldPhase { case Command.POKEMON: case Command.RUN: const isSwitch = command === Command.POKEMON; - const { currentBattle, arena } = this.scene; + const { currentBattle, arena } = globalScene; const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + globalScene.ui.showText("", 0); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { const batonPass = isSwitch && args[0] as boolean; @@ -239,38 +243,38 @@ export class CommandPhase extends FieldPhase { } } else if (trappedAbMessages.length > 0) { if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); } - this.scene.ui.showText(trappedAbMessages[0], null, () => { - this.scene.ui.showText("", 0); + globalScene.ui.showText(trappedAbMessages[0], null, () => { + globalScene.ui.showText("", 0); if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, true); } else { const trapTag = playerPokemon.getTag(TrappedTag); - const fairyLockTag = playerPokemon.scene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER); + const fairyLockTag = globalScene.arena.getTagOnSide(ArenaTagType.FAIRY_LOCK, ArenaTagSide.PLAYER); if (!trapTag && !fairyLockTag) { i18next.t(`battle:noEscape${isSwitch ? "Switch" : "Flee"}`); break; } if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.MESSAGE); } const showNoEscapeText = (tag: any) => { - this.scene.ui.showText( + globalScene.ui.showText( i18next.t("battle:noEscapePokemon", { - pokemonName: tag.sourceId && this.scene.getPokemonById(tag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(tag.sourceId)!) : "", + pokemonName: tag.sourceId && globalScene.getPokemonById(tag.sourceId) ? getPokemonNameWithAffix(globalScene.getPokemonById(tag.sourceId)!) : "", moveName: tag.getMoveName(), escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") }), null, () => { - this.scene.ui.showText("", 0); + globalScene.ui.showText("", 0); if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, @@ -297,8 +301,8 @@ export class CommandPhase extends FieldPhase { cancel() { if (this.fieldIndex) { - this.scene.unshiftPhase(new CommandPhase(this.scene, 0)); - this.scene.unshiftPhase(new CommandPhase(this.scene, 1)); + globalScene.unshiftPhase(new CommandPhase(0)); + globalScene.unshiftPhase(new CommandPhase(1)); this.end(); } } @@ -308,10 +312,10 @@ export class CommandPhase extends FieldPhase { } getPokemon(): PlayerPokemon { - return this.scene.getPlayerField()[this.fieldIndex]; + return globalScene.getPlayerField()[this.fieldIndex]; } end() { - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + globalScene.ui.setMode(Mode.MESSAGE).then(() => super.end()); } } diff --git a/src/phases/common-anim-phase.ts b/src/phases/common-anim-phase.ts index c4071488eef..53cbdfaeb38 100644 --- a/src/phases/common-anim-phase.ts +++ b/src/phases/common-anim-phase.ts @@ -1,6 +1,7 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; -import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; +import type { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { CommonAnim } from "#app/data/battle-anims"; +import { CommonBattleAnim } from "#app/data/battle-anims"; import { PokemonPhase } from "./pokemon-phase"; export class CommonAnimPhase extends PokemonPhase { @@ -8,8 +9,8 @@ export class CommonAnimPhase extends PokemonPhase { private targetIndex: integer | undefined; private playOnEmptyField: boolean; - constructor(scene: BattleScene, battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex | undefined, anim?: CommonAnim, playOnEmptyField: boolean = false) { - super(scene, battlerIndex); + constructor(battlerIndex?: BattlerIndex, targetIndex?: BattlerIndex, anim?: CommonAnim, playOnEmptyField: boolean = false) { + super(battlerIndex); this.anim = anim!; // TODO: is this bang correct? this.targetIndex = targetIndex; @@ -21,8 +22,8 @@ export class CommonAnimPhase extends PokemonPhase { } start() { - const target = this.targetIndex !== undefined ? (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField())[this.targetIndex] : this.getPokemon(); - new CommonBattleAnim(this.anim, this.getPokemon(), target).play(this.scene, false, () => { + const target = this.targetIndex !== undefined ? (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField())[this.targetIndex] : this.getPokemon(); + new CommonBattleAnim(this.anim, this.getPokemon(), target).play(false, () => { this.end(); }); } diff --git a/src/phases/damage-anim-phase.ts b/src/phases/damage-anim-phase.ts index 42f0e1ba845..2983d6b2de0 100644 --- a/src/phases/damage-anim-phase.ts +++ b/src/phases/damage-anim-phase.ts @@ -1,4 +1,4 @@ -import type BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { type BattlerIndex } from "#app/battle"; import { BattleSpec } from "#enums/battle-spec"; import { type DamageResult, HitResult } from "#app/field/pokemon"; @@ -10,8 +10,8 @@ export class DamageAnimPhase extends PokemonPhase { private damageResult: DamageResult; private critical: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, amount: integer, damageResult?: DamageResult, critical: boolean = false) { + super(battlerIndex); this.amount = amount; this.damageResult = damageResult || HitResult.EFFECTIVE; @@ -22,11 +22,11 @@ export class DamageAnimPhase extends PokemonPhase { super.start(); if (this.damageResult === HitResult.ONE_HIT_KO) { - if (this.scene.moveAnimations) { - this.scene.toggleInvert(true); + if (globalScene.moveAnimations) { + globalScene.toggleInvert(true); } - this.scene.time.delayedCall(fixedInt(1000), () => { - this.scene.toggleInvert(false); + globalScene.time.delayedCall(fixedInt(1000), () => { + globalScene.toggleInvert(false); this.applyDamage(); }); return; @@ -42,23 +42,23 @@ export class DamageAnimPhase extends PokemonPhase { applyDamage() { switch (this.damageResult) { case HitResult.EFFECTIVE: - this.scene.playSound("se/hit"); + globalScene.playSound("se/hit"); break; case HitResult.SUPER_EFFECTIVE: case HitResult.ONE_HIT_KO: - this.scene.playSound("se/hit_strong"); + globalScene.playSound("se/hit_strong"); break; case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("se/hit_weak"); + globalScene.playSound("se/hit_weak"); break; } if (this.amount) { - this.scene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); + globalScene.damageNumberHandler.add(this.getPokemon(), this.amount, this.damageResult, this.critical); } if (this.damageResult !== HitResult.OTHER && this.amount > 0) { - const flashTimer = this.scene.time.addEvent({ + const flashTimer = globalScene.time.addEvent({ delay: 100, repeat: 5, startAt: 200, @@ -75,8 +75,8 @@ export class DamageAnimPhase extends PokemonPhase { } override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + globalScene.initFinalBossPhaseTwo(this.getPokemon()); } else { super.end(); } diff --git a/src/phases/egg-hatch-phase.ts b/src/phases/egg-hatch-phase.ts index 803fd478fd4..11bf9584ee7 100644 --- a/src/phases/egg-hatch-phase.ts +++ b/src/phases/egg-hatch-phase.ts @@ -1,19 +1,20 @@ -import BattleScene, { AnySound } from "#app/battle-scene"; -import { Egg } from "#app/data/egg"; +import type { AnySound } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { Egg } from "#app/data/egg"; import { EggCountChangedEvent } from "#app/events/egg"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Phase } from "#app/phase"; import { achvs } from "#app/system/achv"; import EggCounterContainer from "#app/ui/egg-counter-container"; -import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler"; +import type EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler"; import PokemonInfoContainer from "#app/ui/pokemon-info-container"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import * as Utils from "#app/utils"; -import { EggLapsePhase } from "./egg-lapse-phase"; -import { EggHatchData } from "#app/data/egg-hatch-data"; +import type { EggLapsePhase } from "./egg-lapse-phase"; +import type { EggHatchData } from "#app/data/egg-hatch-data"; import { doShinySparkleAnim } from "#app/field/anims"; @@ -67,8 +68,8 @@ export class EggHatchPhase extends Phase { private evolutionBgm: AnySound; private eggLapsePhase: EggLapsePhase; - constructor(scene: BattleScene, hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: integer) { - super(scene); + constructor(hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: integer) { + super(); this.eggLapsePhase = hatchScene; this.egg = egg; this.eggsToHatchCount = eggsToHatchCount; @@ -77,37 +78,37 @@ export class EggHatchPhase extends Phase { start() { super.start(); - this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SCENE).then(() => { + globalScene.ui.setModeForceTransition(Mode.EGG_HATCH_SCENE).then(() => { if (!this.egg) { return this.end(); } - const eggIndex = this.scene.gameData.eggs.findIndex(e => e.id === this.egg.id); + const eggIndex = globalScene.gameData.eggs.findIndex(e => e.id === this.egg.id); if (eggIndex === -1) { return this.end(); } - this.scene.gameData.eggs.splice(eggIndex, 1); + globalScene.gameData.eggs.splice(eggIndex, 1); - this.scene.fadeOutBgm(undefined, false); + globalScene.fadeOutBgm(undefined, false); - this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler; + this.eggHatchHandler = globalScene.ui.getHandler() as EggHatchSceneHandler; this.eggHatchContainer = this.eggHatchHandler.eggHatchContainer; - this.eggHatchBg = this.scene.add.image(0, 0, "default_bg"); + this.eggHatchBg = globalScene.add.image(0, 0, "default_bg"); this.eggHatchBg.setOrigin(0, 0); this.eggHatchContainer.add(this.eggHatchBg); - this.eggContainer = this.scene.add.container(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2); + this.eggContainer = globalScene.add.container(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2); - this.eggSprite = this.scene.add.sprite(0, 0, "egg", `egg_${this.egg.getKey()}`); - this.eggCrackSprite = this.scene.add.sprite(0, 0, "egg_crack", "0"); + this.eggSprite = globalScene.add.sprite(0, 0, "egg", `egg_${this.egg.getKey()}`); + this.eggCrackSprite = globalScene.add.sprite(0, 0, "egg_crack", "0"); this.eggCrackSprite.setVisible(false); - this.eggLightraysOverlay = this.scene.add.sprite((-this.eggHatchBg.displayWidth / 2) + 4, -this.eggHatchBg.displayHeight / 2, "egg_lightrays", "3"); + this.eggLightraysOverlay = globalScene.add.sprite((-this.eggHatchBg.displayWidth / 2) + 4, -this.eggHatchBg.displayHeight / 2, "egg_lightrays", "3"); this.eggLightraysOverlay.setOrigin(0, 0); this.eggLightraysOverlay.setVisible(false); @@ -116,28 +117,28 @@ export class EggHatchPhase extends Phase { this.eggContainer.add(this.eggLightraysOverlay); this.eggHatchContainer.add(this.eggContainer); - this.eggCounterContainer = new EggCounterContainer(this.scene, this.eggsToHatchCount); + this.eggCounterContainer = new EggCounterContainer(this.eggsToHatchCount); this.eggHatchContainer.add(this.eggCounterContainer); const getPokemonSprite = () => { - const ret = this.scene.add.sprite(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2, "pkmn__sub"); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + const ret = globalScene.add.sprite(this.eggHatchBg.displayWidth / 2, this.eggHatchBg.displayHeight / 2, "pkmn__sub"); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); return ret; }; this.eggHatchContainer.add((this.pokemonSprite = getPokemonSprite())); - this.pokemonShinySparkle = this.scene.add.sprite(this.pokemonSprite.x, this.pokemonSprite.y, "shiny"); + this.pokemonShinySparkle = globalScene.add.sprite(this.pokemonSprite.x, this.pokemonSprite.y, "shiny"); this.pokemonShinySparkle.setVisible(false); this.eggHatchContainer.add(this.pokemonShinySparkle); - this.eggHatchOverlay = this.scene.add.rectangle(0, -this.scene.game.canvas.height / 6, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0xFFFFFF); + this.eggHatchOverlay = globalScene.add.rectangle(0, -globalScene.game.canvas.height / 6, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0xFFFFFF); this.eggHatchOverlay.setOrigin(0, 0); this.eggHatchOverlay.setAlpha(0); - this.scene.fieldUI.add(this.eggHatchOverlay); + globalScene.fieldUI.add(this.eggHatchOverlay); - this.infoContainer = new PokemonInfoContainer(this.scene); + this.infoContainer = new PokemonInfoContainer(); this.infoContainer.setup(); this.eggHatchContainer.add(this.infoContainer); @@ -155,13 +156,13 @@ export class EggHatchPhase extends Phase { pokemon.loadAssets().then(() => { this.canSkip = true; - this.scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { if (!this.hatched) { - this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); + this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution"); } }); - this.scene.time.delayedCall(2000, () => { + globalScene.time.delayedCall(2000, () => { if (this.hatched) { return; } @@ -171,25 +172,25 @@ export class EggHatchPhase extends Phase { if (this.hatched) { return; } - this.scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { if (this.hatched) { return; } this.doSpray(2, this.eggSprite.displayHeight / -4); this.eggCrackSprite.setFrame("1"); - this.scene.time.delayedCall(125, () => this.eggCrackSprite.setFrame("2")); + globalScene.time.delayedCall(125, () => this.eggCrackSprite.setFrame("2")); this.doEggShake(4).then(() => { if (this.hatched) { return; } - this.scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { if (this.hatched) { return; } - this.scene.playSound("se/egg_crack"); + globalScene.playSound("se/egg_crack"); this.doSpray(4); this.eggCrackSprite.setFrame("3"); - this.scene.time.delayedCall(125, () => this.eggCrackSprite.setFrame("4")); + globalScene.time.delayedCall(125, () => this.eggCrackSprite.setFrame("4")); this.doEggShake(8, 2).then(() => { if (!this.hatched) { this.doHatch(); @@ -205,10 +206,10 @@ export class EggHatchPhase extends Phase { } end() { - if (this.scene.findPhase((p) => p instanceof EggHatchPhase)) { + if (globalScene.findPhase((p) => p instanceof EggHatchPhase)) { this.eggHatchHandler.clear(); } else { - this.scene.time.delayedCall(250, () => this.scene.setModifiersVisible(true)); + globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); } super.end(); } @@ -228,14 +229,14 @@ export class EggHatchPhase extends Phase { if (count === undefined) { count = 0; } - this.scene.playSound("se/pb_move"); - this.scene.tweens.add({ + globalScene.playSound("se/pb_move"); + globalScene.tweens.add({ targets: this.eggContainer, x: `-=${intensity / (count ? 1 : 2)}`, ease: "Sine.easeInOut", duration: 125, onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.eggContainer, x: `+=${intensity}`, ease: "Sine.easeInOut", @@ -245,7 +246,7 @@ export class EggHatchPhase extends Phase { if (count! < repeatCount!) { // we know they are defined return this.doEggShake(intensity, repeatCount, count).then(() => resolve()); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.eggContainer, x: `-=${intensity / 2}`, ease: "Sine.easeInOut", @@ -286,14 +287,14 @@ export class EggHatchPhase extends Phase { this.canSkip = false; this.hatched = true; if (this.evolutionBgm) { - SoundFade.fadeOut(this.scene, this.evolutionBgm, Utils.fixedInt(100)); + SoundFade.fadeOut(globalScene, this.evolutionBgm, Utils.fixedInt(100)); } for (let e = 0; e < 5; e++) { - this.scene.time.delayedCall(Utils.fixedInt(375 * e), () => this.scene.playSound("se/egg_hatch", { volume: 1 - (e * 0.2) })); + globalScene.time.delayedCall(Utils.fixedInt(375 * e), () => globalScene.playSound("se/egg_hatch", { volume: 1 - (e * 0.2) })); } this.eggLightraysOverlay.setVisible(true); this.eggLightraysOverlay.play("egg_lightrays"); - this.scene.tweens.add({ + globalScene.tweens.add({ duration: Utils.fixedInt(125), targets: this.eggHatchOverlay, alpha: 1, @@ -303,7 +304,7 @@ export class EggHatchPhase extends Phase { this.canSkip = true; } }); - this.scene.time.delayedCall(Utils.fixedInt(1500), () => { + globalScene.time.delayedCall(Utils.fixedInt(1500), () => { this.canSkip = false; if (!this.skipped) { this.doReveal(); @@ -318,16 +319,16 @@ export class EggHatchPhase extends Phase { // set the previous dex data so info container can show new unlocks in egg summary const isShiny = this.pokemon.isShiny(); if (this.pokemon.species.subLegendary) { - this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.HATCH_SUB_LEGENDARY); } if (this.pokemon.species.legendary) { - this.scene.validateAchv(achvs.HATCH_LEGENDARY); + globalScene.validateAchv(achvs.HATCH_LEGENDARY); } if (this.pokemon.species.mythical) { - this.scene.validateAchv(achvs.HATCH_MYTHICAL); + globalScene.validateAchv(achvs.HATCH_MYTHICAL); } if (isShiny) { - this.scene.validateAchv(achvs.HATCH_SHINY); + globalScene.validateAchv(achvs.HATCH_SHINY); } this.eggContainer.setVisible(false); const spriteKey = this.pokemon.getSpriteKey(true); @@ -341,33 +342,33 @@ export class EggHatchPhase extends Phase { this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); this.pokemonSprite.setPipelineData("variant", this.pokemon.variant); this.pokemonSprite.setVisible(true); - this.scene.time.delayedCall(Utils.fixedInt(250), () => { + globalScene.time.delayedCall(Utils.fixedInt(250), () => { this.eggsToHatchCount--; this.eggHatchHandler.eventTarget.dispatchEvent(new EggCountChangedEvent(this.eggsToHatchCount)); this.pokemon.cry(); if (isShiny) { - this.scene.time.delayedCall(Utils.fixedInt(500), () => { - doShinySparkleAnim(this.scene, this.pokemonShinySparkle, this.pokemon.variant); + globalScene.time.delayedCall(Utils.fixedInt(500), () => { + doShinySparkleAnim(this.pokemonShinySparkle, this.pokemon.variant); }); } - this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => { + globalScene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => { this.infoContainer.show(this.pokemon, false, this.skipped ? 2 : 1); - this.scene.playSoundWithoutBgm("evolution_fanfare"); + globalScene.playSoundWithoutBgm("evolution_fanfare"); - this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: getPokemonNameWithAffix(this.pokemon) }), null, () => { - this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); - this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { - this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then((value) => { + globalScene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: getPokemonNameWithAffix(this.pokemon) }), null, () => { + globalScene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs); + globalScene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => { + globalScene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then((value) => { this.eggHatchData.setEggMoveUnlocked(value); - this.scene.ui.showText("", 0); + globalScene.ui.showText("", 0); this.end(); }); }); }, null, true, 3000); }); }); - this.scene.tweens.add({ + globalScene.tweens.add({ duration: Utils.fixedInt(this.skipped ? 500 : 3000), targets: this.eggHatchOverlay, alpha: 0, @@ -391,7 +392,7 @@ export class EggHatchPhase extends Phase { * @param offsetY how much to offset the Y coordinates */ doSpray(intensity: integer, offsetY?: number) { - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: intensity, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -409,7 +410,7 @@ export class EggHatchPhase extends Phase { const initialX = this.eggHatchBg.displayWidth / 2; const initialY = this.eggHatchBg.displayHeight / 2 + offsetY; const shardKey = !this.egg.isManaphyEgg() ? this.egg.tier.toString() : "1"; - const particle = this.scene.add.image(initialX, initialY, "egg_shard", `${shardKey}_${Math.floor(trigIndex / 2)}`); + const particle = globalScene.add.image(initialX, initialY, "egg_shard", `${shardKey}_${Math.floor(trigIndex / 2)}`); this.eggHatchContainer.add(particle); let f = 0; @@ -417,7 +418,7 @@ export class EggHatchPhase extends Phase { const speed = 3 - Utils.randInt(8); const amp = 24 + Utils.randInt(32); - const particleTimer = this.scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: Utils.getFrameMs(1), onRepeat: () => { diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index 8de26ee3ef0..62da0edf5b7 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -1,12 +1,13 @@ -import BattleScene from "#app/battle-scene"; -import { Egg, EGG_SEED } from "#app/data/egg"; +import { globalScene } from "#app/global-scene"; +import type { Egg } from "#app/data/egg"; +import { EGG_SEED } from "#app/data/egg"; import { Phase } from "#app/phase"; import i18next from "i18next"; import Overrides from "#app/overrides"; import { EggHatchPhase } from "./egg-hatch-phase"; import { Mode } from "#app/ui/ui"; import { achvs } from "#app/system/achv"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { EggSummaryPhase } from "./egg-summary-phase"; import { EggHatchData } from "#app/data/egg-hatch-data"; @@ -18,24 +19,24 @@ export class EggLapsePhase extends Phase { private eggHatchData: EggHatchData[] = []; private readonly minEggsToSkip: number = 2; - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => { + const eggsToHatch: Egg[] = globalScene.gameData.eggs.filter((egg: Egg) => { return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1; }); const eggsToHatchCount: number = eggsToHatch.length; this.eggHatchData = []; if (eggsToHatchCount > 0) { - if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) { - this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { + if (eggsToHatchCount >= this.minEggsToSkip && globalScene.eggSkipPreference === 1) { + globalScene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { // show prompt for skip, blocking inputs for 1 second - this.scene.ui.showText(i18next.t("battle:eggSkipPrompt", { eggsToHatch: eggsToHatchCount }), 0); - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { + globalScene.ui.showText(i18next.t("battle:eggSkipPrompt", { eggsToHatch: eggsToHatchCount }), 0); + globalScene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.hatchEggsSkipped(eggsToHatch); this.showSummary(); }, () => { @@ -45,13 +46,13 @@ export class EggLapsePhase extends Phase { null, null, null, 1000, true ); }, 100, true); - } else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) { - this.scene.queueMessage(i18next.t("battle:eggHatching")); + } else if (eggsToHatchCount >= this.minEggsToSkip && globalScene.eggSkipPreference === 2) { + globalScene.queueMessage(i18next.t("battle:eggHatching")); this.hatchEggsSkipped(eggsToHatch); this.showSummary(); } else { // regular hatches, no summary - this.scene.queueMessage(i18next.t("battle:eggHatching")); + globalScene.queueMessage(i18next.t("battle:eggHatching")); this.hatchEggsRegular(eggsToHatch); this.end(); } @@ -67,7 +68,7 @@ export class EggLapsePhase extends Phase { hatchEggsRegular(eggsToHatch: Egg[]) { let eggsToHatchCount: number = eggsToHatch.length; for (const egg of eggsToHatch) { - this.scene.unshiftPhase(new EggHatchPhase(this.scene, this, egg, eggsToHatchCount)); + globalScene.unshiftPhase(new EggHatchPhase(this, egg, eggsToHatchCount)); eggsToHatchCount--; } } @@ -83,7 +84,7 @@ export class EggLapsePhase extends Phase { } showSummary() { - this.scene.unshiftPhase(new EggSummaryPhase(this.scene, this.eggHatchData)); + globalScene.unshiftPhase(new EggSummaryPhase(this.eggHatchData)); this.end(); } @@ -93,11 +94,11 @@ export class EggLapsePhase extends Phase { * @param egg egg to hatch */ hatchEggSilently(egg: Egg) { - const eggIndex = this.scene.gameData.eggs.findIndex(e => e.id === egg.id); + const eggIndex = globalScene.gameData.eggs.findIndex(e => e.id === egg.id); if (eggIndex === -1) { return this.end(); } - this.scene.gameData.eggs.splice(eggIndex, 1); + globalScene.gameData.eggs.splice(eggIndex, 1); const data = this.generatePokemon(egg); const pokemon = data.pokemon; @@ -106,16 +107,16 @@ export class EggLapsePhase extends Phase { } if (pokemon.species.subLegendary) { - this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY); + globalScene.validateAchv(achvs.HATCH_SUB_LEGENDARY); } if (pokemon.species.legendary) { - this.scene.validateAchv(achvs.HATCH_LEGENDARY); + globalScene.validateAchv(achvs.HATCH_LEGENDARY); } if (pokemon.species.mythical) { - this.scene.validateAchv(achvs.HATCH_MYTHICAL); + globalScene.validateAchv(achvs.HATCH_MYTHICAL); } if (pokemon.isShiny()) { - this.scene.validateAchv(achvs.HATCH_SHINY); + globalScene.validateAchv(achvs.HATCH_SHINY); } } @@ -128,9 +129,9 @@ export class EggLapsePhase extends Phase { generatePokemon(egg: Egg): EggHatchData { let ret: PlayerPokemon; let newHatchData: EggHatchData; - this.scene.executeWithSeedOffset(() => { - ret = egg.generatePlayerPokemon(this.scene); - newHatchData = new EggHatchData(this.scene, ret, egg.eggMoveIndex); + globalScene.executeWithSeedOffset(() => { + ret = egg.generatePlayerPokemon(); + newHatchData = new EggHatchData(ret, egg.eggMoveIndex); newHatchData.setDex(); this.eggHatchData.push(newHatchData); diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index b673eb4887b..56741c5820f 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -1,7 +1,7 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; -import { EggHatchData } from "#app/data/egg-hatch-data"; +import type { EggHatchData } from "#app/data/egg-hatch-data"; /** * Class that represents the egg summary phase @@ -11,8 +11,8 @@ import { EggHatchData } from "#app/data/egg-hatch-data"; export class EggSummaryPhase extends Phase { private eggHatchData: EggHatchData[]; - constructor(scene: BattleScene, eggHatchData: EggHatchData[]) { - super(scene); + constructor(eggHatchData: EggHatchData[]) { + super(); this.eggHatchData = eggHatchData; } @@ -22,8 +22,8 @@ export class EggSummaryPhase extends Phase { // updates next pokemon once the current update has been completed const updateNextPokemon = (i: number) => { if (i >= this.eggHatchData.length) { - this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => { - this.scene.fadeOutBgm(undefined, false); + globalScene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => { + globalScene.fadeOutBgm(undefined, false); }); } else { @@ -40,8 +40,8 @@ export class EggSummaryPhase extends Phase { } end() { - this.scene.time.delayedCall(250, () => this.scene.setModifiersVisible(true)); - this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => { + globalScene.time.delayedCall(250, () => globalScene.setModifiersVisible(true)); + globalScene.ui.setModeForceTransition(Mode.MESSAGE).then(() => { super.end(); }); } diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index a4c9aa44b36..6dae7dff8f9 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -1,5 +1,5 @@ import { BattlerIndex, BattleType } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { applyAbAttrs, SyncEncounterNatureAbAttr } from "#app/data/ability"; import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims"; @@ -10,7 +10,8 @@ import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encou import { TrainerSlot } from "#app/data/trainer-config"; import { getRandomWeatherType } from "#app/data/weather"; import { EncounterPhaseEvent } from "#app/events/battle-scene"; -import Pokemon, { FieldPosition } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; @@ -41,8 +42,8 @@ import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mys export class EncounterPhase extends BattlePhase { private loaded: boolean; - constructor(scene: BattleScene, loaded?: boolean) { - super(scene); + constructor(loaded?: boolean) { + super(); this.loaded = !!loaded; } @@ -50,26 +51,26 @@ export class EncounterPhase extends BattlePhase { start() { super.start(); - this.scene.updateGameInfo(); + globalScene.updateGameInfo(); - this.scene.initSession(); + globalScene.initSession(); - this.scene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); + globalScene.eventTarget.dispatchEvent(new EncounterPhaseEvent()); // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { - this.scene.unshiftPhase(new GameOverPhase(this.scene)); + if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { + globalScene.unshiftPhase(new GameOverPhase()); } const loadEnemyAssets: Promise[] = []; - const battle = this.scene.currentBattle; + const battle = globalScene.currentBattle; // Generate and Init Mystery Encounter if (battle.isBattleMysteryEncounter() && !battle.mysteryEncounter) { - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const currentSessionEncounterType = battle.mysteryEncounterType; - battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); + battle.mysteryEncounter = globalScene.getMysteryEncounter(currentSessionEncounterType); }, battle.waveIndex * 16); } const mysteryEncounter = battle.mysteryEncounter; @@ -77,21 +78,21 @@ export class EncounterPhase extends BattlePhase { // If ME has an onInit() function, call it // Usually used for calculating rand data before initializing anything visual // Also prepopulates any dialogue tokens from encounter/option requirements - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { if (mysteryEncounter.onInit) { - mysteryEncounter.onInit(this.scene); + mysteryEncounter.onInit(); } - mysteryEncounter.populateDialogueTokensFromRequirements(this.scene); + mysteryEncounter.populateDialogueTokensFromRequirements(); }, battle.waveIndex); // Add any special encounter animations to load if (mysteryEncounter.encounterAnimations && mysteryEncounter.encounterAnimations.length > 0) { - loadEnemyAssets.push(initEncounterAnims(this.scene, mysteryEncounter.encounterAnimations).then(() => loadEncounterAnimAssets(this.scene, true))); + loadEnemyAssets.push(initEncounterAnims(mysteryEncounter.encounterAnimations).then(() => loadEncounterAnimAssets(true))); } // Add intro visuals for mystery encounter - mysteryEncounter.initIntroVisuals(this.scene); - this.scene.field.add(mysteryEncounter.introVisuals!); + mysteryEncounter.initIntroVisuals(); + globalScene.field.add(mysteryEncounter.introVisuals!); } let totalBst = 0; @@ -105,35 +106,35 @@ export class EncounterPhase extends BattlePhase { if (battle.battleType === BattleType.TRAINER) { battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? } else { - let enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); + let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); // If player has golden bug net, rolls 10% chance to replace non-boss wave wild species from the golden bug net bug pool - if (this.scene.findModifier(m => m instanceof BoostBugSpawnModifier) - && !this.scene.gameMode.isBoss(battle.waveIndex) - && this.scene.arena.biomeType !== Biome.END + if (globalScene.findModifier(m => m instanceof BoostBugSpawnModifier) + && !globalScene.gameMode.isBoss(battle.waveIndex) + && globalScene.arena.biomeType !== Biome.END && randSeedInt(10) === 0) { enemySpecies = getGoldenBugNetSpecies(level); } - battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + battle.enemyParty[e] = globalScene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!globalScene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { battle.enemyParty[e].ivs = new Array(6).fill(31); } - this.scene.getPlayerParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { + globalScene.getPlayerParty().slice(0, !battle.double ? 1 : 2).reverse().forEach(playerPokemon => { applyAbAttrs(SyncEncounterNatureAbAttr, playerPokemon, null, false, battle.enemyParty[e]); }); } } - const enemyPokemon = this.scene.getEnemyParty()[e]; + const enemyPokemon = globalScene.getEnemyParty()[e]; if (e < (battle.double ? 2 : 1)) { enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); enemyPokemon.resetSummonData(); } if (!this.loaded) { - this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER || battle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE); + globalScene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER || battle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE); } if (enemyPokemon.species.speciesId === Species.ETERNATUS) { - if (this.scene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || this.scene.gameMode.isWaveFinal(battle.waveIndex))) { + if (globalScene.gameMode.isClassic && (battle.battleSpec === BattleSpec.FINAL_BOSS || globalScene.gameMode.isWaveFinal(battle.waveIndex))) { if (battle.battleSpec !== BattleSpec.FINAL_BOSS) { enemyPokemon.formIndex = 1; enemyPokemon.updateScale(); @@ -153,8 +154,8 @@ export class EncounterPhase extends BattlePhase { return true; }); - if (this.scene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { - this.scene.validateAchv(achvs.SHINY_PARTY); + if (globalScene.getPlayerParty().filter(p => p.isShiny()).length === PLAYER_PARTY_MAX_SIZE) { + globalScene.validateAchv(achvs.SHINY_PARTY); } if (battle.battleType === BattleType.TRAINER) { @@ -168,11 +169,11 @@ export class EncounterPhase extends BattlePhase { } // Load Mystery Encounter Exclamation bubble and sfx loadEnemyAssets.push(new Promise(resolve => { - this.scene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav"); - this.scene.loadImage("encounter_exclaim", "mystery-encounters"); - this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); - if (!this.scene.load.isLoading()) { - this.scene.load.start(); + globalScene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav"); + globalScene.loadImage("encounter_exclaim", "mystery-encounters"); + globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve()); + if (!globalScene.load.isLoading()) { + globalScene.load.start(); } })); } else { @@ -196,16 +197,16 @@ export class EncounterPhase extends BattlePhase { } if (e < (battle.double ? 2 : 1)) { if (battle.battleType === BattleType.WILD) { - this.scene.field.add(enemyPokemon); + globalScene.field.add(enemyPokemon); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); - const playerPokemon = this.scene.getPlayerPokemon(); + const playerPokemon = globalScene.getPlayerPokemon(); if (playerPokemon?.isOnField()) { - this.scene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); + globalScene.field.moveBelow(enemyPokemon as Pokemon, playerPokemon); } enemyPokemon.tint(0, 0.5); } else if (battle.battleType === BattleType.TRAINER) { enemyPokemon.setVisible(false); - this.scene.currentBattle.trainer?.tint(0, 0.5); + globalScene.currentBattle.trainer?.tint(0, 0.5); } if (battle.double) { enemyPokemon.setFieldPosition(e ? FieldPosition.RIGHT : FieldPosition.LEFT); @@ -215,62 +216,56 @@ export class EncounterPhase extends BattlePhase { }); if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) { - regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); - this.scene.generateEnemyModifiers(); - overrideModifiers(this.scene, false); - this.scene.getEnemyField().forEach(enemy => { - overrideHeldItems(this.scene, enemy, false); + regenerateModifierPoolThresholds(globalScene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); + globalScene.generateEnemyModifiers(); + overrideModifiers(false); + globalScene.getEnemyField().forEach(enemy => { + overrideHeldItems(enemy, false); }); } - this.scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { if (!this.loaded) { this.trySetWeatherIfNewBiome(); // Set weather before session gets saved // Game syncs to server on waves X1 and X6 (As of 1.2.0) - this.scene.gameData.saveAll(this.scene, true, battle.waveIndex % 5 === 1 || (this.scene.lastSavePlayTime ?? 0) >= 300).then(success => { - this.scene.disableMenu = false; + globalScene.gameData.saveAll(true, battle.waveIndex % 5 === 1 || (globalScene.lastSavePlayTime ?? 0) >= 300).then(success => { + globalScene.disableMenu = false; if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } this.doEncounter(); - this.scene.resetSeed(); + globalScene.resetSeed(); }); } else { this.doEncounter(); - this.scene.resetSeed(); + globalScene.resetSeed(); } }); }); } doEncounter() { - this.scene.playBgm(undefined, true); - this.scene.updateModifiers(false); - this.scene.setFieldScale(1); + globalScene.playBgm(undefined, true); + globalScene.updateModifiers(false); + globalScene.setFieldScale(1); - /*if (startingWave > 10) { - for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++) - this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getPlayerParty())[0].type.newModifier(), true); - this.scene.updateModifiers(true); - }*/ - - const { battleType, waveIndex } = this.scene.currentBattle; - if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) { + const { battleType, waveIndex } = globalScene.currentBattle; + if (globalScene.isMysteryEncounterValidForWave(battleType, waveIndex) && !globalScene.currentBattle.isBattleMysteryEncounter()) { // Increment ME spawn chance if an ME could have spawned but did not // Only do this AFTER session has been saved to avoid duplicating increments - this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; + globalScene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; } - for (const pokemon of this.scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); } } - const enemyField = this.scene.getEnemyField(); - this.scene.tweens.add({ - targets: [ this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer ].flat(), + const enemyField = globalScene.getEnemyField(); + globalScene.tweens.add({ + targets: [ globalScene.arenaEnemy, globalScene.currentBattle.trainer, enemyField, globalScene.arenaPlayer, globalScene.trainer ].flat(), x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300, duration: 2000, onComplete: () => { @@ -280,13 +275,13 @@ export class EncounterPhase extends BattlePhase { } }); - const encounterIntroVisuals = this.scene.currentBattle?.mysteryEncounter?.introVisuals; + const encounterIntroVisuals = globalScene.currentBattle?.mysteryEncounter?.introVisuals; if (encounterIntroVisuals) { const enterFromRight = encounterIntroVisuals.enterFromRight; if (enterFromRight) { encounterIntroVisuals.x += 500; } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: encounterIntroVisuals, x: enterFromRight ? "-=200" : "+=300", duration: 2000 @@ -295,18 +290,18 @@ export class EncounterPhase extends BattlePhase { } getEncounterMessage(): string { - const enemyField = this.scene.getEnemyField(); + const enemyField = globalScene.getEnemyField(); - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return i18next.t("battle:bossAppeared", { bossName: getPokemonNameWithAffix(enemyField[0]) }); } - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - if (this.scene.currentBattle.double) { - return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + if (globalScene.currentBattle.battleType === BattleType.TRAINER) { + if (globalScene.currentBattle.double) { + return i18next.t("battle:trainerAppearedDouble", { trainerName: globalScene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); } else { - return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + return i18next.t("battle:trainerAppeared", { trainerName: globalScene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); } } @@ -316,75 +311,75 @@ export class EncounterPhase extends BattlePhase { } doEncounterCommon(showEncounterMessage: boolean = true) { - const enemyField = this.scene.getEnemyField(); + const enemyField = globalScene.getEnemyField(); - if (this.scene.currentBattle.battleType === BattleType.WILD) { + if (globalScene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { enemyPokemon.untint(100, "Sine.easeOut"); enemyPokemon.cry(); enemyPokemon.showInfo(); if (enemyPokemon.isShiny()) { - this.scene.validateAchv(achvs.SEE_SHINY); + globalScene.validateAchv(achvs.SEE_SHINY); } }); - this.scene.updateFieldScale(); + globalScene.updateFieldScale(); if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); + globalScene.ui.showText(this.getEncounterMessage(), null, () => this.end(), 1500); } else { this.end(); } - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - const trainer = this.scene.currentBattle.trainer; + } else if (globalScene.currentBattle.battleType === BattleType.TRAINER) { + const trainer = globalScene.currentBattle.trainer; trainer?.untint(100, "Sine.easeOut"); trainer?.playAnim(); const doSummon = () => { - this.scene.currentBattle.started = true; - this.scene.playBgm(undefined); - this.scene.pbTray.showPbTray(this.scene.getPlayerParty()); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + globalScene.currentBattle.started = true; + globalScene.playBgm(undefined); + globalScene.pbTray.showPbTray(globalScene.getPlayerParty()); + globalScene.pbTrayEnemy.showPbTray(globalScene.getEnemyParty()); const doTrainerSummon = () => { this.hideEnemyTrainer(); - const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; - this.scene.unshiftPhase(new SummonPhase(this.scene, 0, false)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.unshiftPhase(new SummonPhase(this.scene, 1, false)); + const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; + globalScene.unshiftPhase(new SummonPhase(0, false)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.unshiftPhase(new SummonPhase(1, false)); } this.end(); }; if (showEncounterMessage) { - this.scene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); + globalScene.ui.showText(this.getEncounterMessage(), null, doTrainerSummon, 1500, true); } else { doTrainerSummon(); } }; - const encounterMessages = this.scene.currentBattle.trainer?.getEncounterMessages(); + const encounterMessages = globalScene.currentBattle.trainer?.getEncounterMessages(); if (!encounterMessages?.length) { doSummon(); } else { let message: string; - this.scene.executeWithSeedOffset(() => message = randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + globalScene.executeWithSeedOffset(() => message = randSeedItem(encounterMessages), globalScene.currentBattle.waveIndex); message = message!; // tell TS compiler it's defined now const showDialogueAndSummon = () => { - this.scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { - this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); + globalScene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { + globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => doSummon())); }); }; - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? + if (globalScene.currentBattle.trainer?.config.hasCharSprite && !globalScene.ui.shouldSkipDialogue(message)) { + globalScene.showFieldOverlay(500).then(() => globalScene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? } else { showDialogueAndSummon(); } } - } else if (this.scene.currentBattle.isBattleMysteryEncounter() && this.scene.currentBattle.mysteryEncounter) { - const encounter = this.scene.currentBattle.mysteryEncounter; + } else if (globalScene.currentBattle.isBattleMysteryEncounter() && globalScene.currentBattle.mysteryEncounter) { + const encounter = globalScene.currentBattle.mysteryEncounter; const introVisuals = encounter.introVisuals; introVisuals?.playAnim(); if (encounter.onVisualsStart) { - encounter.onVisualsStart(this.scene); + encounter.onVisualsStart(); } else if (encounter.spriteConfigs && introVisuals) { // If the encounter doesn't have any special visual intro, show sparkle for shiny Pokemon introVisuals.playShinySparkles(); @@ -392,10 +387,10 @@ export class EncounterPhase extends BattlePhase { const doEncounter = () => { const doShowEncounterOptions = () => { - this.scene.ui.clearText(); - this.scene.ui.getMessageHandler().hideNameText(); + globalScene.ui.clearText(); + globalScene.ui.getMessageHandler().hideNameText(); - this.scene.unshiftPhase(new MysteryEncounterPhase(this.scene)); + globalScene.unshiftPhase(new MysteryEncounterPhase()); this.end(); }; @@ -409,13 +404,13 @@ export class EncounterPhase extends BattlePhase { const showNextDialogue = () => { const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue; const dialogue = introDialogue[i]; - const title = getEncounterText(this.scene, dialogue?.speaker); - const text = getEncounterText(this.scene, dialogue.text)!; + const title = getEncounterText(dialogue?.speaker); + const text = getEncounterText(dialogue.text)!; i++; if (title) { - this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0); + globalScene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0); } else { - this.scene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true); + globalScene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true); } }; @@ -433,110 +428,110 @@ export class EncounterPhase extends BattlePhase { if (!encounterMessage) { doEncounter(); } else { - doTrainerExclamation(this.scene); - this.scene.ui.showDialogue(encounterMessage, "???", null, () => { - this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doEncounter())); + doTrainerExclamation(); + globalScene.ui.showDialogue(encounterMessage, "???", null, () => { + globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => doEncounter())); }); } } } end() { - const enemyField = this.scene.getEnemyField(); + const enemyField = globalScene.getEnemyField(); enemyField.forEach((enemyPokemon, e) => { if (enemyPokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); + globalScene.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e)); } /** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */ - if (enemyPokemon.species.speciesId === Species.ETERNATUS && (this.scene.gameMode.isBattleClassicFinalBoss(this.scene.currentBattle.waveIndex) || this.scene.gameMode.isEndlessMajorBoss(this.scene.currentBattle.waveIndex))) { - const enemyMBH = this.scene.findModifier(m => m instanceof TurnHeldItemTransferModifier, false) as TurnHeldItemTransferModifier; + if (enemyPokemon.species.speciesId === Species.ETERNATUS && (globalScene.gameMode.isBattleClassicFinalBoss(globalScene.currentBattle.waveIndex) || globalScene.gameMode.isEndlessMajorBoss(globalScene.currentBattle.waveIndex))) { + const enemyMBH = globalScene.findModifier(m => m instanceof TurnHeldItemTransferModifier, false) as TurnHeldItemTransferModifier; if (enemyMBH) { - this.scene.removeModifier(enemyMBH, true); + globalScene.removeModifier(enemyMBH, true); enemyMBH.setTransferrableFalse(); - this.scene.addEnemyModifier(enemyMBH); + globalScene.addEnemyModifier(enemyMBH); } } }); - if (![ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType)) { - enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => { + if (![ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(globalScene.currentBattle.battleType)) { + enemyField.map(p => globalScene.pushConditionalPhase(new PostSummonPhase(p.getBattlerIndex()), () => { // if there is not a player party, we can't continue - if (!this.scene.getPlayerParty().length) { + if (!globalScene.getPlayerParty().length) { return false; } // how many player pokemon are on the field ? - const pokemonsOnFieldCount = this.scene.getPlayerParty().filter(p => p.isOnField()).length; + const pokemonsOnFieldCount = globalScene.getPlayerParty().filter(p => p.isOnField()).length; // if it's a 2vs1, there will never be a 2nd pokemon on our field even - const requiredPokemonsOnField = Math.min(this.scene.getPlayerParty().filter((p) => !p.isFainted()).length, 2); + const requiredPokemonsOnField = Math.min(globalScene.getPlayerParty().filter((p) => !p.isFainted()).length, 2); // if it's a double, there should be 2, otherwise 1 - if (this.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { return pokemonsOnFieldCount === requiredPokemonsOnField; } return pokemonsOnFieldCount === 1; })); - const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); + const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); + enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); } } if (!this.loaded) { - const availablePartyMembers = this.scene.getPokemonAllowedInBattle(); + const availablePartyMembers = globalScene.getPokemonAllowedInBattle(); if (!availablePartyMembers[0].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 0)); + globalScene.pushPhase(new SummonPhase(0)); } - if (this.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, true)); + globalScene.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); + globalScene.pushPhase(new SummonPhase(1)); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - this.scene.pushPhase(new ReturnPhase(this.scene, 1)); + globalScene.pushPhase(new ReturnPhase(1)); } - this.scene.pushPhase(new ToggleDoublePositionPhase(this.scene, false)); + globalScene.pushPhase(new ToggleDoublePositionPhase(false)); } - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (globalScene.currentBattle.battleType !== BattleType.TRAINER && (globalScene.currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily)) { + const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + if (globalScene.currentBattle.double) { + globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } } - handleTutorial(this.scene, Tutorial.Access_Menu).then(() => super.end()); + handleTutorial(Tutorial.Access_Menu).then(() => super.end()); } tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { + switch (globalScene.currentBattle.battleSpec) { case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const enemy = globalScene.getEnemyPokemon(); + globalScene.ui.showText(this.getEncounterMessage(), null, () => { const localizationKey = "battleSpecDialogue:encounter"; - if (this.scene.ui.shouldSkipDialogue(localizationKey)) { + if (globalScene.ui.shouldSkipDialogue(localizationKey)) { // Logging mirrors logging found in dialogue-ui-handler console.log(`Dialogue ${localizationKey} skipped`); this.doEncounterCommon(false); } else { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + const count = 5643853 + globalScene.gameData.gameStats.classicSessionsPlayed; // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; const cycleCount = count.toLocaleString() + ordinalUsed; - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); - if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { - this.scene.gameData.saveSeenDialogue(localizationKey); + if (!globalScene.gameData.getSeenDialogues()[localizationKey]) { + globalScene.gameData.saveSeenDialogue(localizationKey); } - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + globalScene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { this.doEncounterCommon(false); }); } @@ -556,7 +551,7 @@ export class EncounterPhase extends BattlePhase { */ trySetWeatherIfNewBiome(): void { if (!this.loaded) { - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena), false); } } } diff --git a/src/phases/end-card-phase.ts b/src/phases/end-card-phase.ts index 274a745017a..4615a60e661 100644 --- a/src/phases/end-card-phase.ts +++ b/src/phases/end-card-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { PlayerGender } from "#app/enums/player-gender"; import { Phase } from "#app/phase"; import { addTextObject, TextStyle } from "#app/ui/text"; @@ -8,31 +8,31 @@ export class EndCardPhase extends Phase { public endCard: Phaser.GameObjects.Image; public text: Phaser.GameObjects.Text; - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start(): void { super.start(); - this.scene.ui.getMessageHandler().bg.setVisible(false); - this.scene.ui.getMessageHandler().nameBoxContainer.setVisible(false); + globalScene.ui.getMessageHandler().bg.setVisible(false); + globalScene.ui.getMessageHandler().nameBoxContainer.setVisible(false); - this.endCard = this.scene.add.image(0, 0, `end_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); + this.endCard = globalScene.add.image(0, 0, `end_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}`); this.endCard.setOrigin(0); this.endCard.setScale(0.5); - this.scene.field.add(this.endCard); + globalScene.field.add(this.endCard); - this.text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); + this.text = addTextObject(globalScene.game.canvas.width / 12, (globalScene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); this.text.setOrigin(0.5); - this.scene.field.add(this.text); + globalScene.field.add(this.text); - this.scene.ui.clearText(); + globalScene.ui.clearText(); - this.scene.ui.fadeIn(1000).then(() => { + globalScene.ui.fadeIn(1000).then(() => { - this.scene.ui.showText("", null, () => { - this.scene.ui.getMessageHandler().bg.setVisible(true); + globalScene.ui.showText("", null, () => { + globalScene.ui.getMessageHandler().bg.setVisible(true); this.end(); }, null, true); }); diff --git a/src/phases/end-evolution-phase.ts b/src/phases/end-evolution-phase.ts index 8a6ce52553d..58e2e203482 100644 --- a/src/phases/end-evolution-phase.ts +++ b/src/phases/end-evolution-phase.ts @@ -1,16 +1,16 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; export class EndEvolutionPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); + globalScene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end()); } } diff --git a/src/phases/enemy-command-phase.ts b/src/phases/enemy-command-phase.ts index 83a85009ae0..715303863be 100644 --- a/src/phases/enemy-command-phase.ts +++ b/src/phases/enemy-command-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { BattlerIndex } from "#app/battle"; import { Command } from "#app/ui/command-ui-handler"; import { FieldPhase } from "./field-phase"; @@ -18,11 +18,11 @@ export class EnemyCommandPhase extends FieldPhase { protected fieldIndex: integer; protected skipTurn: boolean = false; - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene); + constructor(fieldIndex: integer) { + super(); this.fieldIndex = fieldIndex; - if (this.scene.currentBattle.mysteryEncounter?.skipEnemyBattleTurns) { + if (globalScene.currentBattle.mysteryEncounter?.skipEnemyBattleTurns) { this.skipTurn = true; } } @@ -30,9 +30,9 @@ export class EnemyCommandPhase extends FieldPhase { start() { super.start(); - const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; + const enemyPokemon = globalScene.getEnemyField()[this.fieldIndex]; - const battle = this.scene.currentBattle; + const battle = globalScene.currentBattle; const trainer = battle.trainer; @@ -81,10 +81,10 @@ export class EnemyCommandPhase extends FieldPhase { /** Select a move to use (and a target to use it against, if applicable) */ const nextMove = enemyPokemon.getNextMove(); - this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.FIGHT, move: nextMove, skip: this.skipTurn }; - this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0); + globalScene.currentBattle.enemySwitchCounter = Math.max(globalScene.currentBattle.enemySwitchCounter - 1, 0); this.end(); } diff --git a/src/phases/enemy-party-member-pokemon-phase.ts b/src/phases/enemy-party-member-pokemon-phase.ts index bb34f53b475..7c02cf97880 100644 --- a/src/phases/enemy-party-member-pokemon-phase.ts +++ b/src/phases/enemy-party-member-pokemon-phase.ts @@ -1,10 +1,9 @@ -import BattleScene from "#app/battle-scene"; -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, false); + constructor(partyMemberIndex: integer) { + super(partyMemberIndex, false); } getEnemyPokemon(): EnemyPokemon { diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 76e521c9b3d..bf046e682e4 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -1,12 +1,16 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import { Phase } from "#app/phase"; -import BattleScene, { AnySound } from "#app/battle-scene"; -import { FusionSpeciesFormEvolution, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; -import EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; +import type { AnySound } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import { FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; +import type EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; import * as Utils from "#app/utils"; import { Mode } from "#app/ui/ui"; import { cos, sin } from "#app/field/anims"; -import Pokemon, { LearnMoveSituation, PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { LearnMoveSituation } from "#app/field/pokemon"; import { getTypeRgb } from "#app/data/type"; import i18next from "i18next"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -35,8 +39,8 @@ export class EvolutionPhase extends Phase { protected pokemonEvoSprite: Phaser.GameObjects.Sprite; protected pokemonEvoTintSprite: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: integer) { - super(scene); + constructor(pokemon: PlayerPokemon, evolution: SpeciesFormEvolution | null, lastLevel: integer) { + super(); this.pokemon = pokemon; this.evolution = evolution; @@ -49,7 +53,7 @@ export class EvolutionPhase extends Phase { } setMode(): Promise { - return this.scene.ui.setModeForceTransition(Mode.EVOLUTION_SCENE); + return globalScene.ui.setModeForceTransition(Mode.EVOLUTION_SCENE); } start() { @@ -61,30 +65,30 @@ export class EvolutionPhase extends Phase { return this.end(); } - this.scene.fadeOutBgm(undefined, false); + globalScene.fadeOutBgm(undefined, false); - this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler; + this.evolutionHandler = globalScene.ui.getHandler() as EvolutionSceneHandler; this.evolutionContainer = this.evolutionHandler.evolutionContainer; - this.evolutionBaseBg = this.scene.add.image(0, 0, "default_bg"); + this.evolutionBaseBg = globalScene.add.image(0, 0, "default_bg"); this.evolutionBaseBg.setOrigin(0, 0); this.evolutionContainer.add(this.evolutionBaseBg); - this.evolutionBg = this.scene.add.video(0, 0, "evo_bg").stop(); + this.evolutionBg = globalScene.add.video(0, 0, "evo_bg").stop(); this.evolutionBg.setOrigin(0, 0); this.evolutionBg.setScale(0.4359673025); this.evolutionBg.setVisible(false); this.evolutionContainer.add(this.evolutionBg); - this.evolutionBgOverlay = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0x262626); + this.evolutionBgOverlay = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x262626); this.evolutionBgOverlay.setOrigin(0, 0); this.evolutionBgOverlay.setAlpha(0); this.evolutionContainer.add(this.evolutionBgOverlay); const getPokemonSprite = () => { - const ret = this.scene.addPokemonSprite(this.pokemon, this.evolutionBaseBg.displayWidth / 2, this.evolutionBaseBg.displayHeight / 2, "pkmn__sub"); - ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + const ret = globalScene.addPokemonSprite(this.pokemon, this.evolutionBaseBg.displayWidth / 2, this.evolutionBaseBg.displayHeight / 2, "pkmn__sub"); + ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); return ret; }; @@ -99,10 +103,10 @@ export class EvolutionPhase extends Phase { this.pokemonEvoTintSprite.setVisible(false); this.pokemonEvoTintSprite.setTintFill(0xFFFFFF); - this.evolutionOverlay = this.scene.add.rectangle(0, -this.scene.game.canvas.height / 6, this.scene.game.canvas.width / 6, (this.scene.game.canvas.height / 6) - 48, 0xFFFFFF); + this.evolutionOverlay = globalScene.add.rectangle(0, -globalScene.game.canvas.height / 6, globalScene.game.canvas.width / 6, (globalScene.game.canvas.height / 6) - 48, 0xFFFFFF); this.evolutionOverlay.setOrigin(0, 0); this.evolutionOverlay.setAlpha(0); - this.scene.ui.add(this.evolutionOverlay); + globalScene.ui.add(this.evolutionOverlay); [ this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => { const spriteKey = this.pokemon.getSpriteKey(true); @@ -112,7 +116,7 @@ export class EvolutionPhase extends Phase { console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); sprite.setPipelineData("shiny", this.pokemon.shiny); @@ -130,7 +134,7 @@ export class EvolutionPhase extends Phase { } doEvolution(): void { - this.scene.ui.showText(i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { + globalScene.ui.showText(i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { this.pokemon.cry(); this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => { @@ -155,17 +159,17 @@ export class EvolutionPhase extends Phase { }); }); - this.scene.time.delayedCall(1000, () => { - this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution"); - this.scene.tweens.add({ + globalScene.time.delayedCall(1000, () => { + this.evolutionBgm = globalScene.playSoundWithoutBgm("evolution"); + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 1, delay: 500, duration: 1500, ease: "Sine.easeOut", onComplete: () => { - this.scene.time.delayedCall(1000, () => { - this.scene.tweens.add({ + globalScene.time.delayedCall(1000, () => { + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 0, duration: 250 @@ -173,9 +177,9 @@ export class EvolutionPhase extends Phase { this.evolutionBg.setVisible(true); this.evolutionBg.play(); }); - this.scene.playSound("se/charge"); + globalScene.playSound("se/charge"); this.doSpiralUpward(); - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ from: 0, to: 1, duration: 2000, @@ -184,10 +188,10 @@ export class EvolutionPhase extends Phase { }, onComplete: () => { this.pokemonSprite.setVisible(false); - this.scene.time.delayedCall(1100, () => { - this.scene.playSound("se/beam"); + globalScene.time.delayedCall(1100, () => { + globalScene.playSound("se/beam"); this.doArcDownward(); - this.scene.time.delayedCall(1500, () => { + globalScene.time.delayedCall(1500, () => { this.pokemonEvoTintSprite.setScale(0.25); this.pokemonEvoTintSprite.setVisible(true); this.evolutionHandler.canCancel = true; @@ -216,7 +220,7 @@ export class EvolutionPhase extends Phase { private handleFailedEvolution(evolvedPokemon: Pokemon): void { this.pokemonSprite.setVisible(true); this.pokemonTintSprite.setScale(1); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.evolutionBg, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ], alpha: 0, duration: 250, @@ -225,25 +229,25 @@ export class EvolutionPhase extends Phase { } }); - SoundFade.fadeOut(this.scene, this.evolutionBgm, 100); + SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); - this.scene.unshiftPhase(new EndEvolutionPhase(this.scene)); + globalScene.unshiftPhase(new EndEvolutionPhase()); - this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { - this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: this.preEvolvedPokemonName }), null, () => { + globalScene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: this.preEvolvedPokemonName }), null, () => { + globalScene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: this.preEvolvedPokemonName }), null, () => { const end = () => { - this.scene.ui.showText("", 0); - this.scene.playBgm(); + globalScene.ui.showText("", 0); + globalScene.playBgm(); evolvedPokemon.destroy(); this.end(); }; - this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.ui.revertMode(); + globalScene.ui.setOverlayMode(Mode.CONFIRM, () => { + globalScene.ui.revertMode(); this.pokemon.pauseEvolutions = true; - this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: this.preEvolvedPokemonName }), null, end, 3000); + globalScene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: this.preEvolvedPokemonName }), null, end, 3000); }, () => { - this.scene.ui.revertMode(); - this.scene.time.delayedCall(3000, end); + globalScene.ui.revertMode(); + globalScene.time.delayedCall(3000, end); }); }); }, null, true); @@ -254,38 +258,38 @@ export class EvolutionPhase extends Phase { * @param evolvedPokemon - The evolved Pokemon */ private handleSuccessEvolution(evolvedPokemon: Pokemon): void { - this.scene.playSound("se/sparkle"); + globalScene.playSound("se/sparkle"); this.pokemonEvoSprite.setVisible(true); this.doCircleInward(); const onEvolutionComplete = () => { - SoundFade.fadeOut(this.scene, this.evolutionBgm, 100); - this.scene.time.delayedCall(250, () => { + SoundFade.fadeOut(globalScene, this.evolutionBgm, 100); + globalScene.time.delayedCall(250, () => { this.pokemon.cry(); - this.scene.time.delayedCall(1250, () => { - this.scene.playSoundWithoutBgm("evolution_fanfare"); + globalScene.time.delayedCall(1250, () => { + globalScene.playSoundWithoutBgm("evolution_fanfare"); evolvedPokemon.destroy(); - this.scene.ui.showText(i18next.t("menu:evolutionDone", { pokemonName: this.preEvolvedPokemonName, evolvedPokemonName: this.pokemon.name }), null, () => this.end(), null, true, Utils.fixedInt(4000)); - this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm()); + globalScene.ui.showText(i18next.t("menu:evolutionDone", { pokemonName: this.preEvolvedPokemonName, evolvedPokemonName: this.pokemon.name }), null, () => this.end(), null, true, Utils.fixedInt(4000)); + globalScene.time.delayedCall(Utils.fixedInt(4250), () => globalScene.playBgm()); }); }); }; - this.scene.time.delayedCall(900, () => { + globalScene.time.delayedCall(900, () => { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved ? LearnMoveSituation.EVOLUTION_FUSED : this.pokemon.fusionSpecies ? LearnMoveSituation.EVOLUTION_FUSED_BASE : LearnMoveSituation.EVOLUTION; const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation).filter(lm => lm[0] === EVOLVE_MOVE); for (const lm of levelMoves) { - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getPlayerParty().indexOf(this.pokemon), lm[1])); + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); } - this.scene.unshiftPhase(new EndEvolutionPhase(this.scene)); + globalScene.unshiftPhase(new EndEvolutionPhase()); - this.scene.playSound("se/shine"); + globalScene.playSound("se/shine"); this.doSpray(); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.evolutionOverlay, alpha: 1, duration: 250, @@ -293,14 +297,14 @@ export class EvolutionPhase extends Phase { onComplete: () => { this.evolutionBgOverlay.setAlpha(1); this.evolutionBg.setVisible(false); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.evolutionOverlay, this.pokemonEvoTintSprite ], alpha: 0, duration: 2000, delay: 150, easing: "Sine.easeIn", onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 0, duration: 250, @@ -317,7 +321,7 @@ export class EvolutionPhase extends Phase { doSpiralUpward() { let f = 0; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 64, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -336,7 +340,7 @@ export class EvolutionPhase extends Phase { doArcDownward() { let f = 0; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 96, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -355,14 +359,14 @@ export class EvolutionPhase extends Phase { doCycle(l: number, lastCycle: integer = 15): Promise { return new Promise(resolve => { const isLastCycle = l === lastCycle; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemonTintSprite, scale: 0.25, ease: "Cubic.easeInOut", duration: 500 / l, yoyo: !isLastCycle }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemonEvoTintSprite, scale: 1, ease: "Cubic.easeInOut", @@ -386,7 +390,7 @@ export class EvolutionPhase extends Phase { doCircleInward() { let f = 0; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 48, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -407,7 +411,7 @@ export class EvolutionPhase extends Phase { doSpray() { let f = 0; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ repeat: 48, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -425,13 +429,13 @@ export class EvolutionPhase extends Phase { doSpiralUpwardParticle(trigIndex: integer) { const initialX = this.evolutionBaseBg.displayWidth / 2; - const particle = this.scene.add.image(initialX, 0, "evo_sparkle"); + const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); this.evolutionContainer.add(particle); let f = 0; let amp = 48; - const particleTimer = this.scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -461,14 +465,14 @@ export class EvolutionPhase extends Phase { doArcDownParticle(trigIndex: integer) { const initialX = this.evolutionBaseBg.displayWidth / 2; - const particle = this.scene.add.image(initialX, 0, "evo_sparkle"); + const particle = globalScene.add.image(initialX, 0, "evo_sparkle"); particle.setScale(0.5); this.evolutionContainer.add(particle); let f = 0; let amp = 8; - const particleTimer = this.scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -495,12 +499,12 @@ export class EvolutionPhase extends Phase { doCircleInwardParticle(trigIndex: integer, speed: integer) { const initialX = this.evolutionBaseBg.displayWidth / 2; const initialY = this.evolutionBaseBg.displayHeight / 2; - const particle = this.scene.add.image(initialX, initialY, "evo_sparkle"); + const particle = globalScene.add.image(initialX, initialY, "evo_sparkle"); this.evolutionContainer.add(particle); let amp = 120; - const particleTimer = this.scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: Utils.getFrameMs(1), onRepeat: () => { @@ -527,7 +531,7 @@ export class EvolutionPhase extends Phase { doSprayParticle(trigIndex: integer) { const initialX = this.evolutionBaseBg.displayWidth / 2; const initialY = this.evolutionBaseBg.displayHeight / 2; - const particle = this.scene.add.image(initialX, initialY, "evo_sparkle"); + const particle = globalScene.add.image(initialX, initialY, "evo_sparkle"); this.evolutionContainer.add(particle); let f = 0; @@ -535,7 +539,7 @@ export class EvolutionPhase extends Phase { const speed = 3 - Utils.randInt(8); const amp = 48 + Utils.randInt(64); - const particleTimer = this.scene.tweens.addCounter({ + const particleTimer = globalScene.tweens.addCounter({ repeat: -1, duration: Utils.getFrameMs(1), onRepeat: () => { diff --git a/src/phases/exp-phase.ts b/src/phases/exp-phase.ts index 81982980d2a..f0fe9fde719 100644 --- a/src/phases/exp-phase.ts +++ b/src/phases/exp-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { ExpBoosterModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -9,8 +9,8 @@ import { LevelUpPhase } from "./level-up-phase"; export class ExpPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); + constructor(partyMemberIndex: integer, expValue: number) { + super(partyMemberIndex); this.expValue = expValue; } @@ -20,14 +20,14 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); + globalScene.applyModifiers(ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); - this.scene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { + globalScene.ui.showText(i18next.t("battle:expGain", { pokemonName: getPokemonNameWithAffix(pokemon), exp: exp.value }), null, () => { const lastLevel = pokemon.level; pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + globalScene.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); } pokemon.updateInfo().then(() => this.end()); }, null, true); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index a0c10015fbf..7bf3bc81930 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,13 +1,17 @@ -import { BattlerIndex, BattleType } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import type { BattlerIndex } from "#app/battle"; +import { BattleType } from "#app/battle"; +import { globalScene } from "#app/global-scene"; import { applyPostFaintAbAttrs, applyPostKnockOutAbAttrs, applyPostVictoryAbAttrs, PostFaintAbAttr, PostKnockOutAbAttr, PostVictoryAbAttr } from "#app/data/ability"; -import { BattlerTagLapseType, DestinyBondTag, GrudgeTag } from "#app/data/battler-tags"; +import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags"; +import { BattlerTagLapseType } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; -import Pokemon, { EnemyPokemon, HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { HitResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { SwitchType } from "#enums/switch-type"; @@ -43,8 +47,8 @@ export class FaintPhase extends PokemonPhase { */ private source?: Pokemon; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag | null, grudgeTag?: GrudgeTag | null, source?: Pokemon) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag | null, grudgeTag?: GrudgeTag | null, source?: Pokemon) { + super(battlerIndex); this.preventEndure = preventEndure; this.destinyTag = destinyTag; @@ -66,20 +70,20 @@ export class FaintPhase extends PokemonPhase { } if (!this.preventEndure) { - const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, faintPokemon) as PokemonInstantReviveModifier; + const instantReviveModifier = globalScene.applyModifier(PokemonInstantReviveModifier, this.player, faintPokemon) as PokemonInstantReviveModifier; if (instantReviveModifier) { faintPokemon.loseHeldItem(instantReviveModifier); - this.scene.updateModifiers(this.player); + globalScene.updateModifiers(this.player); return this.end(); } } /** In case the current pokemon was just switched in, make sure it is counted as participating in the combat */ - this.scene.getPlayerField().forEach((pokemon, i) => { + globalScene.getPlayerField().forEach((pokemon, i) => { if (pokemon?.isActive(true)) { if (pokemon.isPlayer()) { - this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); + globalScene.currentBattle.addParticipant(pokemon as PlayerPokemon); } } }); @@ -95,27 +99,27 @@ export class FaintPhase extends PokemonPhase { // Track total times pokemon have been KO'd for supreme overlord/last respects if (pokemon.isPlayer()) { - this.scene.currentBattle.playerFaints += 1; - this.scene.currentBattle.playerFaintsHistory.push({ pokemon: pokemon, turn: this.scene.currentBattle.turn }); + globalScene.currentBattle.playerFaints += 1; + globalScene.currentBattle.playerFaintsHistory.push({ pokemon: pokemon, turn: globalScene.currentBattle.turn }); } else { - this.scene.currentBattle.enemyFaints += 1; - this.scene.currentBattle.enemyFaintsHistory.push({ pokemon: pokemon, turn: this.scene.currentBattle.turn }); + globalScene.currentBattle.enemyFaints += 1; + globalScene.currentBattle.enemyFaintsHistory.push({ pokemon: pokemon, turn: globalScene.currentBattle.turn }); } - this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + globalScene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); if (pokemon.turnData?.attacksReceived?.length) { const lastAttack = pokemon.turnData.attacksReceived[0]; - applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, this.scene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? + applyPostFaintAbAttrs(PostFaintAbAttr, pokemon, globalScene.getPokemonById(lastAttack.sourceId)!, new PokemonMove(lastAttack.move).getMove(), lastAttack.result); // TODO: is this bang correct? } else { //If killed by indirect damage, apply post-faint abilities without providing a last move applyPostFaintAbAttrs(PostFaintAbAttr, pokemon); } - const alivePlayField = this.scene.getField(true); + const alivePlayField = globalScene.getField(true); alivePlayField.forEach(p => applyPostKnockOutAbAttrs(PostKnockOutAbAttr, p, pokemon)); if (pokemon.turnData?.attacksReceived?.length) { - const defeatSource = this.scene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); + const defeatSource = globalScene.getPokemonById(pokemon.turnData.attacksReceived[0].sourceId); if (defeatSource?.isOnField()) { applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; @@ -130,39 +134,39 @@ export class FaintPhase extends PokemonPhase { if (this.player) { /** The total number of Pokemon in the player's party that can legally fight */ - const legalPlayerPokemon = this.scene.getPokemonAllowedInBattle(); + const legalPlayerPokemon = globalScene.getPokemonAllowedInBattle(); /** The total number of legal player Pokemon that aren't currently on the field */ const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { /** If the player doesn't have any legal Pokemon, end the game */ - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + globalScene.unshiftPhase(new GameOverPhase()); + } else if (globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { /** * If the player has exactly one Pokemon in total at this point in a double battle, and that Pokemon * is already on the field, unshift a phase that moves that Pokemon to center position. */ - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); } else if (legalPlayerPartyPokemon.length > 0) { /** * If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field, * push a phase that prompts the player to summon a Pokemon from their party. */ - this.scene.pushPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, true, false)); + globalScene.pushPhase(new SwitchPhase(SwitchType.SWITCH, this.fieldIndex, true, false)); } } else { - this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); - if ([ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType)) { - const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; + globalScene.unshiftPhase(new VictoryPhase(this.battlerIndex)); + if ([ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(globalScene.currentBattle.battleType)) { + const hasReservePartyMember = !!globalScene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length; if (hasReservePartyMember) { - this.scene.pushPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, -1, false, false)); + globalScene.pushPhase(new SwitchSummonPhase(SwitchType.SWITCH, this.fieldIndex, -1, false, false)); } } } // in double battles redirect potential moves off fainted pokemon - if (this.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { const allyPokemon = pokemon.getAlly(); - this.scene.redirectPokemonMoves(pokemon, allyPokemon); + globalScene.redirectPokemonMoves(pokemon, allyPokemon); } pokemon.faintCry(() => { @@ -170,8 +174,8 @@ export class FaintPhase extends PokemonPhase { pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT); } pokemon.hideInfo(); - this.scene.playSound("se/faint"); - this.scene.tweens.add({ + globalScene.playSound("se/faint"); + globalScene.tweens.add({ targets: pokemon, duration: 500, y: pokemon.y + 150, @@ -179,17 +183,17 @@ export class FaintPhase extends PokemonPhase { onComplete: () => { pokemon.resetSprite(); pokemon.lapseTags(BattlerTagLapseType.FAINT); - this.scene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); + globalScene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id)); pokemon.y -= 150; pokemon.trySetStatus(StatusEffect.FAINT); if (pokemon.isPlayer()) { - this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); + globalScene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); } else { - this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); - this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); + globalScene.addFaintedEnemyScore(pokemon as EnemyPokemon); + globalScene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); } - this.scene.field.remove(pokemon); + globalScene.field.remove(pokemon); this.end(); } }); @@ -197,16 +201,16 @@ export class FaintPhase extends PokemonPhase { } tryOverrideForBattleSpec(): boolean { - switch (this.scene.currentBattle.battleSpec) { + switch (globalScene.currentBattle.battleSpec) { case BattleSpec.FINAL_BOSS: if (!this.player) { const enemy = this.getPokemon(); if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + globalScene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase enemy.hp++; - this.scene.unshiftPhase(new DamageAnimPhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + globalScene.unshiftPhase(new DamageAnimPhase(enemy.getBattlerIndex(), 0, HitResult.OTHER)); this.end(); } return true; diff --git a/src/phases/field-phase.ts b/src/phases/field-phase.ts index 46c3c4983b4..0836d1171c0 100644 --- a/src/phases/field-phase.ts +++ b/src/phases/field-phase.ts @@ -1,11 +1,12 @@ -import Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; type PokemonFunc = (pokemon: Pokemon) => void; export abstract class FieldPhase extends BattlePhase { executeForAll(func: PokemonFunc): void { - const field = this.scene.getField(true).filter(p => p.summonData); + const field = globalScene.getField(true).filter(p => p.summonData); field.forEach(pokemon => func(pokemon)); } } diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index b042cd98294..353fed7b76c 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -1,10 +1,11 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; import { achvs } from "../system/achv"; -import { SpeciesFormChange, getSpeciesFormChangeMessage } from "../data/pokemon-forms"; -import { PlayerPokemon } from "../field/pokemon"; +import type { SpeciesFormChange } from "../data/pokemon-forms"; +import { getSpeciesFormChangeMessage } from "../data/pokemon-forms"; +import type { PlayerPokemon } from "../field/pokemon"; import { Mode } from "../ui/ui"; -import PartyUiHandler from "../ui/party-ui-handler"; +import type PartyUiHandler from "../ui/party-ui-handler"; import { getPokemonNameWithAffix } from "../messages"; import { EndEvolutionPhase } from "./end-evolution-phase"; import { EvolutionPhase } from "./evolution-phase"; @@ -15,8 +16,8 @@ export class FormChangePhase extends EvolutionPhase { private formChange: SpeciesFormChange; private modal: boolean; - constructor(scene: BattleScene, pokemon: PlayerPokemon, formChange: SpeciesFormChange, modal: boolean) { - super(scene, pokemon, null, 0); + constructor(pokemon: PlayerPokemon, formChange: SpeciesFormChange, modal: boolean) { + super(pokemon, null, 0); this.formChange = formChange; this.modal = modal; @@ -30,7 +31,7 @@ export class FormChangePhase extends EvolutionPhase { if (!this.modal) { return super.setMode(); } - return this.scene.ui.setOverlayMode(Mode.EVOLUTION_SCENE); + return globalScene.ui.setOverlayMode(Mode.EVOLUTION_SCENE); } doEvolution(): void { @@ -58,16 +59,16 @@ export class FormChangePhase extends EvolutionPhase { }); }); - this.scene.time.delayedCall(250, () => { - this.scene.tweens.add({ + globalScene.time.delayedCall(250, () => { + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 1, delay: 500, duration: 1500, ease: "Sine.easeOut", onComplete: () => { - this.scene.time.delayedCall(1000, () => { - this.scene.tweens.add({ + globalScene.time.delayedCall(1000, () => { + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 0, duration: 250 @@ -75,9 +76,9 @@ export class FormChangePhase extends EvolutionPhase { this.evolutionBg.setVisible(true); this.evolutionBg.play(); }); - this.scene.playSound("se/charge"); + globalScene.playSound("se/charge"); this.doSpiralUpward(); - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ from: 0, to: 1, duration: 2000, @@ -86,25 +87,25 @@ export class FormChangePhase extends EvolutionPhase { }, onComplete: () => { this.pokemonSprite.setVisible(false); - this.scene.time.delayedCall(1100, () => { - this.scene.playSound("se/beam"); + globalScene.time.delayedCall(1100, () => { + globalScene.playSound("se/beam"); this.doArcDownward(); - this.scene.time.delayedCall(1000, () => { + globalScene.time.delayedCall(1000, () => { this.pokemonEvoTintSprite.setScale(0.25); this.pokemonEvoTintSprite.setVisible(true); this.doCycle(1, 1).then(_success => { - this.scene.playSound("se/sparkle"); + globalScene.playSound("se/sparkle"); this.pokemonEvoSprite.setVisible(true); this.doCircleInward(); - this.scene.time.delayedCall(900, () => { + globalScene.time.delayedCall(900, () => { this.pokemon.changeForm(this.formChange).then(() => { if (!this.modal) { - this.scene.unshiftPhase(new EndEvolutionPhase(this.scene)); + globalScene.unshiftPhase(new EndEvolutionPhase()); } - this.scene.playSound("se/shine"); + globalScene.playSound("se/shine"); this.doSpray(); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.evolutionOverlay, alpha: 1, duration: 250, @@ -112,36 +113,36 @@ export class FormChangePhase extends EvolutionPhase { onComplete: () => { this.evolutionBgOverlay.setAlpha(1); this.evolutionBg.setVisible(false); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.evolutionOverlay, this.pokemonEvoTintSprite ], alpha: 0, duration: 2000, delay: 150, easing: "Sine.easeIn", onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.evolutionBgOverlay, alpha: 0, duration: 250, onComplete: () => { - this.scene.time.delayedCall(250, () => { + globalScene.time.delayedCall(250, () => { this.pokemon.cry(); - this.scene.time.delayedCall(1250, () => { + globalScene.time.delayedCall(1250, () => { let playEvolutionFanfare = false; if (this.formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1) { - this.scene.validateAchv(achvs.MEGA_EVOLVE); + globalScene.validateAchv(achvs.MEGA_EVOLVE); playEvolutionFanfare = true; } else if (this.formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || this.formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) { - this.scene.validateAchv(achvs.GIGANTAMAX); + globalScene.validateAchv(achvs.GIGANTAMAX); playEvolutionFanfare = true; } const delay = playEvolutionFanfare ? 4000 : 1750; - this.scene.playSoundWithoutBgm(playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare"); + globalScene.playSoundWithoutBgm(playEvolutionFanfare ? "evolution_fanfare" : "minor_fanfare"); transformedPokemon.destroy(); - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), null, true, Utils.fixedInt(delay)); - this.scene.time.delayedCall(Utils.fixedInt(delay + 250), () => this.scene.playBgm()); + globalScene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), null, true, Utils.fixedInt(delay)); + globalScene.time.delayedCall(Utils.fixedInt(delay + 250), () => globalScene.playBgm()); }); }); } @@ -166,9 +167,9 @@ export class FormChangePhase extends EvolutionPhase { end(): void { this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); if (this.modal) { - this.scene.ui.revertMode().then(() => { - if (this.scene.ui.getMode() === Mode.PARTY) { - const partyUiHandler = this.scene.ui.getHandler() as PartyUiHandler; + globalScene.ui.revertMode().then(() => { + if (globalScene.ui.getMode() === Mode.PARTY) { + const partyUiHandler = globalScene.ui.getHandler() as PartyUiHandler; partyUiHandler.clearPartySlots(); partyUiHandler.populatePartySlots(); } diff --git a/src/phases/game-over-modifier-reward-phase.ts b/src/phases/game-over-modifier-reward-phase.ts index a698e62e2e1..c98bb5fff04 100644 --- a/src/phases/game-over-modifier-reward-phase.ts +++ b/src/phases/game-over-modifier-reward-phase.ts @@ -1,24 +1,24 @@ -import BattleScene from "#app/battle-scene"; -import { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import { globalScene } from "#app/global-scene"; +import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; export class GameOverModifierRewardPhase extends ModifierRewardPhase { - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene, modifierTypeFunc); + constructor(modifierTypeFunc: ModifierTypeFunc) { + super(modifierTypeFunc); } doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { + globalScene.addModifier(newModifier).then(() => { // Sound loaded into game as is - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.fadeIn(250).then(() => { - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + globalScene.playSound("level_up_fanfare"); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.fadeIn(250).then(() => { + globalScene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => { + globalScene.time.delayedCall(1500, () => globalScene.arenaBg.setVisible(true)); resolve(); }, null, true, 1500); }); diff --git a/src/phases/game-over-phase.ts b/src/phases/game-over-phase.ts index 52d0996b946..5e4e8e1cdf7 100644 --- a/src/phases/game-over-phase.ts +++ b/src/phases/game-over-phase.ts @@ -1,11 +1,12 @@ import { clientSessionId } from "#app/account"; import { BattleType } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; -import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; import { trainerConfigs } from "#app/data/trainer-config"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; import { BattlePhase } from "#app/phases/battle-phase"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; @@ -23,7 +24,7 @@ import * as Utils from "#app/utils"; import { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; -import { SessionSaveData } from "#app/system/game-data"; +import type { SessionSaveData } from "#app/system/game-data"; import PersistentModifierData from "#app/system/modifier-data"; import PokemonData from "#app/system/pokemon-data"; import ChallengeData from "#app/system/challenge-data"; @@ -35,8 +36,8 @@ export class GameOverPhase extends BattlePhase { private isVictory: boolean; private firstRibbons: PokemonSpecies[] = []; - constructor(scene: BattleScene, isVictory: boolean = false) { - super(scene); + constructor(isVictory: boolean = false) { + super(); this.isVictory = isVictory; } @@ -45,47 +46,47 @@ export class GameOverPhase extends BattlePhase { super.start(); // Failsafe if players somehow skip floor 200 in classic mode - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex > 200) { + if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex > 200) { this.isVictory = true; } // Handle Mystery Encounter special Game Over cases // Situations such as when player lost a battle, but it isn't treated as full Game Over - if (!this.isVictory && this.scene.currentBattle.mysteryEncounter?.onGameOver && !this.scene.currentBattle.mysteryEncounter.onGameOver(this.scene)) { + if (!this.isVictory && globalScene.currentBattle.mysteryEncounter?.onGameOver && !globalScene.currentBattle.mysteryEncounter.onGameOver()) { // Do not end the game return this.end(); } // Otherwise, continue standard Game Over logic - if (this.isVictory && this.scene.gameMode.isEndless) { - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + if (this.isVictory && globalScene.gameMode.isEndless) { + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); - this.scene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver()); - } else if (this.isVictory || !this.scene.enableRetries) { + globalScene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver()); + } else if (this.isVictory || !globalScene.enableRetries) { this.handleGameOver(); } else { - this.scene.ui.showText(i18next.t("battle:retryBattle"), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.fadeOut(1250).then(() => { - this.scene.reset(); - this.scene.clearPhaseQueue(); - this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => { - this.scene.pushPhase(new EncounterPhase(this.scene, true)); + globalScene.ui.showText(i18next.t("battle:retryBattle"), null, () => { + globalScene.ui.setMode(Mode.CONFIRM, () => { + globalScene.ui.fadeOut(1250).then(() => { + globalScene.reset(); + globalScene.clearPhaseQueue(); + globalScene.gameData.loadSession(globalScene.sessionSlotId).then(() => { + globalScene.pushPhase(new EncounterPhase(true)); - const availablePartyMembers = this.scene.getPokemonAllowedInBattle().length; + const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - this.scene.pushPhase(new SummonPhase(this.scene, 0)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1)); + globalScene.pushPhase(new SummonPhase(0)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.pushPhase(new SummonPhase(1)); } - if (this.scene.currentBattle.waveIndex > 1 && this.scene.currentBattle.battleType !== BattleType.TRAINER) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + if (globalScene.currentBattle.waveIndex > 1 && globalScene.currentBattle.battleType !== BattleType.TRAINER) { + globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } - this.scene.ui.fadeIn(1250); + globalScene.ui.fadeIn(1250); this.end(); }); }); @@ -96,38 +97,38 @@ export class GameOverPhase extends BattlePhase { handleGameOver(): void { const doGameOver = (newClear: boolean) => { - this.scene.disableMenu = true; - this.scene.time.delayedCall(1000, () => { + globalScene.disableMenu = true; + globalScene.time.delayedCall(1000, () => { let firstClear = false; if (this.isVictory && newClear) { - if (this.scene.gameMode.isClassic) { - firstClear = this.scene.validateAchv(achvs.CLASSIC_VICTORY); - this.scene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); - this.scene.gameData.gameStats.sessionsWon++; - for (const pokemon of this.scene.getPlayerParty()) { + if (globalScene.gameMode.isClassic) { + firstClear = globalScene.validateAchv(achvs.CLASSIC_VICTORY); + globalScene.validateAchv(achvs.UNEVOLVED_CLASSIC_VICTORY); + globalScene.gameData.gameStats.sessionsWon++; + for (const pokemon of globalScene.getPlayerParty()) { this.awardRibbon(pokemon); if (pokemon.species.getRootSpeciesId() !== pokemon.species.getRootSpeciesId(true)) { this.awardRibbon(pokemon, true); } } - } else if (this.scene.gameMode.isDaily && newClear) { - this.scene.gameData.gameStats.dailyRunSessionsWon++; + } else if (globalScene.gameMode.isDaily && newClear) { + globalScene.gameData.gameStats.dailyRunSessionsWon++; } } const fadeDuration = this.isVictory ? 10000 : 5000; - this.scene.fadeOutBgm(fadeDuration, true); - const activeBattlers = this.scene.getField().filter(p => p?.isActive(true)); + globalScene.fadeOutBgm(fadeDuration, true); + const activeBattlers = globalScene.getField().filter(p => p?.isActive(true)); activeBattlers.map(p => p.hideInfo()); - this.scene.ui.fadeOut(fadeDuration).then(() => { + globalScene.ui.fadeOut(fadeDuration).then(() => { activeBattlers.map(a => a.setVisible(false)); - this.scene.setFieldScale(1, true); - this.scene.clearPhaseQueue(); - this.scene.ui.clearText(); + globalScene.setFieldScale(1, true); + globalScene.clearPhaseQueue(); + globalScene.ui.clearText(); - if (this.isVictory && this.scene.gameMode.isChallenge) { - this.scene.gameMode.challenges.forEach(c => this.scene.validateAchvs(ChallengeAchv, c)); + if (this.isVictory && globalScene.gameMode.isChallenge) { + globalScene.gameMode.challenges.forEach(c => globalScene.validateAchvs(ChallengeAchv, c)); } const clear = (endCardPhase?: EndCardPhase) => { @@ -135,34 +136,34 @@ export class GameOverPhase extends BattlePhase { this.handleUnlocks(); for (const species of this.firstRibbons) { - this.scene.unshiftPhase(new RibbonModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PLUS, species)); + globalScene.unshiftPhase(new RibbonModifierRewardPhase(modifierTypes.VOUCHER_PLUS, species)); } if (!firstClear) { - this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM)); + globalScene.unshiftPhase(new GameOverModifierRewardPhase(modifierTypes.VOUCHER_PREMIUM)); } } this.getRunHistoryEntry().then(runHistoryEntry => { - this.scene.gameData.saveRunHistory(this.scene, runHistoryEntry, this.isVictory); - this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase)); + globalScene.gameData.saveRunHistory(runHistoryEntry, this.isVictory); + globalScene.pushPhase(new PostGameOverPhase(endCardPhase)); this.end(); }); }; - if (this.isVictory && this.scene.gameMode.isClassic) { + if (this.isVictory && globalScene.gameMode.isClassic) { const dialogueKey = "miscDialogue:ending"; - if (!this.scene.ui.shouldSkipDialogue(dialogueKey)) { - this.scene.ui.fadeIn(500).then(() => { - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + if (!globalScene.ui.shouldSkipDialogue(dialogueKey)) { + globalScene.ui.fadeIn(500).then(() => { + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); // Dialogue has to be retrieved so that the rival's expressions can be loaded and shown via getCharVariantFromDialogue const dialogue = i18next.t(dialogueKey, { context: genderStr }); - this.scene.charSprite.showCharacter(`rival_${this.scene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(dialogue)).then(() => { - this.scene.ui.showDialogue(dialogueKey, this.scene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { - this.scene.ui.fadeOut(500).then(() => { - this.scene.charSprite.hide().then(() => { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); + globalScene.charSprite.showCharacter(`rival_${globalScene.gameData.gender === PlayerGender.FEMALE ? "m" : "f"}`, getCharVariantFromDialogue(dialogue)).then(() => { + globalScene.ui.showDialogue(dialogueKey, globalScene.gameData.gender === PlayerGender.FEMALE ? trainerConfigs[TrainerType.RIVAL].name : trainerConfigs[TrainerType.RIVAL].nameFemale, null, () => { + globalScene.ui.fadeOut(500).then(() => { + globalScene.charSprite.hide().then(() => { + const endCardPhase = new EndCardPhase(); + globalScene.unshiftPhase(endCardPhase); clear(endCardPhase); }); }); @@ -170,8 +171,8 @@ export class GameOverPhase extends BattlePhase { }); }); } else { - const endCardPhase = new EndCardPhase(this.scene); - this.scene.unshiftPhase(endCardPhase); + const endCardPhase = new EndCardPhase(); + globalScene.unshiftPhase(endCardPhase); clear(endCardPhase); } } else { @@ -185,10 +186,10 @@ export class GameOverPhase extends BattlePhase { If Online, execute apiFetch as intended If Offline, execute offlineNewClear() only for victory, a localStorage implementation of newClear daily run checks */ if (!Utils.isLocal || Utils.isLocalServerConnected) { - pokerogueApi.savedata.session.newclear({ slot: this.scene.sessionSlotId, isVictory: this.isVictory, clientSessionId: clientSessionId }) + pokerogueApi.savedata.session.newclear({ slot: globalScene.sessionSlotId, isVictory: this.isVictory, clientSessionId: clientSessionId }) .then((success) => doGameOver(!!success)); } else if (this.isVictory) { - this.scene.gameData.offlineNewClear(this.scene).then(result => { + globalScene.gameData.offlineNewClear().then(result => { doGameOver(result); }); } else { @@ -197,57 +198,58 @@ export class GameOverPhase extends BattlePhase { } handleUnlocks(): void { - if (this.isVictory && this.scene.gameMode.isClassic) { - if (!this.scene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.ENDLESS_MODE)); + if (this.isVictory && globalScene.gameMode.isClassic) { + if (!globalScene.gameData.unlocks[Unlockables.ENDLESS_MODE]) { + globalScene.unshiftPhase(new UnlockPhase(Unlockables.ENDLESS_MODE)); } - if (this.scene.getPlayerParty().filter(p => p.fusionSpecies).length && !this.scene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.SPLICED_ENDLESS_MODE)); + if (globalScene.getPlayerParty().filter(p => p.fusionSpecies).length && !globalScene.gameData.unlocks[Unlockables.SPLICED_ENDLESS_MODE]) { + globalScene.unshiftPhase(new UnlockPhase(Unlockables.SPLICED_ENDLESS_MODE)); } - if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE)); + if (!globalScene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE]) { + globalScene.unshiftPhase(new UnlockPhase(Unlockables.MINI_BLACK_HOLE)); } - if (!this.scene.gameData.unlocks[Unlockables.EVIOLITE] && this.scene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { - this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.EVIOLITE)); + if (!globalScene.gameData.unlocks[Unlockables.EVIOLITE] && globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)) { + globalScene.unshiftPhase(new UnlockPhase(Unlockables.EVIOLITE)); } } } awardRibbon(pokemon: Pokemon, forStarter: boolean = false): void { const speciesId = getPokemonSpecies(pokemon.species.speciesId); - const speciesRibbonCount = this.scene.gameData.incrementRibbonCount(speciesId, forStarter); + const speciesRibbonCount = globalScene.gameData.incrementRibbonCount(speciesId, forStarter); // first time classic win, award voucher if (speciesRibbonCount === 1) { this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter))); } } + // TODO: Make function use existing getSessionSaveData() function and then modify the values from there. /** * Slightly modified version of {@linkcode GameData.getSessionSaveData}. * @returns A promise containing the {@linkcode SessionSaveData} */ private async getRunHistoryEntry(): Promise { - const preWaveSessionData = await this.scene.gameData.getSession(this.scene.sessionSlotId); + const preWaveSessionData = await globalScene.gameData.getSession(globalScene.sessionSlotId); return { - seed: this.scene.seed, - playTime: this.scene.sessionPlayTime, - gameMode: this.scene.gameMode.modeId, - party: this.scene.getPlayerParty().map(p => new PokemonData(p)), - enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)), - modifiers: preWaveSessionData ? preWaveSessionData.modifiers : this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), - enemyModifiers: preWaveSessionData ? preWaveSessionData.enemyModifiers : this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), - arena: new ArenaData(this.scene.arena), - pokeballCounts: this.scene.pokeballCounts, - money: Math.floor(this.scene.money), - score: this.scene.score, - waveIndex: this.scene.currentBattle.waveIndex, - battleType: this.scene.currentBattle.battleType, - trainer: this.scene.currentBattle.trainer ? new TrainerData(this.scene.currentBattle.trainer) : null, - gameVersion: this.scene.game.config.gameVersion, + seed: globalScene.seed, + playTime: globalScene.sessionPlayTime, + gameMode: globalScene.gameMode.modeId, + party: globalScene.getPlayerParty().map(p => new PokemonData(p)), + enemyParty: globalScene.getEnemyParty().map(p => new PokemonData(p)), + modifiers: preWaveSessionData ? preWaveSessionData.modifiers : globalScene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), + enemyModifiers: preWaveSessionData ? preWaveSessionData.enemyModifiers : globalScene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + arena: new ArenaData(globalScene.arena), + pokeballCounts: globalScene.pokeballCounts, + money: Math.floor(globalScene.money), + score: globalScene.score, + waveIndex: globalScene.currentBattle.waveIndex, + battleType: globalScene.currentBattle.battleType, + trainer: globalScene.currentBattle.trainer ? new TrainerData(globalScene.currentBattle.trainer) : null, + gameVersion: globalScene.game.config.gameVersion, timestamp: new Date().getTime(), - challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)), - mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1, - mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData + challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)), + mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1, + mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData } as SessionSaveData; } } diff --git a/src/phases/hide-party-exp-bar-phase.ts b/src/phases/hide-party-exp-bar-phase.ts index 303650ea1ad..0dce41044c0 100644 --- a/src/phases/hide-party-exp-bar-phase.ts +++ b/src/phases/hide-party-exp-bar-phase.ts @@ -1,14 +1,14 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class HidePartyExpBarPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.partyExpBar.hide().then(() => this.end()); + globalScene.partyExpBar.hide().then(() => this.end()); } } diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index fefda384092..6f10022d1fc 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -1,6 +1,7 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; -import Move, { allMoves } from "#app/data/move"; +import type Move from "#app/data/move"; +import { allMoves } from "#app/data/move"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; import { Moves } from "#enums/moves"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -10,7 +11,7 @@ import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; export enum LearnMoveType { @@ -28,8 +29,8 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private learnMoveType; private cost: number; - constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { - super(scene, partyMemberIndex); + constructor(partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { + super(partyMemberIndex); this.moveId = moveId; this.learnMoveType = learnMoveType; this.cost = cost; @@ -48,8 +49,8 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { return this.end(); } - this.messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler ? Mode.EVOLUTION_SCENE : Mode.MESSAGE; - this.scene.ui.setMode(this.messageMode); + this.messageMode = globalScene.ui.getHandler() instanceof EvolutionSceneHandler ? Mode.EVOLUTION_SCENE : Mode.MESSAGE; + globalScene.ui.setMode(this.messageMode); // If the Pokemon has less than 4 moves, the new move is added to the largest empty moveset index // If it has 4 moves, the phase then checks if the player wants to replace the move itself. if (currentMoveset.length < 4) { @@ -73,12 +74,12 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { const moveLimitReached = i18next.t("battle:learnMoveLimitReached", { pokemonName: getPokemonNameWithAffix(pokemon) }); const shouldReplaceQ = i18next.t("battle:learnMoveReplaceQuestion", { moveName: move.name }); const preQText = [ learnMovePrompt, moveLimitReached ].join("$"); - await this.scene.ui.showTextPromise(preQText); - await this.scene.ui.showTextPromise(shouldReplaceQ, undefined, false); - await this.scene.ui.setModeWithoutClear(Mode.CONFIRM, + await globalScene.ui.showTextPromise(preQText); + await globalScene.ui.showTextPromise(shouldReplaceQ, undefined, false); + await globalScene.ui.setModeWithoutClear(Mode.CONFIRM, () => this.forgetMoveProcess(move, pokemon), // Yes () => { // No - this.scene.ui.setMode(this.messageMode); + globalScene.ui.setMode(this.messageMode); this.rejectMoveAndEnd(move, pokemon); } ); @@ -96,16 +97,16 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { * @param Pokemon The Pokemon learning the move */ async forgetMoveProcess(move: Move, pokemon: Pokemon) { - this.scene.ui.setMode(this.messageMode); - await this.scene.ui.showTextPromise(i18next.t("battle:learnMoveForgetQuestion"), undefined, true); - await this.scene.ui.setModeWithoutClear(Mode.SUMMARY, pokemon, SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { + globalScene.ui.setMode(this.messageMode); + await globalScene.ui.showTextPromise(i18next.t("battle:learnMoveForgetQuestion"), undefined, true); + await globalScene.ui.setModeWithoutClear(Mode.SUMMARY, pokemon, SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { if (moveIndex === 4) { - this.scene.ui.setMode(this.messageMode).then(() => this.rejectMoveAndEnd(move, pokemon)); + globalScene.ui.setMode(this.messageMode).then(() => this.rejectMoveAndEnd(move, pokemon)); return; } const forgetSuccessText = i18next.t("battle:learnMoveForgetSuccess", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: pokemon.moveset[moveIndex]!.getName() }); const fullText = [ i18next.t("battle:countdownPoof"), forgetSuccessText, i18next.t("battle:learnMoveAnd") ].join("$"); - this.scene.ui.setMode(this.messageMode).then(() => this.learnMove(moveIndex, move, pokemon, fullText)); + globalScene.ui.setMode(this.messageMode).then(() => this.learnMove(moveIndex, move, pokemon, fullText)); }); } @@ -120,14 +121,14 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { * @param Pokemon The Pokemon learning the move */ async rejectMoveAndEnd(move: Move, pokemon: Pokemon) { - await this.scene.ui.showTextPromise(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), undefined, false); - this.scene.ui.setModeWithoutClear(Mode.CONFIRM, + await globalScene.ui.showTextPromise(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), undefined, false); + globalScene.ui.setModeWithoutClear(Mode.CONFIRM, () => { - this.scene.ui.setMode(this.messageMode); - this.scene.ui.showTextPromise(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), undefined, true).then(() => this.end()); + globalScene.ui.setMode(this.messageMode); + globalScene.ui.showTextPromise(i18next.t("battle:learnMoveNotLearned", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }), undefined, true).then(() => this.end()); }, () => { - this.scene.ui.setMode(this.messageMode); + globalScene.ui.setMode(this.messageMode); this.replaceMoveCheck(move, pokemon); } ); @@ -154,31 +155,31 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); - this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); } else if (this.learnMoveType === LearnMoveType.MEMORY) { if (this.cost !== -1) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= this.cost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); + globalScene.money -= this.cost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); } - this.scene.playSound("se/buy"); + globalScene.playSound("se/buy"); } else { - this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + globalScene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); } } pokemon.setMove(index, this.moveId); - initMoveAnim(this.scene, this.moveId).then(() => { - loadMoveAnimAssets(this.scene, [ this.moveId ], true); + initMoveAnim(this.moveId).then(() => { + loadMoveAnimAssets([ this.moveId ], true); }); - this.scene.ui.setMode(this.messageMode); + globalScene.ui.setMode(this.messageMode); const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }); if (textMessage) { - await this.scene.ui.showTextPromise(textMessage); + await globalScene.ui.showTextPromise(textMessage); } - this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is - this.scene.ui.showText(learnMoveText, null, () => { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + globalScene.playSound("level_up_fanfare"); // Sound loaded into game as is + globalScene.ui.showText(learnMoveText, null, () => { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); this.end(); }, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); } diff --git a/src/phases/level-cap-phase.ts b/src/phases/level-cap-phase.ts index d1404e45010..d75bc3be6d4 100644 --- a/src/phases/level-cap-phase.ts +++ b/src/phases/level-cap-phase.ts @@ -1,20 +1,20 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; export class LevelCapPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start(): void { super.start(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => { + globalScene.ui.setMode(Mode.MESSAGE).then(() => { // Sound loaded into game as is - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: this.scene.getMaxExpLevel() }), null, () => this.end(), null, true); + globalScene.playSound("level_up_fanfare"); + globalScene.ui.showText(i18next.t("battle:levelCapUp", { levelCap: globalScene.getMaxExpLevel() }), null, () => this.end(), null, true); this.executeForAll(pokemon => pokemon.updateInfo(true)); }); } diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 4f26abc5af3..450ecca0c70 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -1,4 +1,4 @@ -import type BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { ExpNotification } from "#app/enums/exp-notification"; import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -14,8 +14,8 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { protected level: number; protected pokemon: PlayerPokemon = this.getPlayerPokemon(); - constructor(scene: BattleScene, partyMemberIndex: number, lastLevel: number, level: number) { - super(scene, partyMemberIndex); + constructor(partyMemberIndex: number, lastLevel: number, level: number) { + super(partyMemberIndex); this.lastLevel = lastLevel; this.level = level; @@ -24,27 +24,27 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { public override start() { super.start(); - if (this.level > this.scene.gameData.gameStats.highestLevel) { - this.scene.gameData.gameStats.highestLevel = this.level; + if (this.level > globalScene.gameData.gameStats.highestLevel) { + globalScene.gameData.gameStats.highestLevel = this.level; } - this.scene.validateAchvs(LevelAchv, new NumberHolder(this.level)); + globalScene.validateAchvs(LevelAchv, new NumberHolder(this.level)); const prevStats = this.pokemon.stats.slice(0); this.pokemon.calculateStats(); this.pokemon.updateInfo(); - if (this.scene.expParty === ExpNotification.DEFAULT) { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText( + if (globalScene.expParty === ExpNotification.DEFAULT) { + globalScene.playSound("level_up_fanfare"); + globalScene.ui.showText( i18next.t("battle:levelUp", { pokemonName: getPokemonNameWithAffix(this.pokemon), level: this.level }), null, - () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false) + () => globalScene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false) .then(() => this.end()), null, true); - } else if (this.scene.expParty === ExpNotification.SKIP) { + } else if (globalScene.expParty === ExpNotification.SKIP) { this.end(); } else { // we still want to display the stats if activated - this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); + globalScene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false).then(() => this.end()); } } @@ -52,13 +52,13 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { if (this.lastLevel < 100) { // this feels like an unnecessary optimization const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); for (const lm of levelMoves) { - this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm[1])); + globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); } } if (!this.pokemon.pauseEvolutions) { const evolution = this.pokemon.getEvolution(); if (evolution) { - this.scene.unshiftPhase(new EvolutionPhase(this.scene, this.pokemon, evolution, this.lastLevel)); + globalScene.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel)); } } return super.end(); diff --git a/src/phases/login-phase.ts b/src/phases/login-phase.ts index ac1e68d1b0e..0ed8b6feb88 100644 --- a/src/phases/login-phase.ts +++ b/src/phases/login-phase.ts @@ -1,5 +1,6 @@ import { updateUserInfo } from "#app/account"; -import BattleScene, { bypassLogin } from "#app/battle-scene"; +import { bypassLogin } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { handleTutorial, Tutorial } from "#app/tutorial"; import { Mode } from "#app/ui/ui"; @@ -11,8 +12,8 @@ import { UnavailablePhase } from "./unavailable-phase"; export class LoginPhase extends Phase { private showText: boolean; - constructor(scene: BattleScene, showText?: boolean) { - super(scene); + constructor(showText?: boolean) { + super(); this.showText = showText === undefined || !!showText; } @@ -22,50 +23,50 @@ export class LoginPhase extends Phase { const hasSession = !!Utils.getCookie(Utils.sessionIdKey); - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); Utils.executeIf(bypassLogin || hasSession, updateUserInfo).then(response => { const success = response ? response[0] : false; const statusCode = response ? response[1] : null; if (!success) { if (!statusCode || statusCode === 400) { if (this.showText) { - this.scene.ui.showText(i18next.t("menu:logInOrCreateAccount")); + globalScene.ui.showText(i18next.t("menu:logInOrCreateAccount")); } - this.scene.playSound("menu_open"); + globalScene.playSound("menu_open"); const loadData = () => { updateUserInfo().then(success => { if (!success[0]) { Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); + globalScene.reset(true, true); return; } - this.scene.gameData.loadSystem().then(() => this.end()); + globalScene.gameData.loadSystem().then(() => this.end()); }); }; - this.scene.ui.setMode(Mode.LOGIN_FORM, { + globalScene.ui.setMode(Mode.LOGIN_FORM, { buttonActions: [ () => { - this.scene.ui.playSelect(); + globalScene.ui.playSelect(); loadData(); }, () => { - this.scene.playSound("menu_open"); - this.scene.ui.setMode(Mode.REGISTRATION_FORM, { + globalScene.playSound("menu_open"); + globalScene.ui.setMode(Mode.REGISTRATION_FORM, { buttonActions: [ () => { - this.scene.ui.playSelect(); + globalScene.ui.playSelect(); updateUserInfo().then(success => { if (!success[0]) { Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); + globalScene.reset(true, true); return; } this.end(); } ); }, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, false)); + globalScene.unshiftPhase(new LoginPhase(false)); this.end(); } ] @@ -85,19 +86,19 @@ export class LoginPhase extends Phase { }); } else if (statusCode === 401) { Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); + globalScene.reset(true, true); } else { - this.scene.unshiftPhase(new UnavailablePhase(this.scene)); + globalScene.unshiftPhase(new UnavailablePhase()); super.end(); } return null; } else { - this.scene.gameData.loadSystem().then(success => { + globalScene.gameData.loadSystem().then(success => { if (success || bypassLogin) { this.end(); } else { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(t("menu:failedToLoadSaveData")); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(t("menu:failedToLoadSaveData")); } }); } @@ -105,12 +106,12 @@ export class LoginPhase extends Phase { } end(): void { - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); - if (!this.scene.gameData.gender) { - this.scene.unshiftPhase(new SelectGenderPhase(this.scene)); + if (!globalScene.gameData.gender) { + globalScene.unshiftPhase(new SelectGenderPhase()); } - handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); + handleTutorial(Tutorial.Intro).then(() => super.end()); } } diff --git a/src/phases/message-phase.ts b/src/phases/message-phase.ts index 1d953801178..9439d8286c3 100644 --- a/src/phases/message-phase.ts +++ b/src/phases/message-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; export class MessagePhase extends Phase { @@ -8,8 +8,8 @@ export class MessagePhase extends Phase { private promptDelay: integer | null; private speaker?: string; - constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, speaker?: string) { - super(scene); + constructor(text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, speaker?: string) { + super(); this.text = text; this.callbackDelay = callbackDelay!; // TODO: is this bang correct? @@ -23,20 +23,20 @@ export class MessagePhase extends Phase { if (this.text.indexOf("$") > -1) { const pageIndex = this.text.indexOf("$"); - this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay, this.speaker)); + globalScene.unshiftPhase(new MessagePhase(this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay, this.speaker)); this.text = this.text.slice(0, pageIndex).trim(); } if (this.speaker) { - this.scene.ui.showDialogue(this.text, this.speaker, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.promptDelay ?? 0); + globalScene.ui.showDialogue(this.text, this.speaker, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.promptDelay ?? 0); } else { - this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); + globalScene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); } } end() { - if (this.scene.abilityBar.shown) { - this.scene.abilityBar.hide(); + if (globalScene.abilityBar.shown) { + globalScene.abilityBar.hide(); } super.end(); diff --git a/src/phases/modifier-reward-phase.ts b/src/phases/modifier-reward-phase.ts index 20a8366d9c6..1cdfd6b2721 100644 --- a/src/phases/modifier-reward-phase.ts +++ b/src/phases/modifier-reward-phase.ts @@ -1,13 +1,14 @@ -import BattleScene from "#app/battle-scene"; -import { ModifierType, ModifierTypeFunc, getModifierType } from "#app/modifier/modifier-type"; +import { globalScene } from "#app/global-scene"; +import type { ModifierType, ModifierTypeFunc } from "#app/modifier/modifier-type"; +import { getModifierType } from "#app/modifier/modifier-type"; import i18next from "i18next"; import { BattlePhase } from "./battle-phase"; export class ModifierRewardPhase extends BattlePhase { protected modifierType: ModifierType; - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { - super(scene); + constructor(modifierTypeFunc: ModifierTypeFunc) { + super(); this.modifierType = getModifierType(modifierTypeFunc); } @@ -21,9 +22,9 @@ export class ModifierRewardPhase extends BattlePhase { doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("item_fanfare"); - this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); + globalScene.addModifier(newModifier).then(() => { + globalScene.playSound("item_fanfare"); + globalScene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }), null, () => resolve(), null, true); }); }); } diff --git a/src/phases/money-reward-phase.ts b/src/phases/money-reward-phase.ts index 2f0a4f7b990..70f0019227c 100644 --- a/src/phases/money-reward-phase.ts +++ b/src/phases/money-reward-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { ArenaTagType } from "#app/enums/arena-tag-type"; import { MoneyMultiplierModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -8,27 +8,27 @@ import { BattlePhase } from "./battle-phase"; export class MoneyRewardPhase extends BattlePhase { private moneyMultiplier: number; - constructor(scene: BattleScene, moneyMultiplier: number) { - super(scene); + constructor(moneyMultiplier: number) { + super(); this.moneyMultiplier = moneyMultiplier; } start() { - const moneyAmount = new Utils.IntegerHolder(this.scene.getWaveMoneyAmount(this.moneyMultiplier)); + const moneyAmount = new Utils.IntegerHolder(globalScene.getWaveMoneyAmount(this.moneyMultiplier)); - this.scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); + globalScene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); - if (this.scene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { + if (globalScene.arena.getTag(ArenaTagType.HAPPY_HOUR)) { moneyAmount.value *= 2; } - this.scene.addMoney(moneyAmount.value); + globalScene.addMoney(moneyAmount.value); const userLocale = navigator.language || "en-US"; const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); const message = i18next.t("battle:moneyWon", { moneyAmount: formattedMoneyAmount }); - this.scene.ui.showText(message, null, () => this.end(), null, true); + globalScene.ui.showText(message, null, () => this.end(), null, true); } } diff --git a/src/phases/move-anim-test-phase.ts b/src/phases/move-anim-test-phase.ts index e4b04ce5de6..588fc402357 100644 --- a/src/phases/move-anim-test-phase.ts +++ b/src/phases/move-anim-test-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets, MoveAnim } from "#app/data/battle-anims"; import { allMoves, SelfStatusMove } from "#app/data/move"; import { Moves } from "#app/enums/moves"; @@ -8,8 +8,8 @@ import { BattlePhase } from "./battle-phase"; export class MoveAnimTestPhase extends BattlePhase { private moveQueue: Moves[]; - constructor(scene: BattleScene, moveQueue?: Moves[]) { - super(scene); + constructor(moveQueue?: Moves[]) { + super(); this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1); } @@ -28,12 +28,12 @@ export class MoveAnimTestPhase extends BattlePhase { console.log(Moves[moveId]); } - initMoveAnim(this.scene, moveId).then(() => { - loadMoveAnimAssets(this.scene, [ moveId ], true) + initMoveAnim(moveId).then(() => { + loadMoveAnimAssets([ moveId ], true) .then(() => { - const user = player ? this.scene.getPlayerPokemon()! : this.scene.getEnemyPokemon()!; - const target = (player !== (allMoves[moveId] instanceof SelfStatusMove)) ? this.scene.getEnemyPokemon()! : this.scene.getPlayerPokemon()!; - new MoveAnim(moveId, user, target.getBattlerIndex()).play(this.scene, allMoves[moveId].hitsSubstitute(user, target), () => { // TODO: are the bangs correct here? + const user = player ? globalScene.getPlayerPokemon()! : globalScene.getEnemyPokemon()!; + const target = (player !== (allMoves[moveId] instanceof SelfStatusMove)) ? globalScene.getEnemyPokemon()! : globalScene.getPlayerPokemon()!; + new MoveAnim(moveId, user, target.getBattlerIndex()).play(allMoves[moveId].hitsSubstitute(user, target), () => { // TODO: are the bangs correct here? if (player) { this.playMoveAnim(moveQueue, false); } else { diff --git a/src/phases/move-charge-phase.ts b/src/phases/move-charge-phase.ts index d1dc340b81b..b0925f1f6cb 100644 --- a/src/phases/move-charge-phase.ts +++ b/src/phases/move-charge-phase.ts @@ -1,8 +1,10 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { MoveChargeAnim } from "#app/data/battle-anims"; import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move"; -import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { BooleanHolder } from "#app/utils"; import { MovePhase } from "#app/phases/move-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase"; @@ -19,8 +21,8 @@ export class MoveChargePhase extends PokemonPhase { /** The field index targeted by the move (Charging moves assume single target) */ public targetIndex: BattlerIndex; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, targetIndex: BattlerIndex, move: PokemonMove) { + super(battlerIndex); this.move = move; this.targetIndex = targetIndex; } @@ -39,7 +41,7 @@ export class MoveChargePhase extends PokemonPhase { return super.end(); } - new MoveChargeAnim(move.chargeAnim, move.id, user).play(this.scene, false, () => { + new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => { move.showChargeText(user, target); applyMoveChargeAttrs(MoveEffectAttr, user, target, move).then(() => { @@ -61,9 +63,9 @@ export class MoveChargePhase extends PokemonPhase { if (instantCharge.value) { // this MoveEndPhase will be duplicated by the queued MovePhase if not removed - this.scene.tryRemovePhase((phase) => phase instanceof MoveEndPhase && phase.getPokemon() === user); + globalScene.tryRemovePhase((phase) => phase instanceof MoveEndPhase && phase.getPokemon() === user); // queue a new MovePhase for this move's attack phase - this.scene.unshiftPhase(new MovePhase(this.scene, user, [ this.targetIndex ], this.move, false)); + globalScene.unshiftPhase(new MovePhase(user, [ this.targetIndex ], this.move, false)); } else { user.getMoveQueue().push({ move: move.id, targets: [ this.targetIndex ]}); } @@ -75,10 +77,10 @@ export class MoveChargePhase extends PokemonPhase { } public getUserPokemon(): Pokemon { - return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; + return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex]; } public getTargetPokemon(): Pokemon | undefined { - return this.scene.getField(true).find((p) => this.targetIndex === p.getBattlerIndex()); + return globalScene.getField(true).find((p) => this.targetIndex === p.getBattlerIndex()); } } diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index d08fc46e563..fff8caf38b5 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { AddSecondStrikeAbAttr, AlwaysHitAbAttr, @@ -23,6 +23,8 @@ import { SemiInvulnerableTag, SubstituteTag, } from "#app/data/battler-tags"; +import type { + MoveAttr } from "#app/data/move"; import { applyFilteredMoveAttrs, applyMoveAttrs, @@ -31,7 +33,6 @@ import { FlinchAttr, HitsTagAttr, MissEffectAttr, - MoveAttr, MoveCategory, MoveEffectAttr, MoveEffectTrigger, @@ -46,7 +47,9 @@ import { } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { Type } from "#enums/type"; -import Pokemon, { HitResult, MoveResult, PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { HitResult, MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ContactHeldItemTransferChanceModifier, @@ -58,15 +61,15 @@ import { import { PokemonPhase } from "#app/phases/pokemon-phase"; import { BooleanHolder, executeIf, isNullOrUndefined, NumberHolder } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import i18next from "i18next"; export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; protected targets: BattlerIndex[]; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) { + super(battlerIndex); this.move = move; /** * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies @@ -149,7 +152,7 @@ export class MoveEffectPhase extends PokemonPhase { // If Parental Bond is applicable, add another hit applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses - this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); + globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount); // Set the user's relevant turnData fields to reflect the final hit count user.turnData.hitCount = hitCount.value; user.turnData.hitsLeft = hitCount.value; @@ -182,11 +185,11 @@ export class MoveEffectPhase extends PokemonPhase { if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { this.stopMultiHit(); if (hasActiveTargets) { - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" })); + globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" })); moveHistoryEntry.result = MoveResult.MISS; applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove()); } else { - this.scene.queueMessage(i18next.t("battle:attackFailed")); + globalScene.queueMessage(i18next.t("battle:attackFailed")); moveHistoryEntry.result = MoveResult.FAIL; } user.pushMoveHistory(moveHistoryEntry); @@ -196,9 +199,9 @@ export class MoveEffectPhase extends PokemonPhase { /** All move effect attributes are chained together in this array to be applied asynchronously. */ const applyAttrs: Promise[] = []; - const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; + const playOnEmptyField = globalScene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false; // Move animation only needs one target - new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(this.scene, move.hitsSubstitute(user, this.getFirstTarget()!), () => { + new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(move.hitsSubstitute(user, this.getFirstTarget()!), () => { /** Has the move successfully hit a target (for damage) yet? */ let hasHit: boolean = false; for (const target of targets) { @@ -215,7 +218,7 @@ export class MoveEffectPhase extends PokemonPhase { const bypassIgnoreProtect = new BooleanHolder(false); /** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */ if (!this.move.getMove().isAllyTarget()) { - this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); + globalScene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect); } /** Is the target protected by Protect, etc. or a relevant conditional protection effect? */ @@ -234,7 +237,7 @@ export class MoveEffectPhase extends PokemonPhase { && !target.getTag(SemiInvulnerableTag); /** Is the target hidden by the effects of its Commander ability? */ - const isCommanding = this.scene.currentBattle.double && target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon(this.scene) === target; + const isCommanding = globalScene.currentBattle.double && target.getAlly()?.getTag(BattlerTagType.COMMANDED)?.getSourcePokemon() === target; /** * If the move missed a target, stop all future hits against that target @@ -243,7 +246,7 @@ export class MoveEffectPhase extends PokemonPhase { if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) { this.stopMultiHit(target); if (!target.switchOutStatus) { - this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); + globalScene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); } if (moveHistoryEntry.result === MoveResult.PENDING) { moveHistoryEntry.result = MoveResult.MISS; @@ -312,7 +315,7 @@ export class MoveEffectPhase extends PokemonPhase { * (see Relic Song's interaction with Parental Bond when used by Meloetta). */ if (lastHit) { - this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); + globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); /** * Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr * other damage source are calculated in damageAndUpdate in pokemon.ts @@ -404,16 +407,16 @@ export class MoveEffectPhase extends PokemonPhase { */ if (user) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getFirstTarget()?.isActive()) { - this.scene.unshiftPhase(this.getNewHitPhase()); + globalScene.unshiftPhase(this.getNewHitPhase()); } else { // Queue message for number of hits made by multi-move // If multi-hit attack only hits once, still want to render a message const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); if (hitsTotal > 1 || (user.turnData.hitsLeft && user.turnData.hitsLeft > 0)) { // If there are multiple hits, or if there are hits of the multi-hit move left - this.scene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); + globalScene.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal })); } - this.scene.applyModifiers(HitHealModifier, this.player, user); + globalScene.applyModifiers(HitHealModifier, this.player, user); // Clear all cached move effectiveness values among targets this.getTargets().forEach((target) => target.turnData.moveEffectiveness = null); } @@ -492,7 +495,7 @@ export class MoveEffectPhase extends PokemonPhase { if (!this.move.getMove().hitsSubstitute(user, target)) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { - user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); + globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); } target.lapseTags(BattlerTagLapseType.AFTER_HIT); @@ -521,7 +524,7 @@ export class MoveEffectPhase extends PokemonPhase { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult)).then(() => { // Item Stealing Effects if (this.move.getMove() instanceof AttackMove) { - this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); + globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); } }) ); @@ -542,7 +545,7 @@ export class MoveEffectPhase extends PokemonPhase { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.getMove().hitsSubstitute(user, target)) { const flinched = new BooleanHolder(false); - user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); + globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); if (flinched.value) { target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); } @@ -616,14 +619,14 @@ export class MoveEffectPhase extends PokemonPhase { /** @returns The {@linkcode Pokemon} using this phase's invoked move */ public getUserPokemon(): Pokemon | null { if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex); + return globalScene.getPokemonById(this.battlerIndex); } - return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex]; + return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex]; } /** @returns An array of all {@linkcode Pokemon} targeted by this phase's invoked move */ public getTargets(): Pokemon[] { - return this.scene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); + return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1); } /** @returns The first target of this phase's invoked move */ @@ -665,6 +668,6 @@ export class MoveEffectPhase extends PokemonPhase { /** @returns A new `MoveEffectPhase` with the same properties as this phase */ protected getNewHitPhase(): MoveEffectPhase { - return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); + return new MoveEffectPhase(this.battlerIndex, this.targets, this.move); } } diff --git a/src/phases/move-end-phase.ts b/src/phases/move-end-phase.ts index e03f2ec14b0..428dacd639a 100644 --- a/src/phases/move-end-phase.ts +++ b/src/phases/move-end-phase.ts @@ -1,11 +1,11 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { PokemonPhase } from "./pokemon-phase"; export class MoveEndPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex) { + super(battlerIndex); } start() { @@ -16,7 +16,7 @@ export class MoveEndPhase extends PokemonPhase { pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); } - this.scene.arena.setIgnoreAbilities(false); + globalScene.arena.setIgnoreAbilities(false); this.end(); } diff --git a/src/phases/move-header-phase.ts b/src/phases/move-header-phase.ts index c307ff0be6e..6a982646b50 100644 --- a/src/phases/move-header-phase.ts +++ b/src/phases/move-header-phase.ts @@ -1,14 +1,14 @@ -import BattleScene from "#app/battle-scene"; import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/move"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class MoveHeaderPhase extends BattlePhase { public pokemon: Pokemon; public move: PokemonMove; - constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { - super(scene); + constructor(pokemon: Pokemon, move: PokemonMove) { + super(); this.pokemon = pokemon; this.move = move; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 089386bee00..0673ad3effe 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyAbAttrs, applyPostMoveUsedAbAttrs, @@ -11,7 +11,7 @@ import { RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability"; -import { DelayedAttackTag } from "#app/data/arena-tag"; +import type { DelayedAttackTag } from "#app/data/arena-tag"; import { CommonAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { @@ -31,7 +31,9 @@ import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/dat import { Type } from "#enums/type"; import { getTerrainBlockMessage } from "#app/data/weather"; import { MoveUsedEvent } from "#app/events/battle-scene"; -import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon"; +import type { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { BattlePhase } from "#app/phases/battle-phase"; @@ -85,8 +87,8 @@ export class MovePhase extends BattlePhase { * @param followUp Indicates that the move being uses is a "follow-up" - for example, a move being used by Metronome or Dancer. * Follow-ups bypass a few failure conditions, including flinches, sleep/paralysis/freeze and volatile status checks, etc. */ - constructor(scene: BattleScene, pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false) { - super(scene); + constructor(pokemon: Pokemon, targets: BattlerIndex[], move: PokemonMove, followUp: boolean = false, ignorePp: boolean = false) { + super(); this.pokemon = pokemon; this.targets = targets; @@ -140,7 +142,7 @@ export class MovePhase extends BattlePhase { // Check move to see if arena.ignoreAbilities should be true. if (!this.followUp) { if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) { - this.scene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); + globalScene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex()); } } @@ -180,7 +182,7 @@ export class MovePhase extends BattlePhase { } public getActiveTargetPokemon(): Pokemon[] { - return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); + return globalScene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); } /** @@ -219,10 +221,10 @@ export class MovePhase extends BattlePhase { if (activated) { this.cancel(); - this.scene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); - this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); + globalScene.queueMessage(getStatusEffectActivationText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + globalScene.unshiftPhase(new CommonAnimPhase(this.pokemon.getBattlerIndex(), undefined, CommonAnim.POISON + (this.pokemon.status.effect - 1))); } else if (healed) { - this.scene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); + globalScene.queueMessage(getStatusEffectHealText(this.pokemon.status.effect, getPokemonNameWithAffix(this.pokemon))); this.pokemon.resetStatus(); this.pokemon.updateInfo(); } @@ -247,13 +249,13 @@ export class MovePhase extends BattlePhase { const moveQueue = this.pokemon.getMoveQueue(); // form changes happen even before we know that the move wll execute. - this.scene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); + globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); if (isDelayedAttack) { // Check the player side arena if future sight is active - const futureSightTags = this.scene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); - const doomDesireTags = this.scene.arena.findTags(t => t.tagType === ArenaTagType.DOOM_DESIRE); + const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); + const doomDesireTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.DOOM_DESIRE); let fail = false; const currentTargetIndex = targets[0].getBattlerIndex(); for (const tag of futureSightTags) { @@ -291,12 +293,12 @@ export class MovePhase extends BattlePhase { const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); + globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } // Update the battle's "last move" pointer, unless we're currently mimicking a move. if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { - this.scene.currentBattle.lastMove = this.move.moveId; + globalScene.currentBattle.lastMove = this.move.moveId; } /** @@ -317,8 +319,8 @@ export class MovePhase extends BattlePhase { * TODO: is this sustainable? */ const passesConditions = move.applyConditions(this.pokemon, targets[0], move); - const failedDueToWeather: boolean = this.scene.arena.isMoveWeatherCancelled(this.pokemon, move); - const failedDueToTerrain: boolean = this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move); + const failedDueToWeather: boolean = globalScene.arena.isMoveWeatherCancelled(this.pokemon, move); + const failedDueToTerrain: boolean = globalScene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, move); const success = passesConditions && !failedDueToWeather && !failedDueToTerrain; @@ -330,7 +332,7 @@ export class MovePhase extends BattlePhase { */ if (success) { applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); - this.scene.unshiftPhase(new MoveEffectPhase(this.scene, this.pokemon.getBattlerIndex(), this.targets, this.move)); + globalScene.unshiftPhase(new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, this.move)); } else { if ([ Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE ].includes(this.move.moveId)) { @@ -345,7 +347,7 @@ export class MovePhase extends BattlePhase { if (failureMessage) { failedText = failureMessage; } else if (failedDueToTerrain) { - failedText = getTerrainBlockMessage(this.pokemon, this.scene.arena.getTerrainType()); + failedText = getTerrainBlockMessage(this.pokemon, globalScene.arena.getTerrainType()); } this.showFailedText(failedText); @@ -357,7 +359,7 @@ export class MovePhase extends BattlePhase { // Handle Dancer, which triggers immediately after a move is used (rather than waiting on `this.end()`). // Note that the `!this.followUp` check here prevents an infinite Dancer loop. if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { - this.scene.getField(true).forEach(pokemon => { + globalScene.getField(true).forEach(pokemon => { applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); }); } @@ -373,7 +375,7 @@ export class MovePhase extends BattlePhase { applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); this.showMoveText(); - this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move)); + globalScene.unshiftPhase(new MoveChargePhase(this.pokemon.getBattlerIndex(), this.targets[0], this.move)); } else { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); @@ -392,7 +394,7 @@ export class MovePhase extends BattlePhase { */ public end(): void { if (!this.followUp && this.canMove()) { - this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); + globalScene.unshiftPhase(new MoveEndPhase(this.pokemon.getBattlerIndex())); } super.end(); @@ -420,7 +422,7 @@ export class MovePhase extends BattlePhase { const redirectTarget = new NumberHolder(currentTarget); // check move redirection abilities of every pokemon *except* the user. - this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); + globalScene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); /** `true` if an Ability is responsible for redirecting the move to another target; `false` otherwise */ let redirectedByAbility = (currentTarget !== redirectTarget.value); @@ -447,7 +449,7 @@ export class MovePhase extends BattlePhase { if (this.pokemon.hasAbilityWithAttr(BlockRedirectAbAttr)) { redirectTarget.value = currentTarget; - this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); + globalScene.unshiftPhase(new ShowAbilityPhase(this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr))); } this.targets[0] = redirectTarget.value; @@ -470,9 +472,9 @@ export class MovePhase extends BattlePhase { // account for metal burst and comeuppance hitting remaining targets in double battles // counterattack will redirect to remaining ally if original attacker faints - if (this.scene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { - if (this.scene.getField()[this.targets[0]].hp === 0) { - const opposingField = this.pokemon.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField(); + if (globalScene.currentBattle.double && this.move.getMove().hasFlag(MoveFlags.REDIRECT_COUNTER)) { + if (globalScene.getField()[this.targets[0]].hp === 0) { + const opposingField = this.pokemon.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField(); this.targets[0] = opposingField.find(p => p.hp > 0)?.getBattlerIndex() ?? BattlerIndex.ATTACKER; } } @@ -509,7 +511,7 @@ export class MovePhase extends BattlePhase { this.move.usePp(); } - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); + globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); } if (this.cancelled && this.pokemon.summonData?.tags?.find(t => t.tagType === BattlerTagType.FRENZY)) { @@ -538,7 +540,7 @@ export class MovePhase extends BattlePhase { return; } - this.scene.queueMessage(i18next.t("battle:useMove", { + globalScene.queueMessage(i18next.t("battle:useMove", { pokemonNameWithAffix: getPokemonNameWithAffix(this.pokemon), moveName: this.move.getName() }), 500); @@ -546,6 +548,6 @@ export class MovePhase extends BattlePhase { } public showFailedText(failedText?: string): void { - this.scene.queueMessage(failedText ?? i18next.t("battle:attackFailed")); + globalScene.queueMessage(failedText ?? i18next.t("battle:attackFailed")); } } diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 2d1c3c4ae31..be07dbfc196 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -1,5 +1,6 @@ import { BattlerTagLapseType } from "#app/data/battler-tags"; -import MysteryEncounterOption, { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option"; +import type { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option"; +import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; @@ -17,9 +18,10 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { SwitchType } from "#enums/switch-type"; import i18next from "i18next"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { getCharVariantFromDialogue } from "../data/dialogue"; -import { OptionSelectSettings, transitionMysteryEncounterIntroVisuals } from "../data/mystery-encounters/utils/encounter-phase-utils"; +import type { OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils"; +import { transitionMysteryEncounterIntroVisuals } from "../data/mystery-encounters/utils/encounter-phase-utils"; import { TrainerSlot } from "../data/trainer-config"; import { IvScannerModifier } from "../modifier/modifier"; import { Phase } from "../phase"; @@ -42,11 +44,10 @@ export class MysteryEncounterPhase extends Phase { /** * Mostly useful for having repeated queries during a single encounter, where the queries and options may differ each time - * @param scene * @param optionSelectSettings allows overriding the typical options of an encounter with new ones */ - constructor(scene: BattleScene, optionSelectSettings?: OptionSelectSettings) { - super(scene); + constructor(optionSelectSettings?: OptionSelectSettings) { + super(); this.optionSelectSettings = optionSelectSettings; } @@ -57,20 +58,20 @@ export class MysteryEncounterPhase extends Phase { super.start(); // Clears out queued phases that are part of standard battle - this.scene.clearPhaseQueue(); - this.scene.clearPhaseQueueSplice(); + globalScene.clearPhaseQueue(); + globalScene.clearPhaseQueueSplice(); - const encounter = this.scene.currentBattle.mysteryEncounter!; - encounter.updateSeedOffset(this.scene); + const encounter = globalScene.currentBattle.mysteryEncounter!; + encounter.updateSeedOffset(); if (!this.optionSelectSettings) { // Sets flag that ME was encountered, only if this is not a followup option select phase // Can be used in later MEs to check for requirements to spawn, run history, etc. - this.scene.mysteryEncounterSaveData.encounteredEvents.push(new SeenEncounterData(encounter.encounterType, encounter.encounterTier, this.scene.currentBattle.waveIndex)); + globalScene.mysteryEncounterSaveData.encounteredEvents.push(new SeenEncounterData(encounter.encounterType, encounter.encounterTier, globalScene.currentBattle.waveIndex)); } // Initiates encounter dialogue window and option select - this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.optionSelectSettings); + globalScene.ui.setMode(Mode.MYSTERY_ENCOUNTER, this.optionSelectSettings); } /** @@ -80,13 +81,13 @@ export class MysteryEncounterPhase extends Phase { */ handleOptionSelect(option: MysteryEncounterOption, index: number): boolean { // Set option selected flag - this.scene.currentBattle.mysteryEncounter!.selectedOption = option; + globalScene.currentBattle.mysteryEncounter!.selectedOption = option; if (!this.optionSelectSettings) { // Saves the selected option in the ME save data, only if this is not a followup option select phase // Can be used for analytics purposes to track what options are popular on certain encounters - const encounterSaveData = this.scene.mysteryEncounterSaveData.encounteredEvents[this.scene.mysteryEncounterSaveData.encounteredEvents.length - 1]; - if (encounterSaveData.type === this.scene.currentBattle.mysteryEncounter?.encounterType) { + const encounterSaveData = globalScene.mysteryEncounterSaveData.encounteredEvents[globalScene.mysteryEncounterSaveData.encounteredEvents.length - 1]; + if (encounterSaveData.type === globalScene.currentBattle.mysteryEncounter?.encounterType) { encounterSaveData.selectedOption = index; } } @@ -96,17 +97,17 @@ export class MysteryEncounterPhase extends Phase { } // Populate dialogue tokens for option requirements - this.scene.currentBattle.mysteryEncounter!.populateDialogueTokensFromRequirements(this.scene); + globalScene.currentBattle.mysteryEncounter!.populateDialogueTokensFromRequirements(); if (option.onPreOptionPhase) { - this.scene.executeWithSeedOffset(async () => { - return await option.onPreOptionPhase!(this.scene) + globalScene.executeWithSeedOffset(async () => { + return await option.onPreOptionPhase!() .then((result) => { if (isNullOrUndefined(result) || result) { this.continueEncounter(); } }); - }, this.scene.currentBattle.mysteryEncounter?.getSeedOffset()); + }, globalScene.currentBattle.mysteryEncounter?.getSeedOffset()); } else { this.continueEncounter(); } @@ -119,30 +120,30 @@ export class MysteryEncounterPhase extends Phase { */ continueEncounter() { const endDialogueAndContinueEncounter = () => { - this.scene.pushPhase(new MysteryEncounterOptionSelectedPhase(this.scene)); + globalScene.pushPhase(new MysteryEncounterOptionSelectedPhase()); this.end(); }; - const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.selectedOption?.dialogue; + const optionSelectDialogue = globalScene.currentBattle?.mysteryEncounter?.selectedOption?.dialogue; if (optionSelectDialogue?.selected && optionSelectDialogue.selected.length > 0) { // Handle intermediate dialogue (between player selection event and the onOptionSelect logic) - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); const selectedDialogue = optionSelectDialogue.selected; let i = 0; const showNextDialogue = () => { const nextAction = i === selectedDialogue.length - 1 ? endDialogueAndContinueEncounter : showNextDialogue; const dialogue = selectedDialogue[i]; let title: string | null = null; - const text: string | null = getEncounterText(this.scene, dialogue.text); + const text: string | null = getEncounterText(dialogue.text); if (dialogue.speaker) { - title = getEncounterText(this.scene, dialogue.speaker); + title = getEncounterText(dialogue.speaker); } i++; if (title) { - this.scene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0); + globalScene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0); } else { - this.scene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true); + globalScene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true); } }; @@ -156,7 +157,7 @@ export class MysteryEncounterPhase extends Phase { * Ends phase */ end() { - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + globalScene.ui.setMode(Mode.MESSAGE).then(() => super.end()); } } @@ -170,9 +171,9 @@ export class MysteryEncounterPhase extends Phase { export class MysteryEncounterOptionSelectedPhase extends Phase { onOptionSelect: OptionPhaseCallback; - constructor(scene: BattleScene) { - super(scene); - this.onOptionSelect = this.scene.currentBattle.mysteryEncounter!.selectedOption!.onOptionPhase; + constructor() { + super(); + this.onOptionSelect = globalScene.currentBattle.mysteryEncounter!.selectedOption!.onOptionPhase; } /** @@ -184,20 +185,20 @@ export class MysteryEncounterOptionSelectedPhase extends Phase { */ start() { super.start(); - if (this.scene.currentBattle.mysteryEncounter?.autoHideIntroVisuals) { - transitionMysteryEncounterIntroVisuals(this.scene).then(() => { - this.scene.executeWithSeedOffset(() => { - this.onOptionSelect(this.scene).finally(() => { + if (globalScene.currentBattle.mysteryEncounter?.autoHideIntroVisuals) { + transitionMysteryEncounterIntroVisuals().then(() => { + globalScene.executeWithSeedOffset(() => { + this.onOptionSelect().finally(() => { this.end(); }); - }, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 500); + }, globalScene.currentBattle.mysteryEncounter?.getSeedOffset() * 500); }); } else { - this.scene.executeWithSeedOffset(() => { - this.onOptionSelect(this.scene).finally(() => { + globalScene.executeWithSeedOffset(() => { + this.onOptionSelect().finally(() => { this.end(); }); - }, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 500); + }, globalScene.currentBattle.mysteryEncounter?.getSeedOffset() * 500); } } } @@ -209,8 +210,8 @@ export class MysteryEncounterOptionSelectedPhase extends Phase { * See {@linkcode TurnEndPhase} for more details */ export class MysteryEncounterBattleStartCleanupPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } /** @@ -221,7 +222,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { // Lapse any residual flinches/endures but ignore all other turn-end battle tags const includedLapseTags = [ BattlerTagType.FLINCHED, BattlerTagType.ENDURING ]; - const field = this.scene.getField(true).filter(p => p.summonData); + const field = globalScene.getField(true).filter(p => p.summonData); field.forEach(pokemon => { const tags = pokemon.summonData.tags; tags.filter(t => includedLapseTags.includes(t.tagType) @@ -233,31 +234,31 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { }); // Remove any status tick phases - while (!!this.scene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) { - this.scene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase); + while (!!globalScene.findPhase(p => p instanceof PostTurnStatusEffectPhase)) { + globalScene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase); } // The total number of Pokemon in the player's party that can legally fight - const legalPlayerPokemon = this.scene.getPokemonAllowedInBattle(); + const legalPlayerPokemon = globalScene.getPokemonAllowedInBattle(); // The total number of legal player Pokemon that aren't currently on the field const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true)); if (!legalPlayerPokemon.length) { - this.scene.unshiftPhase(new GameOverPhase(this.scene)); + globalScene.unshiftPhase(new GameOverPhase()); return this.end(); } // Check for any KOd player mons and switch // For each fainted mon on the field, if there is a legal replacement, summon it - const playerField = this.scene.getPlayerField(); + const playerField = globalScene.getPlayerField(); playerField.forEach((pokemon, i) => { if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) { - this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, i, true, false)); + globalScene.unshiftPhase(new SwitchPhase(SwitchType.SWITCH, i, true, false)); } }); // THEN, if is a double battle, and player only has 1 summoned pokemon, center pokemon on field - if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + if (globalScene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) { + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); } this.end(); @@ -274,8 +275,8 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase { export class MysteryEncounterBattlePhase extends Phase { disableSwitch: boolean; - constructor(scene: BattleScene, disableSwitch = false) { - super(scene); + constructor(disableSwitch = false) { + super(); this.disableSwitch = disableSwitch; } @@ -285,28 +286,27 @@ export class MysteryEncounterBattlePhase extends Phase { start() { super.start(); - this.doMysteryEncounterBattle(this.scene); + this.doMysteryEncounterBattle(); } /** * Gets intro battle message for new battle - * @param scene * @private */ - private getBattleMessage(scene: BattleScene): string { - const enemyField = scene.getEnemyField(); - const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode; + private getBattleMessage(): string { + const enemyField = globalScene.getEnemyField(); + const encounterMode = globalScene.currentBattle.mysteryEncounter!.encounterMode; - if (scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name }); } if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { - if (scene.currentBattle.double) { - return i18next.t("battle:trainerAppearedDouble", { trainerName: scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + if (globalScene.currentBattle.double) { + return i18next.t("battle:trainerAppearedDouble", { trainerName: globalScene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); } else { - return i18next.t("battle:trainerAppeared", { trainerName: scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); + return i18next.t("battle:trainerAppeared", { trainerName: globalScene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }); } } @@ -317,66 +317,65 @@ export class MysteryEncounterBattlePhase extends Phase { /** * Queues {@linkcode SummonPhase}s for the new battle, and handles trainer animations/dialogue if it's a Trainer battle - * @param scene * @private */ - private doMysteryEncounterBattle(scene: BattleScene) { - const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode; + private doMysteryEncounterBattle() { + const encounterMode = globalScene.currentBattle.mysteryEncounter!.encounterMode; if (encounterMode === MysteryEncounterMode.WILD_BATTLE || encounterMode === MysteryEncounterMode.BOSS_BATTLE) { // Summons the wild/boss Pokemon if (encounterMode === MysteryEncounterMode.BOSS_BATTLE) { - scene.playBgm(undefined); + globalScene.playBgm(); } - const availablePartyMembers = scene.getEnemyParty().filter(p => !p.isFainted()).length; - scene.unshiftPhase(new SummonPhase(scene, 0, false)); - if (scene.currentBattle.double && availablePartyMembers > 1) { - scene.unshiftPhase(new SummonPhase(scene, 1, false)); + const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; + globalScene.unshiftPhase(new SummonPhase(0, false)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.unshiftPhase(new SummonPhase(1, false)); } - if (!scene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { - scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 0); + if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { + globalScene.ui.showText(this.getBattleMessage(), null, () => this.endBattleSetup(), 0); } else { - this.endBattleSetup(scene); + this.endBattleSetup(); } } else if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { this.showEnemyTrainer(); const doSummon = () => { - scene.currentBattle.started = true; - scene.playBgm(undefined); - scene.pbTray.showPbTray(scene.getPlayerParty()); - scene.pbTrayEnemy.showPbTray(scene.getEnemyParty()); + globalScene.currentBattle.started = true; + globalScene.playBgm(); + globalScene.pbTray.showPbTray(globalScene.getPlayerParty()); + globalScene.pbTrayEnemy.showPbTray(globalScene.getEnemyParty()); const doTrainerSummon = () => { this.hideEnemyTrainer(); - const availablePartyMembers = scene.getEnemyParty().filter(p => !p.isFainted()).length; - scene.unshiftPhase(new SummonPhase(scene, 0, false)); - if (scene.currentBattle.double && availablePartyMembers > 1) { - scene.unshiftPhase(new SummonPhase(scene, 1, false)); + const availablePartyMembers = globalScene.getEnemyParty().filter(p => !p.isFainted()).length; + globalScene.unshiftPhase(new SummonPhase(0, false)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.unshiftPhase(new SummonPhase(1, false)); } - this.endBattleSetup(scene); + this.endBattleSetup(); }; - if (!scene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { - scene.ui.showText(this.getBattleMessage(scene), null, doTrainerSummon, 1000, true); + if (!globalScene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) { + globalScene.ui.showText(this.getBattleMessage(), null, doTrainerSummon, 1000, true); } else { doTrainerSummon(); } }; - const encounterMessages = scene.currentBattle.trainer?.getEncounterMessages(); + const encounterMessages = globalScene.currentBattle.trainer?.getEncounterMessages(); if (!encounterMessages || !encounterMessages.length) { doSummon(); } else { - const trainer = this.scene.currentBattle.trainer; + const trainer = globalScene.currentBattle.trainer; let message: string; - scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter?.getSeedOffset()); + globalScene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), globalScene.currentBattle.mysteryEncounter?.getSeedOffset()); message = message!; // tell TS compiler it's defined now const showDialogueAndSummon = () => { - scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { - scene.charSprite.hide().then(() => scene.hideFieldOverlay(250).then(() => doSummon())); + globalScene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => { + globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => doSummon())); }); }; - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? + if (globalScene.currentBattle.trainer?.config.hasCharSprite && !globalScene.ui.shouldSkipDialogue(message)) { + globalScene.showFieldOverlay(500).then(() => globalScene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct? } else { showDialogueAndSummon(); } @@ -386,49 +385,48 @@ export class MysteryEncounterBattlePhase extends Phase { /** * Initiate {@linkcode SummonPhase}s, {@linkcode ScanIvsPhase}, {@linkcode PostSummonPhase}s, etc. - * @param scene * @private */ - private endBattleSetup(scene: BattleScene) { - const enemyField = scene.getEnemyField(); - const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode; + private endBattleSetup() { + const enemyField = globalScene.getEnemyField(); + const encounterMode = globalScene.currentBattle.mysteryEncounter!.encounterMode; // PostSummon and ShinySparkle phases are handled by SummonPhase if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) { - const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier); + const ivScannerModifier = globalScene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { - enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); + enemyField.map(p => globalScene.pushPhase(new ScanIvsPhase(p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); } } - const availablePartyMembers = scene.getPlayerParty().filter(p => p.isAllowedInBattle()); + const availablePartyMembers = globalScene.getPlayerParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { - scene.pushPhase(new SummonPhase(scene, 0)); + globalScene.pushPhase(new SummonPhase(0)); } - if (scene.currentBattle.double) { + if (globalScene.currentBattle.double) { if (availablePartyMembers.length > 1) { - scene.pushPhase(new ToggleDoublePositionPhase(scene, true)); + globalScene.pushPhase(new ToggleDoublePositionPhase(true)); if (!availablePartyMembers[1].isOnField()) { - scene.pushPhase(new SummonPhase(scene, 1)); + globalScene.pushPhase(new SummonPhase(1)); } } } else { if (availablePartyMembers.length > 1 && availablePartyMembers[1].isOnField()) { - scene.getPlayerField().forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); - scene.pushPhase(new ReturnPhase(scene, 1)); + globalScene.getPlayerField().forEach((pokemon) => pokemon.lapseTag(BattlerTagType.COMMANDED)); + globalScene.pushPhase(new ReturnPhase(1)); } - scene.pushPhase(new ToggleDoublePositionPhase(scene, false)); + globalScene.pushPhase(new ToggleDoublePositionPhase(false)); } if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) { - const minPartySize = scene.currentBattle.double ? 2 : 1; + const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { - scene.pushPhase(new CheckSwitchPhase(scene, 0, scene.currentBattle.double)); - if (scene.currentBattle.double) { - scene.pushPhase(new CheckSwitchPhase(scene, 1, scene.currentBattle.double)); + globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + if (globalScene.currentBattle.double) { + globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } @@ -442,7 +440,7 @@ export class MysteryEncounterBattlePhase extends Phase { */ private showEnemyTrainer(): void { // Show enemy trainer - const trainer = this.scene.currentBattle.trainer; + const trainer = globalScene.currentBattle.trainer; if (!trainer) { return; } @@ -450,7 +448,7 @@ export class MysteryEncounterBattlePhase extends Phase { trainer.x += 16; trainer.y -= 16; trainer.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: trainer, x: "-=16", y: "+=16", @@ -465,8 +463,8 @@ export class MysteryEncounterBattlePhase extends Phase { } private hideEnemyTrainer(): void { - this.scene.tweens.add({ - targets: this.scene.currentBattle.trainer, + globalScene.tweens.add({ + targets: globalScene.currentBattle.trainer, x: "+=16", y: "-=16", alpha: 0, @@ -490,8 +488,8 @@ export class MysteryEncounterBattlePhase extends Phase { export class MysteryEncounterRewardsPhase extends Phase { addHealPhase: boolean; - constructor(scene: BattleScene, addHealPhase: boolean = false) { - super(scene); + constructor(addHealPhase: boolean = false) { + super(); this.addHealPhase = addHealPhase; } @@ -500,23 +498,23 @@ export class MysteryEncounterRewardsPhase extends Phase { */ start() { super.start(); - const encounter = this.scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.doContinueEncounter) { - encounter.doContinueEncounter(this.scene).then(() => { + encounter.doContinueEncounter().then(() => { this.end(); }); } else { - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { if (encounter.onRewards) { - encounter.onRewards(this.scene).then(() => { + encounter.onRewards().then(() => { this.doEncounterRewardsAndContinue(); }); } else { this.doEncounterRewardsAndContinue(); } // Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave) - }, this.scene.currentBattle.waveIndex * 1000); + }, globalScene.currentBattle.waveIndex * 1000); } } @@ -524,20 +522,20 @@ export class MysteryEncounterRewardsPhase extends Phase { * Queues encounter EXP and rewards phases, {@linkcode PostMysteryEncounterPhase}, and ends phase */ doEncounterRewardsAndContinue() { - const encounter = this.scene.currentBattle.mysteryEncounter!; + const encounter = globalScene.currentBattle.mysteryEncounter!; if (encounter.doEncounterExp) { - encounter.doEncounterExp(this.scene); + encounter.doEncounterExp(); } if (encounter.doEncounterRewards) { - encounter.doEncounterRewards(this.scene); + encounter.doEncounterRewards(); } else if (this.addHealPhase) { - this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase); - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, undefined, { fillRemaining: false, rerollMultiplier: -1 })); + globalScene.tryRemovePhase(p => p instanceof SelectModifierPhase); + globalScene.unshiftPhase(new SelectModifierPhase(0, undefined, { fillRemaining: false, rerollMultiplier: -1 })); } - this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene)); + globalScene.pushPhase(new PostMysteryEncounterPhase()); this.end(); } } @@ -553,9 +551,9 @@ export class PostMysteryEncounterPhase extends Phase { private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750; onPostOptionSelect?: OptionPhaseCallback; - constructor(scene: BattleScene) { - super(scene); - this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter?.selectedOption?.onPostOptionPhase; + constructor() { + super(); + this.onPostOptionSelect = globalScene.currentBattle.mysteryEncounter?.selectedOption?.onPostOptionPhase; } /** @@ -565,14 +563,14 @@ export class PostMysteryEncounterPhase extends Phase { super.start(); if (this.onPostOptionSelect) { - this.scene.executeWithSeedOffset(async () => { - return await this.onPostOptionSelect!(this.scene) + globalScene.executeWithSeedOffset(async () => { + return await this.onPostOptionSelect!() .then((result) => { if (isNullOrUndefined(result) || result) { this.continueEncounter(); } }); - }, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 2000); + }, globalScene.currentBattle.mysteryEncounter?.getSeedOffset() * 2000); } else { this.continueEncounter(); } @@ -583,28 +581,28 @@ export class PostMysteryEncounterPhase extends Phase { */ continueEncounter() { const endPhase = () => { - this.scene.pushPhase(new NewBattlePhase(this.scene)); + globalScene.pushPhase(new NewBattlePhase()); this.end(); }; - const outroDialogue = this.scene.currentBattle?.mysteryEncounter?.dialogue?.outro; + const outroDialogue = globalScene.currentBattle?.mysteryEncounter?.dialogue?.outro; if (outroDialogue && outroDialogue.length > 0) { let i = 0; const showNextDialogue = () => { const nextAction = i === outroDialogue.length - 1 ? endPhase : showNextDialogue; const dialogue = outroDialogue[i]; let title: string | null = null; - const text: string | null = getEncounterText(this.scene, dialogue.text); + const text: string | null = getEncounterText(dialogue.text); if (dialogue.speaker) { - title = getEncounterText(this.scene, dialogue.speaker); + title = getEncounterText(dialogue.speaker); } i++; - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); if (title) { - this.scene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0); + globalScene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0); } else { - this.scene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true); + globalScene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true); } }; diff --git a/src/phases/new-battle-phase.ts b/src/phases/new-battle-phase.ts index 5a422c9e6c7..8cdbdc5891a 100644 --- a/src/phases/new-battle-phase.ts +++ b/src/phases/new-battle-phase.ts @@ -1,10 +1,11 @@ +import { globalScene } from "#app/global-scene"; import { BattlePhase } from "./battle-phase"; export class NewBattlePhase extends BattlePhase { start() { super.start(); - this.scene.newBattle(); + globalScene.newBattle(); this.end(); } diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index 910306b76ad..be6815333e5 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -1,34 +1,34 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/ability"; import { getRandomWeatherType } from "#app/data/weather"; import { NextEncounterPhase } from "./next-encounter-phase"; export class NewBiomeEncounterPhase extends NextEncounterPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } doEncounter(): void { - this.scene.playBgm(undefined, true); + globalScene.playBgm(undefined, true); - for (const pokemon of this.scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); } } - for (const pokemon of this.scene.getPlayerParty().filter(p => p.isOnField())) { + for (const pokemon of globalScene.getPlayerParty().filter(p => p.isOnField())) { applyAbAttrs(PostBiomeChangeAbAttr, pokemon, null); } - const enemyField = this.scene.getEnemyField(); - const moveTargets: any[] = [ this.scene.arenaEnemy, enemyField ]; - const mysteryEncounter = this.scene.currentBattle?.mysteryEncounter?.introVisuals; + const enemyField = globalScene.getEnemyField(); + const moveTargets: any[] = [ globalScene.arenaEnemy, enemyField ]; + const mysteryEncounter = globalScene.currentBattle?.mysteryEncounter?.introVisuals; if (mysteryEncounter) { moveTargets.push(mysteryEncounter); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: moveTargets.flat(), x: "+=300", duration: 2000, @@ -44,6 +44,6 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { * Set biome weather. */ trySetWeatherIfNewBiome(): void { - this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena), false); + globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena), false); } } diff --git a/src/phases/next-encounter-phase.ts b/src/phases/next-encounter-phase.ts index e086ed4fe3e..229d37f9a69 100644 --- a/src/phases/next-encounter-phase.ts +++ b/src/phases/next-encounter-phase.ts @@ -1,9 +1,9 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { EncounterPhase } from "./encounter-phase"; export class NextEncounterPhase extends EncounterPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { @@ -11,29 +11,29 @@ export class NextEncounterPhase extends EncounterPhase { } doEncounter(): void { - this.scene.playBgm(undefined, true); + globalScene.playBgm(undefined, true); - for (const pokemon of this.scene.getPlayerParty()) { + for (const pokemon of globalScene.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); } } - this.scene.arenaNextEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaNextEnemy.setVisible(true); + globalScene.arenaNextEnemy.setBiome(globalScene.arena.biomeType); + globalScene.arenaNextEnemy.setVisible(true); - const enemyField = this.scene.getEnemyField(); - const moveTargets: any[] = [ this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer ]; - const lastEncounterVisuals = this.scene.lastMysteryEncounter?.introVisuals; + const enemyField = globalScene.getEnemyField(); + const moveTargets: any[] = [ globalScene.arenaEnemy, globalScene.arenaNextEnemy, globalScene.currentBattle.trainer, enemyField, globalScene.lastEnemyTrainer ]; + const lastEncounterVisuals = globalScene.lastMysteryEncounter?.introVisuals; if (lastEncounterVisuals) { moveTargets.push(lastEncounterVisuals); } - const nextEncounterVisuals = this.scene.currentBattle.mysteryEncounter?.introVisuals; + const nextEncounterVisuals = globalScene.currentBattle.mysteryEncounter?.introVisuals; if (nextEncounterVisuals) { const enterFromRight = nextEncounterVisuals.enterFromRight; if (enterFromRight) { nextEncounterVisuals.x += 500; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: nextEncounterVisuals, x: "-=200", duration: 2000 @@ -43,22 +43,22 @@ export class NextEncounterPhase extends EncounterPhase { } } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: moveTargets.flat(), x: "+=300", duration: 2000, onComplete: () => { - this.scene.arenaEnemy.setBiome(this.scene.arena.biomeType); - this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300); - this.scene.arenaNextEnemy.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); + globalScene.arenaEnemy.setBiome(globalScene.arena.biomeType); + globalScene.arenaEnemy.setX(globalScene.arenaNextEnemy.x); + globalScene.arenaEnemy.setAlpha(1); + globalScene.arenaNextEnemy.setX(globalScene.arenaNextEnemy.x - 300); + globalScene.arenaNextEnemy.setVisible(false); + if (globalScene.lastEnemyTrainer) { + globalScene.lastEnemyTrainer.destroy(); } if (lastEncounterVisuals) { - this.scene.field.remove(lastEncounterVisuals, true); - this.scene.lastMysteryEncounter!.introVisuals = undefined; + globalScene.field.remove(lastEncounterVisuals, true); + globalScene.lastMysteryEncounter!.introVisuals = undefined; } if (!this.tryOverrideForBattleSpec()) { diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index 01384b932cb..b01a90bd235 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -1,9 +1,9 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; @@ -13,8 +13,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase { private sourceText?: string | null; private sourcePokemon?: Pokemon | null; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, statusEffect?: StatusEffect, turnsRemaining?: number, sourceText?: string | null, sourcePokemon?: Pokemon | null) { + super(battlerIndex); this.statusEffect = statusEffect; this.turnsRemaining = turnsRemaining; @@ -30,14 +30,14 @@ export class ObtainStatusEffectPhase extends PokemonPhase { pokemon.status!.sleepTurnsRemaining = this.turnsRemaining; } pokemon.updateInfo(true); - new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { - this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); + new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(false, () => { + globalScene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); this.end(); }); return; } } else if (pokemon.status?.effect === this.statusEffect) { - this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon))); } this.end(); } diff --git a/src/phases/party-exp-phase.ts b/src/phases/party-exp-phase.ts index c5a254871ca..8fd9e1cf0f6 100644 --- a/src/phases/party-exp-phase.ts +++ b/src/phases/party-exp-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; /** @@ -10,8 +10,8 @@ export class PartyExpPhase extends Phase { useWaveIndexMultiplier?: boolean; pokemonParticipantIds?: Set; - constructor(scene: BattleScene, expValue: number, useWaveIndexMultiplier?: boolean, pokemonParticipantIds?: Set) { - super(scene); + constructor(expValue: number, useWaveIndexMultiplier?: boolean, pokemonParticipantIds?: Set) { + super(); this.expValue = expValue; this.useWaveIndexMultiplier = useWaveIndexMultiplier; @@ -24,7 +24,7 @@ export class PartyExpPhase extends Phase { start() { super.start(); - this.scene.applyPartyExp(this.expValue, false, this.useWaveIndexMultiplier, this.pokemonParticipantIds); + globalScene.applyPartyExp(this.expValue, false, this.useWaveIndexMultiplier, this.pokemonParticipantIds); this.end(); } diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 4841bf9a5b4..2c1a6c33163 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,12 +1,12 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "#app/utils"; import { BattlePhase } from "./battle-phase"; export class PartyHealPhase extends BattlePhase { private resumeBgm: boolean; - constructor(scene: BattleScene, resumeBgm: boolean) { - super(scene); + constructor(resumeBgm: boolean) { + super(); this.resumeBgm = resumeBgm; } @@ -14,12 +14,12 @@ export class PartyHealPhase extends BattlePhase { start() { super.start(); - const bgmPlaying = this.scene.isBgmPlaying(); + const bgmPlaying = globalScene.isBgmPlaying(); if (bgmPlaying) { - this.scene.fadeOutBgm(1000, false); + globalScene.fadeOutBgm(1000, false); } - this.scene.ui.fadeOut(1000).then(() => { - for (const pokemon of this.scene.getPlayerParty()) { + globalScene.ui.fadeOut(1000).then(() => { + for (const pokemon of globalScene.getPlayerParty()) { pokemon.hp = pokemon.getMaxHp(); pokemon.resetStatus(); for (const move of pokemon.moveset) { @@ -27,13 +27,13 @@ export class PartyHealPhase extends BattlePhase { } pokemon.updateInfo(true); } - const healSong = this.scene.playSoundWithoutBgm("heal"); - this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { + const healSong = globalScene.playSoundWithoutBgm("heal"); + globalScene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { healSong.destroy(); if (this.resumeBgm && bgmPlaying) { - this.scene.playBgm(); + globalScene.playBgm(); } - this.scene.ui.fadeIn(500).then(() => this.end()); + globalScene.ui.fadeIn(500).then(() => this.end()); }); }); } diff --git a/src/phases/party-member-pokemon-phase.ts b/src/phases/party-member-pokemon-phase.ts index f2e2b23bfb2..bc702c832b1 100644 --- a/src/phases/party-member-pokemon-phase.ts +++ b/src/phases/party-member-pokemon-phase.ts @@ -1,5 +1,5 @@ -import BattleScene from "#app/battle-scene"; -import Pokemon from "#app/field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; export abstract class PartyMemberPokemonPhase extends FieldPhase { @@ -7,18 +7,18 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase { protected fieldIndex: integer; protected player: boolean; - constructor(scene: BattleScene, partyMemberIndex: integer, player: boolean) { - super(scene); + constructor(partyMemberIndex: integer, player: boolean) { + super(); this.partyMemberIndex = partyMemberIndex; - this.fieldIndex = partyMemberIndex < this.scene.currentBattle.getBattlerCount() + this.fieldIndex = partyMemberIndex < globalScene.currentBattle.getBattlerCount() ? partyMemberIndex : -1; this.player = player; } getParty(): Pokemon[] { - return this.player ? this.scene.getPlayerParty() : this.scene.getEnemyParty(); + return this.player ? globalScene.getPlayerParty() : globalScene.getEnemyParty(); } getPokemon(): Pokemon { diff --git a/src/phases/player-party-member-pokemon-phase.ts b/src/phases/player-party-member-pokemon-phase.ts index 87855b9334c..bdedc0743e9 100644 --- a/src/phases/player-party-member-pokemon-phase.ts +++ b/src/phases/player-party-member-pokemon-phase.ts @@ -1,10 +1,9 @@ -import BattleScene from "#app/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { - constructor(scene: BattleScene, partyMemberIndex: integer) { - super(scene, partyMemberIndex, true); + constructor(partyMemberIndex: integer) { + super(partyMemberIndex, true); } getPlayerPokemon(): PlayerPokemon { diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index eb5431cbc56..54140ec96b5 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { SubstituteTag } from "#app/data/battler-tags"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { BattlePhase } from "#app/phases/battle-phase"; import { isNullOrUndefined } from "#app/utils"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; @@ -15,8 +15,8 @@ export class PokemonAnimPhase extends BattlePhase { /** Any other field sprites affected by this animation */ private fieldAssets: Phaser.GameObjects.Sprite[]; - constructor(scene: BattleScene, key: PokemonAnimType, pokemon: Pokemon, fieldAssets?: Phaser.GameObjects.Sprite[]) { - super(scene); + constructor(key: PokemonAnimType, pokemon: Pokemon, fieldAssets?: Phaser.GameObjects.Sprite[]) { + super(); this.key = key; this.pokemon = pokemon; @@ -57,13 +57,13 @@ export class PokemonAnimPhase extends BattlePhase { } const getSprite = () => { - const sprite = this.scene.addFieldSprite( + const sprite = globalScene.addFieldSprite( this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, `pkmn${this.pokemon.isPlayer() ? "__back" : ""}__sub` ); sprite.setOrigin(0.5, 1); - this.scene.field.add(sprite); + globalScene.field.add(sprite); return sprite; }; @@ -76,12 +76,12 @@ export class PokemonAnimPhase extends BattlePhase { subTintSprite.setScale(0.01); if (this.pokemon.isPlayer()) { - this.scene.field.bringToTop(this.pokemon); + globalScene.field.bringToTop(this.pokemon); } - this.scene.playSound("PRSFX- Transform"); + globalScene.playSound("PRSFX- Transform"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemon, duration: 500, x: this.pokemon.x + this.pokemon.getSubstituteOffset()[0], @@ -90,7 +90,7 @@ export class PokemonAnimPhase extends BattlePhase { ease: "Sine.easeIn" }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: subTintSprite, delay: 250, scale: subScale, @@ -98,7 +98,7 @@ export class PokemonAnimPhase extends BattlePhase { duration: 500, onComplete: () => { subSprite.setVisible(true); - this.pokemon.scene.tweens.add({ + globalScene.tweens.add({ targets: subTintSprite, delay: 250, alpha: 0, @@ -124,14 +124,14 @@ export class PokemonAnimPhase extends BattlePhase { return this.end(); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: subSprite, alpha: 0, ease: "Sine.easeInOut", duration: 500 }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemon, x: subSprite.x, y: subSprite.y, @@ -153,7 +153,7 @@ export class PokemonAnimPhase extends BattlePhase { return this.end(); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemon, x: subSprite.x + this.pokemon.getSubstituteOffset()[0], y: subSprite.y + this.pokemon.getSubstituteOffset()[1], @@ -162,7 +162,7 @@ export class PokemonAnimPhase extends BattlePhase { duration: 500 }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: subSprite, alpha: 1, ease: "Sine.easeInOut", @@ -183,13 +183,13 @@ export class PokemonAnimPhase extends BattlePhase { } const getSprite = () => { - const sprite = this.scene.addFieldSprite( + const sprite = globalScene.addFieldSprite( subSprite.x, subSprite.y, `pkmn${this.pokemon.isPlayer() ? "__back" : ""}__sub` ); sprite.setOrigin(0.5, 1); - this.scene.field.add(sprite); + globalScene.field.add(sprite); return sprite; }; @@ -199,30 +199,30 @@ export class PokemonAnimPhase extends BattlePhase { subTintSprite.setTintFill(0xFFFFFF); subTintSprite.setScale(subScale); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: subTintSprite, alpha: 1, ease: "Sine.easeInOut", duration: 500, onComplete: () => { subSprite.destroy(); - const flashTimer = this.scene.time.addEvent({ + const flashTimer = globalScene.time.addEvent({ delay: 100, repeat: 7, startAt: 200, callback: () => { - this.scene.playSound("PRSFX- Substitute2.wav"); + globalScene.playSound("PRSFX- Substitute2.wav"); subTintSprite.setVisible(flashTimer.repeatCount % 2 === 0); if (!flashTimer.repeatCount) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: subTintSprite, scale: 0.01, ease: "Sine.cubicEaseIn", duration: 500 }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemon, x: this.pokemon.x - this.pokemon.getSubstituteOffset()[0], y: this.pokemon.y - this.pokemon.getSubstituteOffset()[1], @@ -243,7 +243,7 @@ export class PokemonAnimPhase extends BattlePhase { } private doCommanderApplyAnim(): void { - if (!this.scene.currentBattle?.double) { + if (!globalScene.currentBattle?.double) { return this.end(); } const dondozo = this.pokemon.getAlly(); @@ -256,7 +256,7 @@ export class PokemonAnimPhase extends BattlePhase { const tatsugiriY = this.pokemon.y + this.pokemon.getSprite().y; const getSourceSprite = () => { - const sprite = this.scene.addPokemonSprite(this.pokemon, tatsugiriX, tatsugiriY, this.pokemon.getSprite().texture, this.pokemon.getSprite()!.frame.name, true); + const sprite = globalScene.addPokemonSprite(this.pokemon, tatsugiriX, tatsugiriY, this.pokemon.getSprite().texture, this.pokemon.getSprite()!.frame.name, true); [ "spriteColors", "fusionSpriteColors" ].map(k => sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]); sprite.setPipelineData("spriteKey", this.pokemon.getBattleSpriteKey()); sprite.setPipelineData("shiny", this.pokemon.shiny); @@ -264,7 +264,7 @@ export class PokemonAnimPhase extends BattlePhase { sprite.setPipelineData("ignoreFieldPos", true); sprite.setOrigin(0.5, 1); this.pokemon.getSprite().on("animationupdate", (_anim, frame) => sprite.setFrame(frame.textureFrame)); - this.scene.field.add(sprite); + globalScene.field.add(sprite); return sprite; }; @@ -275,17 +275,17 @@ export class PokemonAnimPhase extends BattlePhase { const sourceFpOffset = this.pokemon.getFieldPositionOffset(); const dondozoFpOffset = dondozo.getFieldPositionOffset(); - this.scene.playSound("se/pb_throw"); + globalScene.playSound("se/pb_throw"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: sourceSprite, duration: 375, scale: 0.5, x: { value: tatsugiriX + (dondozoFpOffset[0] - sourceFpOffset[0]) / 2, ease: "Linear" }, y: { value: (this.pokemon.isPlayer() ? 100 : 65) + sourceFpOffset[1], ease: "Sine.easeOut" }, onComplete: () => { - this.scene.field.bringToTop(dondozo); - this.scene.tweens.add({ + globalScene.field.bringToTop(dondozo); + globalScene.tweens.add({ targets: sourceSprite, duration: 375, scale: 0.01, @@ -293,8 +293,8 @@ export class PokemonAnimPhase extends BattlePhase { y: { value: dondozo.y + dondozo.height / 2, ease: "Sine.easeIn" }, onComplete: () => { sourceSprite.destroy(); - this.scene.playSound("battle_anims/PRSFX- Liquidation1.wav"); - this.scene.tweens.add({ + globalScene.playSound("battle_anims/PRSFX- Liquidation1.wav"); + globalScene.tweens.add({ targets: dondozo, duration: 250, ease: "Sine.easeInOut", @@ -317,7 +317,7 @@ export class PokemonAnimPhase extends BattlePhase { return this.end(); } - const tatsuSprite = this.scene.addPokemonSprite( + const tatsuSprite = globalScene.addPokemonSprite( tatsugiri, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y + this.pokemon.height / 2, @@ -335,19 +335,19 @@ export class PokemonAnimPhase extends BattlePhase { tatsuSprite.setOrigin(0.5, 1); tatsuSprite.setScale(0.01); - this.scene.field.add(tatsuSprite); - this.scene.field.bringToTop(this.pokemon); + globalScene.field.add(tatsuSprite); + globalScene.field.bringToTop(this.pokemon); tatsuSprite.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemon, duration: 250, ease: "Sine.easeInOut", scale: 1.15, yoyo: true, onComplete: () => { - this.scene.playSound("battle_anims/PRSFX- Liquidation4.wav"); - this.scene.tweens.add({ + globalScene.playSound("battle_anims/PRSFX- Liquidation4.wav"); + globalScene.tweens.add({ targets: tatsuSprite, duration: 500, scale: 1, diff --git a/src/phases/pokemon-heal-phase.ts b/src/phases/pokemon-heal-phase.ts index c95b92e3b64..268794ce97c 100644 --- a/src/phases/pokemon-heal-phase.ts +++ b/src/phases/pokemon-heal-phase.ts @@ -1,9 +1,10 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { StatusEffect } from "#app/enums/status-effect"; -import { HitResult, DamageResult } from "#app/field/pokemon"; +import type { DamageResult } from "#app/field/pokemon"; +import { HitResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealAchv } from "#app/system/achv"; @@ -11,7 +12,7 @@ import i18next from "i18next"; import * as Utils from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { HealBlockTag } from "#app/data/battler-tags"; +import type { HealBlockTag } from "#app/data/battler-tags"; export class PokemonHealPhase extends CommonAnimPhase { private hpHealed: integer; @@ -23,8 +24,8 @@ export class PokemonHealPhase extends CommonAnimPhase { private preventFullHeal: boolean; private fullRestorePP: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false, fullRestorePP: boolean = false) { - super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP); + constructor(battlerIndex: BattlerIndex, hpHealed: integer, message: string | null, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false, preventFullHeal: boolean = false, fullRestorePP: boolean = false) { + super(battlerIndex, undefined, CommonAnim.HEALTH_UP); this.hpHealed = hpHealed; this.message = message; @@ -57,13 +58,13 @@ export class PokemonHealPhase extends CommonAnimPhase { let lastStatusEffect = StatusEffect.NONE; if (healBlock && this.hpHealed > 0) { - this.scene.queueMessage(healBlock.onActivation(pokemon)); + globalScene.queueMessage(healBlock.onActivation(pokemon)); this.message = null; return super.end(); } else if (healOrDamage) { const hpRestoreMultiplier = new Utils.IntegerHolder(1); if (!this.revive) { - this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); + globalScene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier); } const healAmount = new Utils.NumberHolder(Math.floor(this.hpHealed * hpRestoreMultiplier.value)); if (healAmount.value < 0) { @@ -76,12 +77,12 @@ export class PokemonHealPhase extends CommonAnimPhase { } healAmount.value = pokemon.heal(healAmount.value); if (healAmount.value) { - this.scene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); + globalScene.damageNumberHandler.add(pokemon, healAmount.value, HitResult.HEAL); } if (pokemon.isPlayer()) { - this.scene.validateAchvs(HealAchv, healAmount); - if (healAmount.value > this.scene.gameData.gameStats.highestHeal) { - this.scene.gameData.gameStats.highestHeal = healAmount.value; + globalScene.validateAchvs(HealAchv, healAmount); + if (healAmount.value > globalScene.gameData.gameStats.highestHeal) { + globalScene.gameData.gameStats.highestHeal = healAmount.value; } } if (this.healStatus && !this.revive && pokemon.status) { @@ -105,11 +106,11 @@ export class PokemonHealPhase extends CommonAnimPhase { } if (this.message) { - this.scene.queueMessage(this.message); + globalScene.queueMessage(this.message); } if (this.healStatus && lastStatusEffect && !hasMessage) { - this.scene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectHealText(lastStatusEffect, getPokemonNameWithAffix(pokemon))); } if (!healOrDamage && !lastStatusEffect) { diff --git a/src/phases/pokemon-phase.ts b/src/phases/pokemon-phase.ts index b980c1d1719..29153ee726a 100644 --- a/src/phases/pokemon-phase.ts +++ b/src/phases/pokemon-phase.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { BattlerIndex } from "#app/battle"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { FieldPhase } from "./field-phase"; export abstract class PokemonPhase extends FieldPhase { @@ -8,11 +8,11 @@ export abstract class PokemonPhase extends FieldPhase { public player: boolean; public fieldIndex: integer; - constructor(scene: BattleScene, battlerIndex?: BattlerIndex | integer) { - super(scene); + constructor(battlerIndex?: BattlerIndex | integer) { + super(); if (battlerIndex === undefined) { - battlerIndex = scene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? + battlerIndex = globalScene.getField().find(p => p?.isActive())!.getBattlerIndex(); // TODO: is the bang correct here? } this.battlerIndex = battlerIndex; @@ -22,8 +22,8 @@ export abstract class PokemonPhase extends FieldPhase { getPokemon(): Pokemon { if (this.battlerIndex > BattlerIndex.ENEMY_2) { - return this.scene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? + return globalScene.getPokemonById(this.battlerIndex)!; //TODO: is this bang correct? } - return this.scene.getField()[this.battlerIndex]!; //TODO: is this bang correct? + return globalScene.getField()[this.battlerIndex]!; //TODO: is this bang correct? } } diff --git a/src/phases/post-game-over-phase.ts b/src/phases/post-game-over-phase.ts index beeb30c7260..4643d4d1bef 100644 --- a/src/phases/post-game-over-phase.ts +++ b/src/phases/post-game-over-phase.ts @@ -1,13 +1,13 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; -import { EndCardPhase } from "./end-card-phase"; +import type { EndCardPhase } from "./end-card-phase"; import { TitlePhase } from "./title-phase"; export class PostGameOverPhase extends Phase { private endCardPhase: EndCardPhase | null; - constructor(scene: BattleScene, endCardPhase?: EndCardPhase) { - super(scene); + constructor(endCardPhase?: EndCardPhase) { + super(); this.endCardPhase = endCardPhase!; // TODO: is this bang correct? } @@ -16,24 +16,24 @@ export class PostGameOverPhase extends Phase { super.start(); const saveAndReset = () => { - this.scene.gameData.saveAll(this.scene, true, true, true).then(success => { + globalScene.gameData.saveAll(true, true, true).then(success => { if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } - this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { + globalScene.gameData.tryClearSession(globalScene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { if (!success[0]) { - return this.scene.reset(true); + return globalScene.reset(true); } - this.scene.reset(); - this.scene.unshiftPhase(new TitlePhase(this.scene)); + globalScene.reset(); + globalScene.unshiftPhase(new TitlePhase()); this.end(); }); }); }; if (this.endCardPhase) { - this.scene.ui.fadeOut(500).then(() => { - this.scene.ui.getMessageHandler().bg.setVisible(true); + globalScene.ui.fadeOut(500).then(() => { + globalScene.ui.getMessageHandler().bg.setVisible(true); this.endCardPhase?.endCard.destroy(); this.endCardPhase?.text.destroy(); diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index 42e5b930eb1..d76708b7050 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -1,5 +1,5 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability"; import { ArenaTrapTag } from "#app/data/arena-tag"; import { StatusEffect } from "#app/enums/status-effect"; @@ -8,8 +8,8 @@ import { MysteryEncounterPostSummonTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#enums/battler-tag-type"; export class PostSummonPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex) { + super(battlerIndex); } start() { @@ -20,16 +20,16 @@ export class PostSummonPhase extends PokemonPhase { if (pokemon.status?.effect === StatusEffect.TOXIC) { pokemon.status.toxicTurnCount = 0; } - this.scene.arena.applyTags(ArenaTrapTag, false, pokemon); + globalScene.arena.applyTags(ArenaTrapTag, false, pokemon); // If this is mystery encounter and has post summon phase tag, apply post summon effects - if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) { + if (globalScene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) { pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); } applyPostSummonAbAttrs(PostSummonAbAttr, pokemon) .then(() => { - const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); + const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false)); this.end(); diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 378a932cdc5..13cac6eed7c 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -1,5 +1,5 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { applyAbAttrs, applyPostDamageAbAttrs, BlockNonDirectDamageAbAttr, BlockStatusDamageAbAttr, PostDamageAbAttr, ReduceBurnDamageAbAttr } from "#app/data/ability"; import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { getStatusEffectActivationText } from "#app/data/status-effect"; @@ -10,8 +10,8 @@ import * as Utils from "#app/utils"; import { PokemonPhase } from "./pokemon-phase"; export class PostTurnStatusEffectPhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex) { + super(battlerIndex); } start() { @@ -23,7 +23,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { applyAbAttrs(BlockStatusDamageAbAttr, pokemon, cancelled); if (!cancelled.value) { - this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + globalScene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); const damage = new Utils.NumberHolder(0); switch (pokemon.status.effect) { case StatusEffect.POISON: @@ -39,11 +39,11 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { } if (damage.value) { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... - this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); + globalScene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage.value, false, true)); pokemon.updateInfo(); applyPostDamageAbAttrs(PostDamageAbAttr, pokemon, damage.value, pokemon.hasPassive(), false, []); } - new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, false, () => this.end()); + new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(false, () => this.end()); } else { this.end(); } @@ -53,8 +53,8 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { } override end() { - if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { - this.scene.initFinalBossPhaseTwo(this.getPokemon()); + if (globalScene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { + globalScene.initFinalBossPhaseTwo(this.getPokemon()); } else { super.end(); } diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index c9e5bec845d..185156a20c7 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -1,10 +1,12 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { SemiInvulnerableTag } from "#app/data/battler-tags"; -import { SpeciesFormChange, getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; import { getTypeRgb } from "#app/data/type"; import { BattleSpec } from "#app/enums/battle-spec"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import Pokemon, { EnemyPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { BattlePhase } from "./battle-phase"; import { MovePhase } from "./move-phase"; @@ -14,8 +16,8 @@ export class QuietFormChangePhase extends BattlePhase { protected pokemon: Pokemon; protected formChange: SpeciesFormChange; - constructor(scene: BattleScene, pokemon: Pokemon, formChange: SpeciesFormChange) { - super(scene); + constructor(pokemon: Pokemon, formChange: SpeciesFormChange) { + super(); this.pokemon = pokemon; this.formChange = formChange; } @@ -32,7 +34,7 @@ export class QuietFormChangePhase extends BattlePhase { if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag) || this.pokemon.isFainted()) { if (this.pokemon.isPlayer() || this.pokemon.isActive()) { this.pokemon.changeForm(this.formChange).then(() => { - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + globalScene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); }); } else { this.end(); @@ -41,7 +43,7 @@ export class QuietFormChangePhase extends BattlePhase { } const getPokemonSprite = () => { - const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); + const sprite = globalScene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub"); sprite.setOrigin(0.5, 1); const spriteKey = this.pokemon.getBattleSpriteKey(); try { @@ -49,14 +51,14 @@ export class QuietFormChangePhase extends BattlePhase { } catch (err: unknown) { console.error(`Failed to play animation for ${spriteKey}`, err); } - sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); + sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); [ "spriteColors", "fusionSpriteColors" ].map(k => { if (this.pokemon.summonData?.speciesForm) { k += "Base"; } sprite.pipelineData[k] = this.pokemon.getSprite().pipelineData[k]; }); - this.scene.field.add(sprite); + globalScene.field.add(sprite); return sprite; }; @@ -75,9 +77,9 @@ export class QuietFormChangePhase extends BattlePhase { pokemonFormTintSprite.setVisible(false); pokemonFormTintSprite.setTintFill(0xFFFFFF); - this.scene.playSound("battle_anims/PRSFX- Transform"); + globalScene.playSound("battle_anims/PRSFX- Transform"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonTintSprite, alpha: 1, duration: 1000, @@ -93,7 +95,7 @@ export class QuietFormChangePhase extends BattlePhase { console.error(`Failed to play animation for ${spriteKey}`, err); } pokemonFormTintSprite.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonTintSprite, delay: 250, scale: 0.01, @@ -101,7 +103,7 @@ export class QuietFormChangePhase extends BattlePhase { duration: 500, onComplete: () => pokemonTintSprite.destroy() }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonFormTintSprite, delay: 250, scale: this.pokemon.getSpriteScale(), @@ -109,7 +111,7 @@ export class QuietFormChangePhase extends BattlePhase { duration: 500, onComplete: () => { this.pokemon.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemonFormTintSprite, delay: 250, alpha: 0, @@ -117,7 +119,7 @@ export class QuietFormChangePhase extends BattlePhase { duration: 1000, onComplete: () => { pokemonTintSprite.setVisible(false); - this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); + globalScene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); } }); } @@ -129,16 +131,16 @@ export class QuietFormChangePhase extends BattlePhase { end(): void { this.pokemon.findAndRemoveTags(t => t.tagType === BattlerTagType.AUTOTOMIZED); - if (this.pokemon.scene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { - this.scene.playBgm(); - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); + if (globalScene?.currentBattle.battleSpec === BattleSpec.FINAL_BOSS && this.pokemon instanceof EnemyPokemon) { + globalScene.playBgm(); + globalScene.unshiftPhase(new PokemonHealPhase(this.pokemon.getBattlerIndex(), this.pokemon.getMaxHp(), null, false, false, false, true)); this.pokemon.findAndRemoveTags(() => true); this.pokemon.bossSegments = 5; this.pokemon.bossSegmentIndex = 4; this.pokemon.initBattleInfo(); this.pokemon.cry(); - const movePhase = this.scene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; + const movePhase = globalScene.findPhase(p => p instanceof MovePhase && p.pokemon === this.pokemon) as MovePhase; if (movePhase) { movePhase.cancel(); } diff --git a/src/phases/reload-session-phase.ts b/src/phases/reload-session-phase.ts index f8a38105869..a88cb1b1de3 100644 --- a/src/phases/reload-session-phase.ts +++ b/src/phases/reload-session-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; import * as Utils from "#app/utils"; @@ -6,19 +6,19 @@ import * as Utils from "#app/utils"; export class ReloadSessionPhase extends Phase { private systemDataStr: string | null; - constructor(scene: BattleScene, systemDataStr?: string) { - super(scene); + constructor(systemDataStr?: string) { + super(); this.systemDataStr = systemDataStr ?? null; } start(): void { - this.scene.ui.setMode(Mode.SESSION_RELOAD); + globalScene.ui.setMode(Mode.SESSION_RELOAD); let delayElapsed = false; let loaded = false; - this.scene.time.delayedCall(Utils.fixedInt(1500), () => { + globalScene.time.delayedCall(Utils.fixedInt(1500), () => { if (loaded) { this.end(); } else { @@ -26,9 +26,9 @@ export class ReloadSessionPhase extends Phase { } }); - this.scene.gameData.clearLocalData(); + globalScene.gameData.clearLocalData(); - (this.systemDataStr ? this.scene.gameData.initSystem(this.systemDataStr) : this.scene.gameData.loadSystem()).then(() => { + (this.systemDataStr ? globalScene.gameData.initSystem(this.systemDataStr) : globalScene.gameData.loadSystem()).then(() => { if (delayElapsed) { this.end(); } else { diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index eb587201585..9dd13f6d5be 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -1,11 +1,11 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; export class ReturnPhase extends SwitchSummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, SwitchType.SWITCH, fieldIndex, -1, true); + constructor(fieldIndex: integer) { + super(SwitchType.SWITCH, fieldIndex, -1, true); } switchAndSummon(): void { @@ -21,8 +21,8 @@ export class ReturnPhase extends SwitchSummonPhase { pokemon.resetTurnData(); pokemon.resetSummonData(); - this.scene.updateFieldScale(); + globalScene.updateFieldScale(); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); } } diff --git a/src/phases/ribbon-modifier-reward-phase.ts b/src/phases/ribbon-modifier-reward-phase.ts index fabb3bfa1b1..8cf15ba8f2c 100644 --- a/src/phases/ribbon-modifier-reward-phase.ts +++ b/src/phases/ribbon-modifier-reward-phase.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; -import PokemonSpecies from "#app/data/pokemon-species"; -import { ModifierTypeFunc } from "#app/modifier/modifier-type"; +import { globalScene } from "#app/global-scene"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type { ModifierTypeFunc } from "#app/modifier/modifier-type"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import { ModifierRewardPhase } from "./modifier-reward-phase"; @@ -8,8 +8,8 @@ import { ModifierRewardPhase } from "./modifier-reward-phase"; export class RibbonModifierRewardPhase extends ModifierRewardPhase { private species: PokemonSpecies; - constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { - super(scene, modifierTypeFunc); + constructor(modifierTypeFunc: ModifierTypeFunc, species: PokemonSpecies) { + super(modifierTypeFunc); this.species = species; } @@ -17,12 +17,12 @@ export class RibbonModifierRewardPhase extends ModifierRewardPhase { doReward(): Promise { return new Promise(resolve => { const newModifier = this.modifierType.newModifier(); - this.scene.addModifier(newModifier).then(() => { - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:beatModeFirstTime", { + globalScene.addModifier(newModifier).then(() => { + globalScene.playSound("level_up_fanfare"); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:beatModeFirstTime", { speciesName: this.species.name, - gameMode: this.scene.gameMode.getName(), + gameMode: globalScene.gameMode.getName(), newModifier: newModifier?.type.name }), null, () => { resolve(); diff --git a/src/phases/scan-ivs-phase.ts b/src/phases/scan-ivs-phase.ts index 5ec61d5eec6..b3385a5de94 100644 --- a/src/phases/scan-ivs-phase.ts +++ b/src/phases/scan-ivs-phase.ts @@ -1,5 +1,5 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { Stat } from "#app/enums/stat"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -11,8 +11,8 @@ import { PokemonPhase } from "./pokemon-phase"; export class ScanIvsPhase extends PokemonPhase { private shownIvs: integer; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, shownIvs: integer) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, shownIvs: integer) { + super(battlerIndex); this.shownIvs = shownIvs; } @@ -29,12 +29,12 @@ export class ScanIvsPhase extends PokemonPhase { let enemyIvs: number[] = []; let statsContainer: Phaser.GameObjects.Sprite[] = []; let statsContainerLabels: Phaser.GameObjects.Sprite[] = []; - const enemyField = this.scene.getEnemyField(); - const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible + const enemyField = globalScene.getEnemyField(); + const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible for (let e = 0; e < enemyField.length; e++) { enemyIvs = enemyField[e].ivs; - const currentIvs = this.scene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists - const ivsToShow = this.scene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); + const currentIvs = globalScene.gameData.dexData[enemyField[e].species.getRootSpeciesId()].ivs; // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + const ivsToShow = globalScene.ui.getMessageHandler().getTopIvs(enemyIvs, this.shownIvs); statsContainer = enemyField[e].getBattleInfo().getStatsValueContainer().list as Phaser.GameObjects.Sprite[]; statsContainerLabels = statsContainer.filter(m => m.name.indexOf("icon_stat_label") >= 0); for (let s = 0; s < statsContainerLabels.length; s++) { @@ -48,17 +48,17 @@ export class ScanIvsPhase extends PokemonPhase { } } - if (!this.scene.hideIvs) { - this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { - this.scene.ui.setMode(Mode.CONFIRM, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); - new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, false, () => { - this.scene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); + if (!globalScene.hideIvs) { + globalScene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { + globalScene.ui.setMode(Mode.CONFIRM, () => { + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); + new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(false, () => { + globalScene.ui.getMessageHandler().promptIvs(pokemon.id, pokemon.ivs, this.shownIvs).then(() => this.end()); }); }, () => { - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); this.end(); }); }); diff --git a/src/phases/select-biome-phase.ts b/src/phases/select-biome-phase.ts index 817cd7bcd3d..41077dfffc1 100644 --- a/src/phases/select-biome-phase.ts +++ b/src/phases/select-biome-phase.ts @@ -1,8 +1,8 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { biomeLinks, getBiomeName } from "#app/data/balance/biomes"; import { Biome } from "#app/enums/biome"; import { MoneyInterestModifier, MapModifier } from "#app/modifier/modifier"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { BattlePhase } from "./battle-phase"; import * as Utils from "#app/utils"; @@ -10,58 +10,58 @@ import { PartyHealPhase } from "./party-heal-phase"; import { SwitchBiomePhase } from "./switch-biome-phase"; export class SelectBiomePhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - const currentBiome = this.scene.arena.biomeType; + const currentBiome = globalScene.arena.biomeType; const setNextBiome = (nextBiome: Biome) => { - if (this.scene.currentBattle.waveIndex % 10 === 1) { - this.scene.applyModifiers(MoneyInterestModifier, true, this.scene); - this.scene.unshiftPhase(new PartyHealPhase(this.scene, false)); + if (globalScene.currentBattle.waveIndex % 10 === 1) { + globalScene.applyModifiers(MoneyInterestModifier, true); + globalScene.unshiftPhase(new PartyHealPhase(false)); } - this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome)); + globalScene.unshiftPhase(new SwitchBiomePhase(nextBiome)); this.end(); }; - if ((this.scene.gameMode.isClassic && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex + 9)) - || (this.scene.gameMode.isDaily && this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) - || (this.scene.gameMode.hasShortBiomes && !(this.scene.currentBattle.waveIndex % 50))) { + if ((globalScene.gameMode.isClassic && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex + 9)) + || (globalScene.gameMode.isDaily && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) + || (globalScene.gameMode.hasShortBiomes && !(globalScene.currentBattle.waveIndex % 50))) { setNextBiome(Biome.END); - } else if (this.scene.gameMode.hasRandomBiomes) { + } else if (globalScene.gameMode.hasRandomBiomes) { setNextBiome(this.generateNextBiome()); } else if (Array.isArray(biomeLinks[currentBiome])) { let biomes: Biome[] = []; - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { biomes = (biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) .filter(b => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .map(b => !Array.isArray(b) ? b : b[0]); - }, this.scene.currentBattle.waveIndex); - if (biomes.length > 1 && this.scene.findModifier(m => m instanceof MapModifier)) { + }, globalScene.currentBattle.waveIndex); + if (biomes.length > 1 && globalScene.findModifier(m => m instanceof MapModifier)) { let biomeChoices: Biome[] = []; - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) ? [ biomeLinks[currentBiome] as Biome ] : biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .map(b => Array.isArray(b) ? b[0] : b); - }, this.scene.currentBattle.waveIndex); + }, globalScene.currentBattle.waveIndex); const biomeSelectItems = biomeChoices.map(b => { const ret: OptionSelectItem = { label: getBiomeName(b), handler: () => { - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); setNextBiome(b); return true; } }; return ret; }); - this.scene.ui.setMode(Mode.OPTION_SELECT, { + globalScene.ui.setMode(Mode.OPTION_SELECT, { options: biomeSelectItems, delay: 1000 }); @@ -76,9 +76,9 @@ export class SelectBiomePhase extends BattlePhase { } generateNextBiome(): Biome { - if (!(this.scene.currentBattle.waveIndex % 50)) { + if (!(globalScene.currentBattle.waveIndex % 50)) { return Biome.END; } - return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); + return globalScene.generateRandomBiome(globalScene.currentBattle.waveIndex); } } diff --git a/src/phases/select-challenge-phase.ts b/src/phases/select-challenge-phase.ts index 9450c60fec5..2a6797d3556 100644 --- a/src/phases/select-challenge-phase.ts +++ b/src/phases/select-challenge-phase.ts @@ -1,17 +1,17 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; export class SelectChallengePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.playBgm("menu"); + globalScene.playBgm("menu"); - this.scene.ui.setMode(Mode.CHALLENGE_SELECT); + globalScene.ui.setMode(Mode.CHALLENGE_SELECT); } } diff --git a/src/phases/select-gender-phase.ts b/src/phases/select-gender-phase.ts index 7f2c965f1d1..45cbb70dcac 100644 --- a/src/phases/select-gender-phase.ts +++ b/src/phases/select-gender-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { PlayerGender } from "#app/enums/player-gender"; import { Phase } from "#app/phase"; import { SettingKeys } from "#app/system/settings/settings"; @@ -6,31 +6,31 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; export class SelectGenderPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start(): void { super.start(); - this.scene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { - this.scene.ui.setMode(Mode.OPTION_SELECT, { + globalScene.ui.showText(i18next.t("menu:boyOrGirl"), null, () => { + globalScene.ui.setMode(Mode.OPTION_SELECT, { options: [ { label: i18next.t("settings:boy"), handler: () => { - this.scene.gameData.gender = PlayerGender.MALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 0); - this.scene.gameData.saveSystem().then(() => this.end()); + globalScene.gameData.gender = PlayerGender.MALE; + globalScene.gameData.saveSetting(SettingKeys.Player_Gender, 0); + globalScene.gameData.saveSystem().then(() => this.end()); return true; } }, { label: i18next.t("settings:girl"), handler: () => { - this.scene.gameData.gender = PlayerGender.FEMALE; - this.scene.gameData.saveSetting(SettingKeys.Player_Gender, 1); - this.scene.gameData.saveSystem().then(() => this.end()); + globalScene.gameData.gender = PlayerGender.FEMALE; + globalScene.gameData.saveSetting(SettingKeys.Player_Gender, 1); + globalScene.gameData.saveSystem().then(() => this.end()); return true; } } @@ -40,7 +40,7 @@ export class SelectGenderPhase extends Phase { } end(): void { - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.MESSAGE); super.end(); } } diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 19e1ccc12ae..6c8788c6a51 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,15 +1,18 @@ -import BattleScene from "#app/battle-scene"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; -import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; -import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; +import { globalScene } from "#app/global-scene"; +import type { ModifierTier } from "#app/modifier/modifier-tier"; +import type { ModifierTypeOption, ModifierType } from "#app/modifier/modifier-type"; +import { regenerateModifierPoolThresholds, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; +import type { Modifier } from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, PokemonHeldItemModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; +import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; import * as Utils from "#app/utils"; import { BattlePhase } from "./battle-phase"; import Overrides from "#app/overrides"; -import { CustomModifierSettings } from "#app/modifier/modifier-type"; +import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import { isNullOrUndefined, NumberHolder } from "#app/utils"; export class SelectModifierPhase extends BattlePhase { @@ -20,8 +23,8 @@ export class SelectModifierPhase extends BattlePhase { private typeOptions: ModifierTypeOption[]; - constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { - super(scene); + constructor(rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { + super(); this.rerollCount = rerollCount; this.modifierTiers = modifierTiers; @@ -35,17 +38,17 @@ export class SelectModifierPhase extends BattlePhase { if (!this.rerollCount && !this.isCopy) { this.updateSeed(); } else if (this.rerollCount) { - this.scene.reroll = false; + globalScene.reroll = false; } - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); if (!this.isCopy) { regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); } const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) { - this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); - this.scene.applyModifiers(TempExtraModifierModifier, true, modifierCount); + globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount); + globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount); } // If custom modifiers are specified, overrides default item count @@ -65,64 +68,64 @@ export class SelectModifierPhase extends BattlePhase { const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { if (rowCursor < 0 || cursor < 0) { - this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { - this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.ui.revertMode(); - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { + globalScene.ui.setOverlayMode(Mode.CONFIRM, () => { + globalScene.ui.revertMode(); + globalScene.ui.setMode(Mode.MESSAGE); super.end(); - }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers))); + }, () => globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers))); }); return false; } let modifierType: ModifierType; let cost: integer; - const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); + const rerollCost = this.getRerollCost(globalScene.lockModifierTiers); switch (rowCursor) { case 0: switch (cursor) { case 0: - if (rerollCost < 0 || this.scene.money < rerollCost) { - this.scene.ui.playError(); + if (rerollCost < 0 || globalScene.money < rerollCost) { + globalScene.ui.playError(); return false; } else { - this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + globalScene.reroll = true; + globalScene.unshiftPhase(new SelectModifierPhase(this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE).then(() => super.end()); if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); + globalScene.money -= rerollCost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); } - this.scene.playSound("se/buy"); + globalScene.playSound("se/buy"); } break; case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const itemModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity, undefined, undefined, false); + globalScene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity, undefined, undefined, false); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers)); } }, PartyUiHandler.FilterItemMaxStacks); break; case 2: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers)); }); break; case 3: if (rerollCost < 0) { // Reroll lock button is also disabled when reroll is disabled - this.scene.ui.playError(); + globalScene.ui.playError(); return false; } - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.lockModifierTiers = !globalScene.lockModifierTiers; + const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers)); uiHandler.updateLockRaritiesText(); uiHandler.updateRerollCostText(); return false; @@ -130,8 +133,8 @@ export class SelectModifierPhase extends BattlePhase { return true; case 1: if (this.typeOptions.length === 0) { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE); super.end(); return true; } @@ -140,51 +143,51 @@ export class SelectModifierPhase extends BattlePhase { } break; default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOptions = getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, globalScene.getWaveMoneyAmount(1)); const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; if (shopOption.type) { modifierType = shopOption.type; } // Apply Black Sludge to healing item cost const healingItemCost = new NumberHolder(shopOption.cost); - this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); + globalScene.applyModifier(HealShopCostModifier, true, healingItemCost); cost = healingItemCost.value; break; } - if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? - this.scene.ui.playError(); + if (cost! && (globalScene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? + globalScene.ui.playError(); return false; } const applyModifier = (modifier: Modifier, playSound: boolean = false) => { - const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost); + const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost); // Queue a copy of this phase when applying a TM or Memory Mushroom. // If the player selects either of these, then escapes out of consuming them, // they are returned to a shop in the same state. if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) { - this.scene.unshiftPhase(this.copy()); + globalScene.unshiftPhase(this.copy()); } if (cost && !(modifier.type instanceof RememberMoveModifierType)) { result.then(success => { if (success) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { - this.scene.money -= cost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); + globalScene.money -= cost; + globalScene.updateMoneyText(); + globalScene.animateMoneyChanged(false); } - this.scene.playSound("se/buy"); - (this.scene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); + globalScene.playSound("se/buy"); + (globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText(); } else { - this.scene.ui.playError(); + globalScene.ui.playError(); } }); } else { const doEnd = () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE); super.end(); }; if (result instanceof Promise) { @@ -197,14 +200,14 @@ export class SelectModifierPhase extends BattlePhase { if (modifierType! instanceof PokemonModifierType) { //TODO: is the bang correct? if (modifierType instanceof FusePokemonModifierType) { - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct? applyModifier(modifier, true); }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers)); } }, modifierType.selectFilter); } else { @@ -220,9 +223,9 @@ export class SelectModifierPhase extends BattlePhase { const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined; - this.scene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { + globalScene.ui.setModeWithoutClear(Mode.PARTY, partyUiMode, -1, (slotIndex: integer, option: PartyOption) => { if (slotIndex < 6) { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = !isMoveModifier ? !isRememberMoveModifier ? modifierType.newModifier(party[slotIndex]) @@ -231,7 +234,7 @@ export class SelectModifierPhase extends BattlePhase { applyModifier(modifier!, true); // TODO: is the bang correct? }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers)); } }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); } @@ -241,11 +244,11 @@ export class SelectModifierPhase extends BattlePhase { return !cost!;// TODO: is the bang correct? }; - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + globalScene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(globalScene.lockModifierTiers)); } updateSeed(): void { - this.scene.resetSeed(); + globalScene.resetSeed(); } isPlayer(): boolean { @@ -276,11 +279,11 @@ export class SelectModifierPhase extends BattlePhase { multiplier = this.customModifierSettings.rerollMultiplier; } - const baseMultiplier = Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * (2 ** this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); + const baseMultiplier = Math.min(Math.ceil(globalScene.currentBattle.waveIndex / 10) * baseValue * (2 ** this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); // Apply Black Sludge to reroll cost const modifiedRerollCost = new NumberHolder(baseMultiplier); - this.scene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); + globalScene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); return modifiedRerollCost.value; } @@ -289,12 +292,11 @@ export class SelectModifierPhase extends BattlePhase { } getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { - return getPlayerModifierTypeOptions(modifierCount, this.scene.getPlayerParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings); + return getPlayerModifierTypeOptions(modifierCount, globalScene.getPlayerParty(), globalScene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings); } copy(): SelectModifierPhase { return new SelectModifierPhase( - this.scene, this.rerollCount, this.modifierTiers, { guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false }, @@ -303,6 +305,6 @@ export class SelectModifierPhase extends BattlePhase { } addModifier(modifier: Modifier): Promise { - return this.scene.addModifier(modifier, false, true); + return globalScene.addModifier(modifier, false, true); } } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 2273ab1cd3c..20ba414147a 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { Gender } from "#app/data/gender"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; @@ -8,31 +8,31 @@ import Overrides from "#app/overrides"; import { Phase } from "#app/phase"; import { TitlePhase } from "#app/phases/title-phase"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; -import { Starter } from "#app/ui/starter-select-ui-handler"; +import type { Starter } from "#app/ui/starter-select-ui-handler"; import { Mode } from "#app/ui/ui"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; export class SelectStarterPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.playBgm("menu"); + globalScene.playBgm("menu"); - this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + globalScene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => { + globalScene.ui.clearText(); + globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { if (slotId === -1) { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.pushPhase(new TitlePhase()); return this.end(); } - this.scene.sessionSlotId = slotId; + globalScene.sessionSlotId = slotId; this.initBattle(starters); }); }); @@ -43,13 +43,13 @@ export class SelectStarterPhase extends Phase { * @param starters {@linkcode Pokemon} with which to start the first battle */ initBattle(starters: Starter[]) { - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); const loadPokemonAssets: Promise[] = []; starters.forEach((starter: Starter, i: integer) => { if (!i && Overrides.STARTER_SPECIES_OVERRIDE) { starter.species = getPokemonSpecies(Overrides.STARTER_SPECIES_OVERRIDE as Species); } - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); if ( starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES && @@ -64,13 +64,13 @@ export class SelectStarterPhase extends Phase { if (Overrides.GENDER_OVERRIDE !== null) { starterGender = Overrides.GENDER_OVERRIDE; } - const starterIvs = this.scene.gameData.dexData[starter.species.speciesId].ivs.slice(0); - const starterPokemon = this.scene.addPlayerPokemon(starter.species, this.scene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); + const starterIvs = globalScene.gameData.dexData[starter.species.speciesId].ivs.slice(0); + const starterPokemon = globalScene.addPlayerPokemon(starter.species, globalScene.gameMode.getStartingLevel(), starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, starterIvs, starter.nature); starter.moveset && starterPokemon.tryPopulateMoveset(starter.moveset); if (starter.passive) { starterPokemon.passive = true; } - starterPokemon.luck = this.scene.gameData.getDexAttrLuck(this.scene.gameData.dexData[starter.species.speciesId].caughtAttr); + starterPokemon.luck = globalScene.gameData.getDexAttrLuck(globalScene.gameData.dexData[starter.species.speciesId].caughtAttr); if (starter.pokerus) { starterPokemon.pokerus = true; } @@ -79,31 +79,31 @@ export class SelectStarterPhase extends Phase { starterPokemon.nickname = starter.nickname; } - if (this.scene.gameMode.isSplicedOnly || Overrides.STARTER_FUSION_OVERRIDE) { + if (globalScene.gameMode.isSplicedOnly || Overrides.STARTER_FUSION_OVERRIDE) { starterPokemon.generateFusionSpecies(true); } starterPokemon.setVisible(false); - applyChallenges(this.scene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); + applyChallenges(globalScene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); party.push(starterPokemon); loadPokemonAssets.push(starterPokemon.loadAssets()); }); - overrideModifiers(this.scene); - overrideHeldItems(this.scene, party[0]); + overrideModifiers(); + overrideHeldItems(party[0]); Promise.all(loadPokemonAssets).then(() => { - SoundFade.fadeOut(this.scene, this.scene.sound.get("menu"), 500, true); - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - if (this.scene.gameMode.isClassic) { - this.scene.gameData.gameStats.classicSessionsPlayed++; + SoundFade.fadeOut(globalScene, globalScene.sound.get("menu"), 500, true); + globalScene.time.delayedCall(500, () => globalScene.playBgm()); + if (globalScene.gameMode.isClassic) { + globalScene.gameData.gameStats.classicSessionsPlayed++; } else { - this.scene.gameData.gameStats.endlessSessionsPlayed++; + globalScene.gameData.gameStats.endlessSessionsPlayed++; } - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; + globalScene.newBattle(); + globalScene.arena.init(); + globalScene.sessionPlayTime = 0; + globalScene.lastSavePlayTime = 0; // Ensures Keldeo (or any future Pokemon that have this type of form change) starts in the correct form - this.scene.getPlayerParty().forEach((p) => { - this.scene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); + globalScene.getPlayerParty().forEach((p) => { + globalScene.triggerPokemonFormChange(p, SpeciesFormChangeMoveLearnedTrigger); }); this.end(); }); diff --git a/src/phases/select-target-phase.ts b/src/phases/select-target-phase.ts index 6f11f984c4b..b21415746fb 100644 --- a/src/phases/select-target-phase.ts +++ b/src/phases/select-target-phase.ts @@ -1,5 +1,5 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; import { CommandPhase } from "./command-phase"; @@ -8,33 +8,33 @@ import i18next from "#app/plugins/i18n"; import { allMoves } from "#app/data/move"; export class SelectTargetPhase extends PokemonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); + constructor(fieldIndex: integer) { + super(fieldIndex); } start() { super.start(); - const turnCommand = this.scene.currentBattle.turnCommands[this.fieldIndex]; + const turnCommand = globalScene.currentBattle.turnCommands[this.fieldIndex]; const move = turnCommand?.move?.move; - this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { - this.scene.ui.setMode(Mode.MESSAGE); - const fieldSide = this.scene.getField(); + globalScene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (targets: BattlerIndex[]) => { + globalScene.ui.setMode(Mode.MESSAGE); + const fieldSide = globalScene.getField(); const user = fieldSide[this.fieldIndex]; const moveObject = allMoves[move!]; if (moveObject && user.isMoveTargetRestricted(moveObject.id, user, fieldSide[targets[0]])) { const errorMessage = user.getRestrictingTag(move!, user, fieldSide[targets[0]])!.selectionDeniedText(user, moveObject.id); - user.scene.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true); + globalScene.queueMessage(i18next.t(errorMessage, { moveName: moveObject.name }), 0, true); targets = []; } if (targets.length < 1) { - this.scene.currentBattle.turnCommands[this.fieldIndex] = null; - this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); + globalScene.currentBattle.turnCommands[this.fieldIndex] = null; + globalScene.unshiftPhase(new CommandPhase(this.fieldIndex)); } else { turnCommand!.targets = targets; //TODO: is the bang correct here? } if (turnCommand?.command === Command.BALL && this.fieldIndex) { - this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? + globalScene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; //TODO: is the bang correct here? } this.end(); }); diff --git a/src/phases/shiny-sparkle-phase.ts b/src/phases/shiny-sparkle-phase.ts index 49c60a82dd5..2540d98fb79 100644 --- a/src/phases/shiny-sparkle-phase.ts +++ b/src/phases/shiny-sparkle-phase.ts @@ -1,16 +1,16 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; export class ShinySparklePhase extends PokemonPhase { - constructor(scene: BattleScene, battlerIndex: BattlerIndex) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex) { + super(battlerIndex); } start() { super.start(); this.getPokemon().sparkle(); - this.scene.time.delayedCall(1000, () => this.end()); + globalScene.time.delayedCall(1000, () => this.end()); } } diff --git a/src/phases/show-ability-phase.ts b/src/phases/show-ability-phase.ts index cf34e327b4f..a0db660ded5 100644 --- a/src/phases/show-ability-phase.ts +++ b/src/phases/show-ability-phase.ts @@ -1,12 +1,12 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex } from "#app/battle"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { PokemonPhase } from "./pokemon-phase"; export class ShowAbilityPhase extends PokemonPhase { private passive: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, passive: boolean = false) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, passive: boolean = false) { + super(battlerIndex); this.passive = passive; } @@ -17,7 +17,7 @@ export class ShowAbilityPhase extends PokemonPhase { const pokemon = this.getPokemon(); if (pokemon) { - this.scene.abilityBar.showAbility(pokemon, this.passive); + globalScene.abilityBar.showAbility(pokemon, this.passive); if (pokemon?.battleData) { pokemon.battleData.abilityRevealed = true; diff --git a/src/phases/show-party-exp-bar-phase.ts b/src/phases/show-party-exp-bar-phase.ts index f1783e7715f..71bd39e3898 100644 --- a/src/phases/show-party-exp-bar-phase.ts +++ b/src/phases/show-party-exp-bar-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { ExpGainsSpeed } from "#app/enums/exp-gains-speed"; import { ExpNotification } from "#app/enums/exp-notification"; import { ExpBoosterModifier } from "#app/modifier/modifier"; @@ -10,8 +10,8 @@ import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-pha export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; - constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) { - super(scene, partyMemberIndex); + constructor(partyMemberIndex: integer, expValue: number) { + super(partyMemberIndex); this.expValue = expValue; } @@ -21,33 +21,33 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { const pokemon = this.getPokemon(); const exp = new Utils.NumberHolder(this.expValue); - this.scene.applyModifiers(ExpBoosterModifier, true, exp); + globalScene.applyModifiers(ExpBoosterModifier, true, exp); exp.value = Math.floor(exp.value); const lastLevel = pokemon.level; pokemon.addExp(exp.value); const newLevel = pokemon.level; if (newLevel > lastLevel) { - this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel)); + globalScene.unshiftPhase(new LevelUpPhase(this.partyMemberIndex, lastLevel, newLevel)); } - this.scene.unshiftPhase(new HidePartyExpBarPhase(this.scene)); + globalScene.unshiftPhase(new HidePartyExpBarPhase()); pokemon.updateInfo(); - if (this.scene.expParty === ExpNotification.SKIP) { + if (globalScene.expParty === ExpNotification.SKIP) { this.end(); - } else if (this.scene.expParty === ExpNotification.ONLY_LEVEL_UP) { + } else if (globalScene.expParty === ExpNotification.ONLY_LEVEL_UP) { if (newLevel > lastLevel) { // this means if we level up // instead of displaying the exp gain in the small frame, we display the new level // we use the same method for mode 0 & 1, by giving a parameter saying to display the exp or the level - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, this.scene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { - setTimeout(() => this.end(), 800 / Math.pow(2, this.scene.expGainsSpeed)); + globalScene.partyExpBar.showPokemonExp(pokemon, exp.value, globalScene.expParty === ExpNotification.ONLY_LEVEL_UP, newLevel).then(() => { + setTimeout(() => this.end(), 800 / Math.pow(2, globalScene.expGainsSpeed)); }); } else { this.end(); } - } else if (this.scene.expGainsSpeed < ExpGainsSpeed.SKIP) { - this.scene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { - setTimeout(() => this.end(), 500 / Math.pow(2, this.scene.expGainsSpeed)); + } else if (globalScene.expGainsSpeed < ExpGainsSpeed.SKIP) { + globalScene.partyExpBar.showPokemonExp(pokemon, exp.value, false, newLevel).then(() => { + setTimeout(() => this.end(), 500 / Math.pow(2, globalScene.expGainsSpeed)); }); } else { this.end(); diff --git a/src/phases/show-trainer-phase.ts b/src/phases/show-trainer-phase.ts index 26ccddd53fc..17106b54048 100644 --- a/src/phases/show-trainer-phase.ts +++ b/src/phases/show-trainer-phase.ts @@ -1,21 +1,21 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { PlayerGender } from "#app/enums/player-gender"; import { BattlePhase } from "./battle-phase"; export class ShowTrainerPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.trainer.setVisible(true); + globalScene.trainer.setVisible(true); - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); - this.scene.tweens.add({ - targets: this.scene.trainer, + globalScene.tweens.add({ + targets: globalScene.trainer, x: 106, duration: 1000, onComplete: () => this.end() diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 44144f9d047..f2e95446139 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { BattlerIndex } from "#app/battle"; import { applyAbAttrs, applyPostStatStageChangeAbAttrs, applyPreStatStageChangeAbAttrs, PostStatStageChangeAbAttr, ProtectStatAbAttr, StatStageChangeCopyAbAttr, StatStageChangeMultiplierAbAttr } from "#app/data/ability"; import { ArenaTagSide, MistTag } from "#app/data/arena-tag"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { ResetNegativeStatStageModifier } from "#app/modifier/modifier"; import { handleTutorial, Tutorial } from "#app/tutorial"; @@ -23,8 +23,8 @@ export class StatStageChangePhase extends PokemonPhase { private onChange: StatStageChangeCallback | null; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null) { + super(battlerIndex); this.selfTarget = selfTarget; this.stats = stats; @@ -41,7 +41,7 @@ export class StatStageChangePhase extends PokemonPhase { if (this.stats.length > 1) { for (let i = 0; i < this.stats.length; i++) { const stat = [ this.stats[i] ]; - this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); + globalScene.unshiftPhase(new StatStageChangePhase(this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); } return this.end(); } @@ -65,7 +65,7 @@ export class StatStageChangePhase extends PokemonPhase { if (!this.selfTarget && stages.value < 0) { // TODO: add a reference to the source of the stat change to fix Infiltrator interaction - this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, cancelled); + globalScene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, cancelled); } if (!cancelled.value && !this.selfTarget && stages.value < 0) { @@ -88,7 +88,7 @@ export class StatStageChangePhase extends PokemonPhase { if (this.showMessage) { const messages = this.getStatStageChangeMessages(filteredStats, stages.value, relLevels); for (const message of messages) { - this.scene.queueMessage(message); + globalScene.queueMessage(message); } } @@ -119,50 +119,50 @@ export class StatStageChangePhase extends PokemonPhase { applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); // Look for any other stat change phases; if this is the last one, do White Herb check - const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex); + const existingPhase = globalScene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex); if (!(existingPhase instanceof StatStageChangePhase)) { // Apply White Herb if needed - const whiteHerb = this.scene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as ResetNegativeStatStageModifier; + const whiteHerb = globalScene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as ResetNegativeStatStageModifier; // If the White Herb was applied, consume it if (whiteHerb) { pokemon.loseHeldItem(whiteHerb); - this.scene.updateModifiers(this.player); + globalScene.updateModifiers(this.player); } } pokemon.updateInfo(); - handleTutorial(this.scene, Tutorial.Stat_Change).then(() => super.end()); + handleTutorial(Tutorial.Stat_Change).then(() => super.end()); }; - if (relLevels.filter(l => l).length && this.scene.moveAnimations) { + if (relLevels.filter(l => l).length && globalScene.moveAnimations) { pokemon.enableMask(); const pokemonMaskSprite = pokemon.maskSprite; - const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileY = ((this.player ? 148 : 84) + (stages.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); - const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); + const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * globalScene.field.scale; + const tileY = ((this.player ? 148 : 84) + (stages.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * globalScene.field.scale; + const tileWidth = 156 * globalScene.field.scale * pokemon.getSpriteScale(); + const tileHeight = 316 * globalScene.field.scale * pokemon.getSpriteScale(); // On increase, show the red sprite located at ATK // On decrease, show the blue sprite located at SPD const spriteColor = stages.value >= 1 ? Stat[Stat.ATK].toLowerCase() : Stat[Stat.SPD].toLowerCase(); - const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); - statSprite.setPipeline(this.scene.fieldSpritePipeline); + const statSprite = globalScene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); + statSprite.setPipeline(globalScene.fieldSpritePipeline); statSprite.setAlpha(0); statSprite.setScale(6); statSprite.setOrigin(0.5, 1); - this.scene.playSound(`se/stat_${stages.value >= 1 ? "up" : "down"}`); + globalScene.playSound(`se/stat_${stages.value >= 1 ? "up" : "down"}`); - statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); + statSprite.setMask(new Phaser.Display.Masks.BitmapMask(globalScene, pokemonMaskSprite ?? undefined)); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: statSprite, duration: 250, alpha: 0.8375, onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: statSprite, delay: 1000, duration: 250, @@ -171,13 +171,13 @@ export class StatStageChangePhase extends PokemonPhase { } }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: statSprite, duration: 1500, y: `${stages.value >= 1 ? "-" : "+"}=${160 * 6}` }); - this.scene.time.delayedCall(1750, () => { + globalScene.time.delayedCall(1750, () => { pokemon.disableMask(); end(); }); @@ -191,21 +191,21 @@ export class StatStageChangePhase extends PokemonPhase { const isAccEva = accEva.some(s => this.stats.includes(s)); let existingPhase: StatStageChangePhase; if (this.stats.length === 1) { - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 + while ((existingPhase = (globalScene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 && (p.stats[0] === this.stats[0]) && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) { this.stages += existingPhase.stages; - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + if (!globalScene.tryRemovePhase(p => p === existingPhase)) { break; } } } - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget + while ((existingPhase = (globalScene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget && (accEva.some(s => p.stats.includes(s)) === isAccEva) && p.stages === this.stages && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) { this.stats.push(...existingPhase.stats); - if (!this.scene.tryRemovePhase(p => p === existingPhase)) { + if (!globalScene.tryRemovePhase(p => p === existingPhase)) { break; } } diff --git a/src/phases/summon-missing-phase.ts b/src/phases/summon-missing-phase.ts index 83ac8779dd8..f32f6a377f5 100644 --- a/src/phases/summon-missing-phase.ts +++ b/src/phases/summon-missing-phase.ts @@ -1,15 +1,15 @@ -import BattleScene from "#app/battle-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { SummonPhase } from "./summon-phase"; +import { globalScene } from "#app/global-scene"; export class SummonMissingPhase extends SummonPhase { - constructor(scene: BattleScene, fieldIndex: integer) { - super(scene, fieldIndex); + constructor(fieldIndex: integer) { + super(fieldIndex); } preSummon(): void { - this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); - this.scene.time.delayedCall(250, () => this.summon()); + globalScene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + globalScene.time.delayedCall(250, () => this.summon()); } } diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index 177e09c4527..72f5ec8640f 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -1,11 +1,11 @@ -import BattleScene from "#app/battle-scene"; import { BattleType } from "#app/battle"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { TrainerSlot } from "#app/data/trainer-config"; import { PlayerGender } from "#app/enums/player-gender"; import { addPokeballOpenParticles } from "#app/field/anims"; -import Pokemon, { FieldPosition } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import i18next from "i18next"; import { PartyMemberPokemonPhase } from "./party-member-pokemon-phase"; @@ -13,12 +13,13 @@ import { PostSummonPhase } from "./post-summon-phase"; import { GameOverPhase } from "./game-over-phase"; import { ShinySparklePhase } from "./shiny-sparkle-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import { globalScene } from "#app/global-scene"; export class SummonPhase extends PartyMemberPokemonPhase { private loaded: boolean; - constructor(scene: BattleScene, fieldIndex: integer, player: boolean = true, loaded: boolean = false) { - super(scene, fieldIndex, player); + constructor(fieldIndex: integer, player: boolean = true, loaded: boolean = false) { + super(fieldIndex, player); this.loaded = loaded; } @@ -50,8 +51,8 @@ export class SummonPhase extends PartyMemberPokemonPhase { if (legalIndex === -1) { console.error("Party Details:\n", party); console.error("All available Pokemon were fainted or illegal!"); - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase()); this.end(); return; } @@ -62,33 +63,33 @@ export class SummonPhase extends PartyMemberPokemonPhase { } if (this.player) { - this.scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); + globalScene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(this.getPokemon()) })); if (this.player) { - this.scene.pbTray.hide(); + globalScene.pbTray.hide(); } - this.scene.trainer.setTexture(`trainer_${this.scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); - this.scene.time.delayedCall(562, () => { - this.scene.trainer.setFrame("2"); - this.scene.time.delayedCall(64, () => { - this.scene.trainer.setFrame("3"); + globalScene.trainer.setTexture(`trainer_${globalScene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); + globalScene.time.delayedCall(562, () => { + globalScene.trainer.setFrame("2"); + globalScene.time.delayedCall(64, () => { + globalScene.trainer.setFrame("3"); }); }); - this.scene.tweens.add({ - targets: this.scene.trainer, + globalScene.tweens.add({ + targets: globalScene.trainer, x: -36, duration: 1000, - onComplete: () => this.scene.trainer.setVisible(false) + onComplete: () => globalScene.trainer.setVisible(false) }); - this.scene.time.delayedCall(750, () => this.summon()); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { - const trainerName = this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); + globalScene.time.delayedCall(750, () => this.summon()); + } else if (globalScene.currentBattle.battleType === BattleType.TRAINER || globalScene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { + const trainerName = globalScene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); const pokemonName = this.getPokemon().getNameToRender(); const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); - this.scene.pbTrayEnemy.hide(); - this.scene.ui.showText(message, null, () => this.summon()); - } else if (this.scene.currentBattle.isBattleMysteryEncounter()) { - this.scene.pbTrayEnemy.hide(); + globalScene.pbTrayEnemy.hide(); + globalScene.ui.showText(message, null, () => this.summon()); + } else if (globalScene.currentBattle.isBattleMysteryEncounter()) { + globalScene.pbTrayEnemy.hide(); this.summonWild(); } } @@ -99,55 +100,55 @@ export class SummonPhase extends PartyMemberPokemonPhase { summon(): void { const pokemon = this.getPokemon(); - const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); + const pokeball = globalScene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball)); pokeball.setVisible(false); pokeball.setOrigin(0.5, 0.625); - this.scene.field.add(pokeball); + globalScene.field.add(pokeball); if (this.fieldIndex === 1) { pokemon.setFieldPosition(FieldPosition.RIGHT, 0); } else { const availablePartyMembers = this.getParty().filter(p => p.isAllowedInBattle()).length; - pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); + pokemon.setFieldPosition(!globalScene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); } const fpOffset = pokemon.getFieldPositionOffset(); pokeball.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 650, x: (this.player ? 100 : 236) + fpOffset[0] }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 150, ease: "Cubic.easeOut", y: (this.player ? 70 : 34) + fpOffset[1], onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokeball, duration: 500, ease: "Cubic.easeIn", angle: 1440, y: (this.player ? 132 : 86) + fpOffset[1], onComplete: () => { - this.scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokeball.destroy(); - this.scene.add.existing(pokemon); - this.scene.field.add(pokemon); + globalScene.add.existing(pokemon); + globalScene.field.add(pokemon); if (!this.player) { - const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; + const playerPokemon = globalScene.getPlayerPokemon() as Pokemon; if (playerPokemon?.isOnField()) { - this.scene.field.moveBelow(pokemon, playerPokemon); + globalScene.field.moveBelow(pokemon, playerPokemon); } - this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); + globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); } - addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball); - this.scene.updateModifiers(this.player); - this.scene.updateFieldScale(); + addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball); + globalScene.updateModifiers(this.player); + globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); pokemon.setVisible(true); @@ -155,8 +156,8 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.setScale(0.5); pokemon.tint(getPokeballTintColor(pokemon.pokeball)); pokemon.untint(250, "Sine.easeIn"); - this.scene.updateFieldScale(); - this.scene.tweens.add({ + globalScene.updateFieldScale(); + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeIn", @@ -165,7 +166,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); pokemon.getSprite().clearTint(); pokemon.resetSummonData(); - this.scene.time.delayedCall(1000, () => this.end()); + globalScene.time.delayedCall(1000, () => this.end()); } }); } @@ -186,20 +187,20 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.setFieldPosition(FieldPosition.RIGHT, 0); } else { const availablePartyMembers = this.getParty().filter(p => !p.isFainted()).length; - pokemon.setFieldPosition(!this.scene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); + pokemon.setFieldPosition(!globalScene.currentBattle.double || availablePartyMembers === 1 ? FieldPosition.CENTER : FieldPosition.LEFT); } - this.scene.add.existing(pokemon); - this.scene.field.add(pokemon); + globalScene.add.existing(pokemon); + globalScene.field.add(pokemon); if (!this.player) { - const playerPokemon = this.scene.getPlayerPokemon() as Pokemon; + const playerPokemon = globalScene.getPlayerPokemon() as Pokemon; if (playerPokemon?.isOnField()) { - this.scene.field.moveBelow(pokemon, playerPokemon); + globalScene.field.moveBelow(pokemon, playerPokemon); } - this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); + globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); } - this.scene.updateModifiers(this.player); - this.scene.updateFieldScale(); + globalScene.updateModifiers(this.player); + globalScene.updateFieldScale(); pokemon.showInfo(); pokemon.playAnim(); pokemon.setVisible(true); @@ -207,13 +208,13 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.setScale(0.75); pokemon.tint(getPokeballTintColor(pokemon.pokeball)); pokemon.untint(250, "Sine.easeIn"); - this.scene.updateFieldScale(); + globalScene.updateFieldScale(); pokemon.x += 16; pokemon.y -= 20; pokemon.alpha = 0; // Ease pokemon in - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, x: "-=16", y: "+=16", @@ -225,8 +226,8 @@ export class SummonPhase extends PartyMemberPokemonPhase { pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 }); pokemon.getSprite().clearTint(); pokemon.resetSummonData(); - this.scene.updateFieldScale(); - this.scene.time.delayedCall(1000, () => this.end()); + globalScene.updateFieldScale(); + globalScene.time.delayedCall(1000, () => this.end()); } }); } @@ -235,19 +236,19 @@ export class SummonPhase extends PartyMemberPokemonPhase { const pokemon = this.getPokemon(); if (pokemon.isShiny()) { - this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); + globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex())); } pokemon.resetTurnData(); - if (!this.loaded || [ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(this.scene.currentBattle.battleType) || (this.scene.currentBattle.waveIndex % 10) === 1) { - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + if (!this.loaded || [ BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER ].includes(globalScene.currentBattle.battleType) || (globalScene.currentBattle.waveIndex % 10) === 1) { + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); this.queuePostSummon(); } } queuePostSummon(): void { - this.scene.pushPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + globalScene.pushPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); } end() { diff --git a/src/phases/switch-biome-phase.ts b/src/phases/switch-biome-phase.ts index 80a31794209..88addb4ef47 100644 --- a/src/phases/switch-biome-phase.ts +++ b/src/phases/switch-biome-phase.ts @@ -1,13 +1,13 @@ -import BattleScene from "#app/battle-scene"; -import { Biome } from "#app/enums/biome"; +import { globalScene } from "#app/global-scene"; +import type { Biome } from "#app/enums/biome"; import { getBiomeKey } from "#app/field/arena"; import { BattlePhase } from "./battle-phase"; export class SwitchBiomePhase extends BattlePhase { private nextBiome: Biome; - constructor(scene: BattleScene, nextBiome: Biome) { - super(scene); + constructor(nextBiome: Biome) { + super(); this.nextBiome = nextBiome; } @@ -19,41 +19,41 @@ export class SwitchBiomePhase extends BattlePhase { return this.end(); } - this.scene.tweens.add({ - targets: [ this.scene.arenaEnemy, this.scene.lastEnemyTrainer ], + globalScene.tweens.add({ + targets: [ globalScene.arenaEnemy, globalScene.lastEnemyTrainer ], x: "+=300", duration: 2000, onComplete: () => { - this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600); + globalScene.arenaEnemy.setX(globalScene.arenaEnemy.x - 600); - this.scene.newArena(this.nextBiome); + globalScene.newArena(this.nextBiome); const biomeKey = getBiomeKey(this.nextBiome); const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBgTransition.setTexture(bgTexture); - this.scene.arenaBgTransition.setAlpha(0); - this.scene.arenaBgTransition.setVisible(true); - this.scene.arenaPlayerTransition.setBiome(this.nextBiome); - this.scene.arenaPlayerTransition.setAlpha(0); - this.scene.arenaPlayerTransition.setVisible(true); + globalScene.arenaBgTransition.setTexture(bgTexture); + globalScene.arenaBgTransition.setAlpha(0); + globalScene.arenaBgTransition.setVisible(true); + globalScene.arenaPlayerTransition.setBiome(this.nextBiome); + globalScene.arenaPlayerTransition.setAlpha(0); + globalScene.arenaPlayerTransition.setVisible(true); - this.scene.tweens.add({ - targets: [ this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition ], + globalScene.tweens.add({ + targets: [ globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition ], duration: 1000, delay: 1000, ease: "Sine.easeInOut", - alpha: (target: any) => target === this.scene.arenaPlayer ? 0 : 1, + alpha: (target: any) => target === globalScene.arenaPlayer ? 0 : 1, onComplete: () => { - this.scene.arenaBg.setTexture(bgTexture); - this.scene.arenaPlayer.setBiome(this.nextBiome); - this.scene.arenaPlayer.setAlpha(1); - this.scene.arenaEnemy.setBiome(this.nextBiome); - this.scene.arenaEnemy.setAlpha(1); - this.scene.arenaNextEnemy.setBiome(this.nextBiome); - this.scene.arenaBgTransition.setVisible(false); - this.scene.arenaPlayerTransition.setVisible(false); - if (this.scene.lastEnemyTrainer) { - this.scene.lastEnemyTrainer.destroy(); + globalScene.arenaBg.setTexture(bgTexture); + globalScene.arenaPlayer.setBiome(this.nextBiome); + globalScene.arenaPlayer.setAlpha(1); + globalScene.arenaEnemy.setBiome(this.nextBiome); + globalScene.arenaEnemy.setAlpha(1); + globalScene.arenaNextEnemy.setBiome(this.nextBiome); + globalScene.arenaBgTransition.setVisible(false); + globalScene.arenaPlayerTransition.setVisible(false); + if (globalScene.lastEnemyTrainer) { + globalScene.lastEnemyTrainer.destroy(); } this.end(); diff --git a/src/phases/switch-phase.ts b/src/phases/switch-phase.ts index 481d64c451e..e763aee1521 100644 --- a/src/phases/switch-phase.ts +++ b/src/phases/switch-phase.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import { SwitchType } from "#enums/switch-type"; @@ -18,7 +18,6 @@ export class SwitchPhase extends BattlePhase { /** * Creates a new SwitchPhase - * @param scene {@linkcode BattleScene} Current battle scene * @param switchType {@linkcode SwitchType} The type of switch logic this phase implements * @param fieldIndex Field index to switch out * @param isModal Indicates if the switch should be forced (true) or is @@ -26,8 +25,8 @@ export class SwitchPhase extends BattlePhase { * @param doReturn Indicates if the party member on the field should be * recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}. */ - constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, isModal: boolean, doReturn: boolean) { - super(scene); + constructor(switchType: SwitchType, fieldIndex: integer, isModal: boolean, doReturn: boolean) { + super(); this.switchType = switchType; this.fieldIndex = fieldIndex; @@ -39,7 +38,7 @@ export class SwitchPhase extends BattlePhase { super.start(); // Skip modal switch if impossible (no remaining party members that aren't in battle) - if (this.isModal && !this.scene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + if (this.isModal && !globalScene.getPlayerParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { return super.end(); } @@ -50,27 +49,27 @@ export class SwitchPhase extends BattlePhase { * if the mon should have already been returned but is still alive and well * on the field. see also; battle.test.ts */ - if (this.isModal && !this.doReturn && !this.scene.getPlayerParty()[this.fieldIndex].isFainted()) { + if (this.isModal && !this.doReturn && !globalScene.getPlayerParty()[this.fieldIndex].isFainted()) { return super.end(); } // Check if there is any space still in field - if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + if (this.isModal && globalScene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= globalScene.currentBattle.getBattlerCount()) { return super.end(); } // Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once - const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getPokemonAllowedInBattle().length > 1 ? this.fieldIndex : 0; + const fieldIndex = globalScene.currentBattle.getBattlerCount() === 1 || globalScene.getPokemonAllowedInBattle().length > 1 ? this.fieldIndex : 0; - this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { - if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + globalScene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { + if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) { // Remove any pre-existing PostSummonPhase under the same field index. // Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave. - this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); + globalScene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType; - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn)); + globalScene.unshiftPhase(new SwitchSummonPhase(switchType, fieldIndex, slotIndex, this.doReturn)); } - this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); + globalScene.ui.setMode(Mode.MESSAGE).then(() => super.end()); }, PartyUiHandler.FilterNonFainted); } } diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index a1925768d83..94e57435922 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -1,10 +1,10 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability"; import { allMoves, ForceSwitchOutAttr } from "#app/data/move"; import { getPokeballTintColor } from "#app/data/pokeball"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { TrainerSlot } from "#app/data/trainer-config"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { SwitchEffectTransferModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; @@ -23,15 +23,14 @@ export class SwitchSummonPhase extends SummonPhase { /** * Constructor for creating a new SwitchSummonPhase - * @param scene {@linkcode BattleScene} the scene the phase is associated with * @param switchType the type of switch behavior * @param fieldIndex integer representing position on the battle field * @param slotIndex integer for the index of pokemon (in party of 6) to switch into * @param doReturn boolean whether to render "comeback" dialogue * @param player boolean if the switch is from the player */ - constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, slotIndex: integer, doReturn: boolean, player?: boolean) { - super(scene, fieldIndex, player !== undefined ? player : true); + constructor(switchType: SwitchType, fieldIndex: integer, slotIndex: integer, doReturn: boolean, player?: boolean) { + super(fieldIndex, player !== undefined ? player : true); this.switchType = switchType; this.slotIndex = slotIndex; @@ -46,29 +45,29 @@ export class SwitchSummonPhase extends SummonPhase { if (!this.player) { if (this.slotIndex === -1) { //@ts-ignore - this.slotIndex = this.scene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? + this.slotIndex = globalScene.currentBattle.trainer?.getNextSummonIndex(!this.fieldIndex ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); // TODO: what would be the default trainer-slot fallback? } if (this.slotIndex > -1) { this.showEnemyTrainer(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); - this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); + globalScene.pbTrayEnemy.showPbTray(globalScene.getEnemyParty()); } } - if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getPlayerParty() : this.scene.getEnemyParty())[this.slotIndex])) { + if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? globalScene.getPlayerParty() : globalScene.getEnemyParty())[this.slotIndex])) { if (this.player) { return this.switchAndSummon(); } else { - this.scene.time.delayedCall(750, () => this.switchAndSummon()); + globalScene.time.delayedCall(750, () => this.switchAndSummon()); return; } } const pokemon = this.getPokemon(); - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) { const substitute = pokemon.getTag(SubstituteTag); if (substitute) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: substitute.sprite, duration: 250, scale: substitute.sprite.scale * 0.5, @@ -78,40 +77,40 @@ export class SwitchSummonPhase extends SummonPhase { } } - this.scene.ui.showText(this.player ? + globalScene.ui.showText(this.player ? i18next.t("battle:playerComeBack", { pokemonName: getPokemonNameWithAffix(pokemon) }) : i18next.t("battle:trainerComeBack", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + trainerName: globalScene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), pokemonName: pokemon.getNameToRender() }) ); - this.scene.playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); pokemon.hideInfo(); pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: pokemon, duration: 250, ease: "Sine.easeIn", scale: 0.5, onComplete: () => { pokemon.leaveField(this.switchType === SwitchType.SWITCH, false); - this.scene.time.delayedCall(750, () => this.switchAndSummon()); + globalScene.time.delayedCall(750, () => this.switchAndSummon()); } }); } switchAndSummon() { - const party = this.player ? this.getParty() : this.scene.getEnemyParty(); + const party = this.player ? this.getParty() : globalScene.getEnemyParty(); const switchedInPokemon = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); if (this.switchType === SwitchType.BATON_PASS && switchedInPokemon) { - (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); - if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier + (this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id)); + if (!globalScene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + const batonPassModifier = globalScene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; - if (batonPassModifier && !this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { - this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false, undefined, undefined, undefined, false); + if (batonPassModifier && !globalScene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) { + globalScene.tryTransferHeldItemModifier(batonPassModifier, switchedInPokemon, false, undefined, undefined, undefined, false); } } } @@ -119,10 +118,10 @@ export class SwitchSummonPhase extends SummonPhase { party[this.slotIndex] = this.lastPokemon; party[this.fieldIndex] = switchedInPokemon; const showTextAndSummon = () => { - this.scene.ui.showText(this.player ? + globalScene.ui.showText(this.player ? i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(switchedInPokemon) }) : i18next.t("battle:trainerGo", { - trainerName: this.scene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), + trainerName: globalScene.currentBattle.trainer?.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER), pokemonName: this.getPokemon().getNameToRender() }) ); @@ -145,9 +144,9 @@ export class SwitchSummonPhase extends SummonPhase { if (this.player) { showTextAndSummon(); } else { - this.scene.time.delayedCall(1500, () => { + globalScene.time.delayedCall(1500, () => { this.hideEnemyTrainer(); - this.scene.pbTrayEnemy.hide(); + globalScene.pbTrayEnemy.hide(); showTextAndSummon(); }); } @@ -161,10 +160,10 @@ export class SwitchSummonPhase extends SummonPhase { const pokemon = this.getPokemon(); - const moveId = this.lastPokemon?.scene.currentBattle.lastMove; + const moveId = globalScene.currentBattle.lastMove; const lastUsedMove = moveId ? allMoves[moveId] : undefined; - const currentCommand = pokemon.scene.currentBattle.turnCommands[this.fieldIndex]?.command; + const currentCommand = globalScene.currentBattle.turnCommands[this.fieldIndex]?.command; const lastPokemonIsForceSwitchedAndNotFainted = lastUsedMove?.hasAttr(ForceSwitchOutAttr) && !this.lastPokemon.isFainted(); const lastPokemonHasForceSwitchAbAttr = this.lastPokemon.hasAbilityWithAttr(PostDamageForceSwitchAbAttr) && !this.lastPokemon.isFainted(); @@ -191,12 +190,12 @@ export class SwitchSummonPhase extends SummonPhase { this.lastPokemon?.resetSummonData(); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true); // Reverts to weather-based forms when weather suppressors (Cloud Nine/Air Lock) are switched out - this.scene.arena.triggerWeatherBasedFormChanges(); + globalScene.arena.triggerWeatherBasedFormChanges(); } queuePostSummon(): void { - this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); + globalScene.unshiftPhase(new PostSummonPhase(this.getPokemon().getBattlerIndex())); } } diff --git a/src/phases/test-message-phase.ts b/src/phases/test-message-phase.ts index 464a5ed1f94..d5e74efd490 100644 --- a/src/phases/test-message-phase.ts +++ b/src/phases/test-message-phase.ts @@ -1,8 +1,7 @@ -import BattleScene from "#app/battle-scene"; import { MessagePhase } from "./message-phase"; export class TestMessagePhase extends MessagePhase { - constructor(scene: BattleScene, message: string) { - super(scene, message, null, true); + constructor(message: string) { + super(message, null, true); } } diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 88793617776..219ff441c52 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -1,17 +1,16 @@ import { loggedInUser } from "#app/account"; import { BattleType } from "#app/battle"; -import BattleScene from "#app/battle-scene"; import { fetchDailyRunSeed, getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; import { getBiomeKey } from "#app/field/arena"; import { GameMode, GameModes, getGameMode } from "#app/game-mode"; -import { Modifier } from "#app/modifier/modifier"; +import type { Modifier } from "#app/modifier/modifier"; import { getDailyRunStarterModifiers, ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { Phase } from "#app/phase"; -import { SessionSaveData } from "#app/system/game-data"; +import type { SessionSaveData } from "#app/system/game-data"; import { Unlockables } from "#app/system/unlockables"; import { vouchers } from "#app/system/voucher"; -import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "#app/ui/save-slot-select-ui-handler"; import { Mode } from "#app/ui/ui"; import * as Utils from "#app/utils"; @@ -21,6 +20,7 @@ import { EncounterPhase } from "./encounter-phase"; import { SelectChallengePhase } from "./select-challenge-phase"; import { SelectStarterPhase } from "./select-starter-phase"; import { SummonPhase } from "./summon-phase"; +import { globalScene } from "#app/global-scene"; export class TitlePhase extends Phase { @@ -28,8 +28,8 @@ export class TitlePhase extends Phase { private lastSessionData: SessionSaveData; public gameMode: GameModes; - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); this.loaded = false; } @@ -37,17 +37,17 @@ export class TitlePhase extends Phase { start(): void { super.start(); - this.scene.ui.clearText(); - this.scene.ui.fadeIn(250); + globalScene.ui.clearText(); + globalScene.ui.fadeIn(250); - this.scene.playBgm("title", true); + globalScene.playBgm("title", true); - this.scene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { + globalScene.gameData.getSession(loggedInUser?.lastSessionSlot ?? -1).then(sessionData => { if (sessionData) { this.lastSessionData = sessionData; const biomeKey = getBiomeKey(sessionData.arena.biome); const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBg.setTexture(bgTexture); + globalScene.arenaBg.setTexture(bgTexture); } this.showOptions(); }).catch(err => { @@ -72,11 +72,11 @@ export class TitlePhase extends Phase { handler: () => { const setModeAndEnd = (gameMode: GameModes) => { this.gameMode = gameMode; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); this.end(); }; - const { gameData } = this.scene; + const { gameData } = globalScene; if (gameData.isUnlocked(Unlockables.ENDLESS_MODE)) { const options: OptionSelectItem[] = [ { @@ -113,17 +113,17 @@ export class TitlePhase extends Phase { options.push({ label: i18next.t("menu:cancel"), handler: () => { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.pushPhase(new TitlePhase()); super.end(); return true; } }); - this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + globalScene.ui.showText(i18next.t("menu:selectGameMode"), null, () => globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); } else { this.gameMode = GameModes.CLASSIC; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.clearText(); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.clearText(); this.end(); } return true; @@ -132,7 +132,7 @@ export class TitlePhase extends Phase { { label: i18next.t("menu:loadGame"), handler: () => { - this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, + globalScene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, (slotId: integer) => { if (slotId === -1) { return this.showOptions(); @@ -153,7 +153,7 @@ export class TitlePhase extends Phase { { label: i18next.t("menu:settings"), handler: () => { - this.scene.ui.setOverlayMode(Mode.SETTINGS); + globalScene.ui.setOverlayMode(Mode.SETTINGS); return true; }, keepOpen: true @@ -163,55 +163,55 @@ export class TitlePhase extends Phase { noCancel: true, yOffset: 47 }; - this.scene.ui.setMode(Mode.TITLE, config); + globalScene.ui.setMode(Mode.TITLE, config); } loadSaveSlot(slotId: integer): void { - this.scene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.resetModeChain(); - this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { + globalScene.sessionSlotId = slotId > -1 || !loggedInUser ? slotId : loggedInUser.lastSessionSlot; + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.resetModeChain(); + globalScene.gameData.loadSession(slotId, slotId === -1 ? this.lastSessionData : undefined).then((success: boolean) => { if (success) { this.loaded = true; - this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); + globalScene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); } else { this.end(); } }).catch(err => { console.error(err); - this.scene.ui.showText(i18next.t("menu:failedToLoadSession"), null); + globalScene.ui.showText(i18next.t("menu:failedToLoadSession"), null); }); } initDailyRun(): void { - this.scene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { - this.scene.clearPhaseQueue(); + globalScene.ui.setMode(Mode.SAVE_SLOT, SaveSlotUiMode.SAVE, (slotId: integer) => { + globalScene.clearPhaseQueue(); if (slotId === -1) { - this.scene.pushPhase(new TitlePhase(this.scene)); + globalScene.pushPhase(new TitlePhase()); return super.end(); } - this.scene.sessionSlotId = slotId; + globalScene.sessionSlotId = slotId; const generateDaily = (seed: string) => { - this.scene.gameMode = getGameMode(GameModes.DAILY); + globalScene.gameMode = getGameMode(GameModes.DAILY); - this.scene.setSeed(seed); - this.scene.resetSeed(0); + globalScene.setSeed(seed); + globalScene.resetSeed(0); - this.scene.money = this.scene.gameMode.getStartingMoney(); + globalScene.money = globalScene.gameMode.getStartingMoney(); - const starters = getDailyRunStarters(this.scene, seed); - const startingLevel = this.scene.gameMode.getStartingLevel(); + const starters = getDailyRunStarters(seed); + const startingLevel = globalScene.gameMode.getStartingLevel(); - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); const loadPokemonAssets: Promise[] = []; for (const starter of starters) { - const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); + const starterProps = globalScene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); const starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0)); const starterGender = starter.species.malePercent !== null ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; - const starterPokemon = this.scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); + const starterPokemon = globalScene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); starterPokemon.setVisible(false); party.push(starterPokemon); loadPokemonAssets.push(starterPokemon.loadAssets()); @@ -226,18 +226,18 @@ export class TitlePhase extends Phase { .filter((m) => m !== null); for (const m of modifiers) { - this.scene.addModifier(m, true, false, false, true); + globalScene.addModifier(m, true, false, false, true); } - this.scene.updateModifiers(true, true); + globalScene.updateModifiers(true, true); Promise.all(loadPokemonAssets).then(() => { - this.scene.time.delayedCall(500, () => this.scene.playBgm()); - this.scene.gameData.gameStats.dailyRunSessionsPlayed++; - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); - this.scene.newBattle(); - this.scene.arena.init(); - this.scene.sessionPlayTime = 0; - this.scene.lastSavePlayTime = 0; + globalScene.time.delayedCall(500, () => globalScene.playBgm()); + globalScene.gameData.gameStats.dailyRunSessionsPlayed++; + globalScene.newArena(globalScene.gameMode.getStartingBiome()); + globalScene.newBattle(); + globalScene.arena.init(); + globalScene.sessionPlayTime = 0; + globalScene.lastSavePlayTime = 0; this.end(); }); }; @@ -260,43 +260,43 @@ export class TitlePhase extends Phase { } end(): void { - if (!this.loaded && !this.scene.gameMode.isDaily) { - this.scene.arena.preloadBgm(); - this.scene.gameMode = getGameMode(this.gameMode); + if (!this.loaded && !globalScene.gameMode.isDaily) { + globalScene.arena.preloadBgm(); + globalScene.gameMode = getGameMode(this.gameMode); if (this.gameMode === GameModes.CHALLENGE) { - this.scene.pushPhase(new SelectChallengePhase(this.scene)); + globalScene.pushPhase(new SelectChallengePhase()); } else { - this.scene.pushPhase(new SelectStarterPhase(this.scene)); + globalScene.pushPhase(new SelectStarterPhase()); } - this.scene.newArena(this.scene.gameMode.getStartingBiome(this.scene)); + globalScene.newArena(globalScene.gameMode.getStartingBiome()); } else { - this.scene.playBgm(); + globalScene.playBgm(); } - this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded)); + globalScene.pushPhase(new EncounterPhase(this.loaded)); if (this.loaded) { - const availablePartyMembers = this.scene.getPokemonAllowedInBattle().length; + const availablePartyMembers = globalScene.getPokemonAllowedInBattle().length; - this.scene.pushPhase(new SummonPhase(this.scene, 0, true, true)); - if (this.scene.currentBattle.double && availablePartyMembers > 1) { - this.scene.pushPhase(new SummonPhase(this.scene, 1, true, true)); + globalScene.pushPhase(new SummonPhase(0, true, true)); + if (globalScene.currentBattle.double && availablePartyMembers > 1) { + globalScene.pushPhase(new SummonPhase(1, true, true)); } - if (this.scene.currentBattle.battleType !== BattleType.TRAINER && (this.scene.currentBattle.waveIndex > 1 || !this.scene.gameMode.isDaily)) { - const minPartySize = this.scene.currentBattle.double ? 2 : 1; + if (globalScene.currentBattle.battleType !== BattleType.TRAINER && (globalScene.currentBattle.waveIndex > 1 || !globalScene.gameMode.isDaily)) { + const minPartySize = globalScene.currentBattle.double ? 2 : 1; if (availablePartyMembers > minPartySize) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 0, this.scene.currentBattle.double)); - if (this.scene.currentBattle.double) { - this.scene.pushPhase(new CheckSwitchPhase(this.scene, 1, this.scene.currentBattle.double)); + globalScene.pushPhase(new CheckSwitchPhase(0, globalScene.currentBattle.double)); + if (globalScene.currentBattle.double) { + globalScene.pushPhase(new CheckSwitchPhase(1, globalScene.currentBattle.double)); } } } } - for (const achv of Object.keys(this.scene.gameData.achvUnlocks)) { + for (const achv of Object.keys(globalScene.gameData.achvUnlocks)) { if (vouchers.hasOwnProperty(achv) && achv !== "CLASSIC_VICTORY") { - this.scene.validateVoucher(vouchers[achv]); + globalScene.validateVoucher(vouchers[achv]); } } diff --git a/src/phases/toggle-double-position-phase.ts b/src/phases/toggle-double-position-phase.ts index eff92bc6acd..f61577444d2 100644 --- a/src/phases/toggle-double-position-phase.ts +++ b/src/phases/toggle-double-position-phase.ts @@ -1,12 +1,12 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { FieldPosition } from "#app/field/pokemon"; import { BattlePhase } from "./battle-phase"; export class ToggleDoublePositionPhase extends BattlePhase { private double: boolean; - constructor(scene: BattleScene, double: boolean) { - super(scene); + constructor(double: boolean) { + super(); this.double = double; } @@ -14,11 +14,11 @@ export class ToggleDoublePositionPhase extends BattlePhase { start() { super.start(); - const playerPokemon = this.scene.getPlayerField().find(p => p.isActive(true)); + const playerPokemon = globalScene.getPlayerField().find(p => p.isActive(true)); if (playerPokemon) { - playerPokemon.setFieldPosition(this.double && this.scene.getPokemonAllowedInBattle().length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { + playerPokemon.setFieldPosition(this.double && globalScene.getPokemonAllowedInBattle().length > 1 ? FieldPosition.LEFT : FieldPosition.CENTER, 500).then(() => { if (playerPokemon.getFieldIndex() === 1) { - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); party[1] = party[0]; party[0] = playerPokemon; } diff --git a/src/phases/trainer-message-test-phase.ts b/src/phases/trainer-message-test-phase.ts index d9e58473bd5..34ce2e8b53e 100644 --- a/src/phases/trainer-message-test-phase.ts +++ b/src/phases/trainer-message-test-phase.ts @@ -1,14 +1,14 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { trainerConfigs } from "#app/data/trainer-config"; -import { TrainerType } from "#app/enums/trainer-type"; +import type { TrainerType } from "#app/enums/trainer-type"; import { BattlePhase } from "./battle-phase"; import { TestMessagePhase } from "./test-message-phase"; export class TrainerMessageTestPhase extends BattlePhase { private trainerTypes: TrainerType[]; - constructor(scene: BattleScene, ...trainerTypes: TrainerType[]) { - super(scene); + constructor(...trainerTypes: TrainerType[]) { + super(); this.trainerTypes = trainerTypes; } @@ -33,7 +33,7 @@ export class TrainerMessageTestPhase extends BattlePhase { } for (const message of testMessages) { - this.scene.pushPhase(new TestMessagePhase(this.scene, message)); + globalScene.pushPhase(new TestMessagePhase(message)); } this.end(); diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index d797e4360ac..3fa7209751a 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -1,4 +1,3 @@ -import BattleScene from "#app/battle-scene"; import { getCharVariantFromDialogue } from "#app/data/dialogue"; import { TrainerType } from "#app/enums/trainer-type"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -9,63 +8,66 @@ import { BattlePhase } from "./battle-phase"; import { ModifierRewardPhase } from "./modifier-reward-phase"; import { MoneyRewardPhase } from "./money-reward-phase"; import { TrainerSlot } from "#app/data/trainer-config"; +import { globalScene } from "#app/global-scene"; import { Biome } from "#app/enums/biome"; import { achvs } from "#app/system/achv"; export class TrainerVictoryPhase extends BattlePhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { - this.scene.disableMenu = true; + globalScene.disableMenu = true; - this.scene.playBgm(this.scene.currentBattle.trainer?.config.victoryBgm); + globalScene.playBgm(globalScene.currentBattle.trainer?.config.victoryBgm); - this.scene.unshiftPhase(new MoneyRewardPhase(this.scene, this.scene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? + globalScene.unshiftPhase(new MoneyRewardPhase(globalScene.currentBattle.trainer?.config.moneyMultiplier!)); // TODO: is this bang correct? - const modifierRewardFuncs = this.scene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? + const modifierRewardFuncs = globalScene.currentBattle.trainer?.config.modifierRewardFuncs!; // TODO: is this bang correct? for (const modifierRewardFunc of modifierRewardFuncs) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, modifierRewardFunc)); + globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } - if (this.scene.eventManager.isEventActive()) { - for (const rewardFunc of this.scene.currentBattle.trainer?.config.eventRewardFuncs!) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, rewardFunc)); + if (globalScene.eventManager.isEventActive()) { + for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { + globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); } } - const trainerType = this.scene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? + const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct? // Validate Voucher for boss trainers if (vouchers.hasOwnProperty(TrainerType[trainerType])) { - if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer?.config.isBoss) { - this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); + if (!globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) && globalScene.currentBattle.trainer?.config.isBoss) { + globalScene.unshiftPhase(new ModifierRewardPhase([ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); } } // Breeders in Space achievement - if (this.scene.arena.biomeType === Biome.SPACE - && (trainerType === TrainerType.BREEDER || trainerType === TrainerType.EXPERT_POKEMON_BREEDER)) { - this.scene.validateAchv(achvs.BREEDERS_IN_SPACE); + if ( + globalScene.arena.biomeType === Biome.SPACE + && (trainerType === TrainerType.BREEDER || trainerType === TrainerType.EXPERT_POKEMON_BREEDER) + ) { + globalScene.validateAchv(achvs.BREEDERS_IN_SPACE); } - this.scene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: this.scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { - const victoryMessages = this.scene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? + globalScene.ui.showText(i18next.t("battle:trainerDefeated", { trainerName: globalScene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) }), null, () => { + const victoryMessages = globalScene.currentBattle.trainer?.getVictoryMessages()!; // TODO: is this bang correct? let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), this.scene.currentBattle.waveIndex); + globalScene.executeWithSeedOffset(() => message = Utils.randSeedItem(victoryMessages), globalScene.currentBattle.waveIndex); message = message!; // tell TS compiler it's defined now const showMessage = () => { const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.ui.showDialogue(message, this.scene.currentBattle.trainer?.getName(TrainerSlot.TRAINER, true), null, originalFunc); + showMessageOrEnd = () => globalScene.ui.showDialogue(message, globalScene.currentBattle.trainer?.getName(TrainerSlot.TRAINER, true), null, originalFunc); showMessageOrEnd(); }; let showMessageOrEnd = () => this.end(); if (victoryMessages?.length) { - if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) { + if (globalScene.currentBattle.trainer?.config.hasCharSprite && !globalScene.ui.shouldSkipDialogue(message)) { const originalFunc = showMessageOrEnd; - showMessageOrEnd = () => this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => originalFunc())); - this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(this.scene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? + showMessageOrEnd = () => globalScene.charSprite.hide().then(() => globalScene.hideFieldOverlay(250).then(() => originalFunc())); + globalScene.showFieldOverlay(500).then(() => globalScene.charSprite.showCharacter(globalScene.currentBattle.trainer?.getKey()!, getCharVariantFromDialogue(victoryMessages[0])).then(() => showMessage())); // TODO: is this bang correct? } else { showMessage(); } diff --git a/src/phases/turn-end-phase.ts b/src/phases/turn-end-phase.ts index e5f1850758d..fc4190ef2eb 100644 --- a/src/phases/turn-end-phase.ts +++ b/src/phases/turn-end-phase.ts @@ -1,49 +1,49 @@ -import BattleScene from "#app/battle-scene"; import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability"; import { BattlerTagLapseType } from "#app/data/battler-tags"; import { TerrainType } from "#app/data/terrain"; import { WeatherType } from "#app/enums/weather-type"; import { TurnEndEvent } from "#app/events/battle-scene"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase"; +import { globalScene } from "#app/global-scene"; export class TurnEndPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.currentBattle.incrementTurn(this.scene); - this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); + globalScene.currentBattle.incrementTurn(); + globalScene.eventTarget.dispatchEvent(new TurnEndEvent(globalScene.currentBattle.turn)); const handlePokemon = (pokemon: Pokemon) => { if (!pokemon.switchOutStatus) { pokemon.lapseTags(BattlerTagLapseType.TURN_END); - this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); + globalScene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon); - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { - this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(), + if (globalScene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) { + globalScene.unshiftPhase(new PokemonHealPhase(pokemon.getBattlerIndex(), Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true)); } if (!pokemon.isPlayer()) { - this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon); - this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); + globalScene.applyModifiers(EnemyTurnHealModifier, false, pokemon); + globalScene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon); } applyPostTurnAbAttrs(PostTurnAbAttr, pokemon); } - this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); + globalScene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon); - this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); + globalScene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon); pokemon.battleSummonData.turnCount++; pokemon.battleSummonData.waveTurnCount++; @@ -51,15 +51,15 @@ export class TurnEndPhase extends FieldPhase { this.executeForAll(handlePokemon); - this.scene.arena.lapseTags(); + globalScene.arena.lapseTags(); - if (this.scene.arena.weather && !this.scene.arena.weather.lapse()) { - this.scene.arena.trySetWeather(WeatherType.NONE, false); - this.scene.arena.triggerWeatherBasedFormChangesToNormal(); + if (globalScene.arena.weather && !globalScene.arena.weather.lapse()) { + globalScene.arena.trySetWeather(WeatherType.NONE, false); + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); } - if (this.scene.arena.terrain && !this.scene.arena.terrain.lapse()) { - this.scene.arena.trySetTerrain(TerrainType.NONE, false); + if (globalScene.arena.terrain && !globalScene.arena.terrain.lapse()) { + globalScene.arena.trySetTerrain(TerrainType.NONE, false); } this.end(); diff --git a/src/phases/turn-init-phase.ts b/src/phases/turn-init-phase.ts index baff6c7d73f..946c9626718 100644 --- a/src/phases/turn-init-phase.ts +++ b/src/phases/turn-init-phase.ts @@ -1,8 +1,7 @@ import { BattlerIndex } from "#app/battle"; -import BattleScene from "#app/battle-scene"; import { handleMysteryEncounterBattleStartEffects, handleMysteryEncounterTurnStartEffects } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { TurnInitEvent } from "#app/events/battle-scene"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import i18next from "i18next"; import { CommandPhase } from "./command-phase"; import { EnemyCommandPhase } from "./enemy-command-phase"; @@ -10,27 +9,28 @@ import { FieldPhase } from "./field-phase"; import { GameOverPhase } from "./game-over-phase"; import { ToggleDoublePositionPhase } from "./toggle-double-position-phase"; import { TurnStartPhase } from "./turn-start-phase"; +import { globalScene } from "#app/global-scene"; export class TurnInitPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start() { super.start(); - this.scene.getPlayerField().forEach(p => { + globalScene.getPlayerField().forEach(p => { // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch if (p.isOnField() && !p.isAllowedInBattle()) { - this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); + globalScene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true); - const allowedPokemon = this.scene.getPokemonAllowedInBattle(); + const allowedPokemon = globalScene.getPokemonAllowedInBattle(); if (!allowedPokemon.length) { // If there are no longer any legal pokemon in the party, game over. - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new GameOverPhase(this.scene)); - } else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) { + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new GameOverPhase()); + } else if (allowedPokemon.length >= globalScene.currentBattle.getBattlerCount() || (globalScene.currentBattle.double && !allowedPokemon[0].isActive(true))) { // If there is at least one pokemon in the back that is legal to switch in, force a switch. p.switchOut(); } else { @@ -38,36 +38,35 @@ export class TurnInitPhase extends FieldPhase { // This should only happen in double battles. p.leaveField(); } - if (allowedPokemon.length === 1 && this.scene.currentBattle.double) { - this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + if (allowedPokemon.length === 1 && globalScene.currentBattle.double) { + globalScene.unshiftPhase(new ToggleDoublePositionPhase(true)); } } }); - //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); - this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); + globalScene.eventTarget.dispatchEvent(new TurnInitEvent()); - handleMysteryEncounterBattleStartEffects(this.scene); + handleMysteryEncounterBattleStartEffects(); // If true, will skip remainder of current phase (and not queue CommandPhases etc.) - if (handleMysteryEncounterTurnStartEffects(this.scene)) { + if (handleMysteryEncounterTurnStartEffects()) { this.end(); return; } - this.scene.getField().forEach((pokemon, i) => { + globalScene.getField().forEach((pokemon, i) => { if (pokemon?.isActive()) { if (pokemon.isPlayer()) { - this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon); + globalScene.currentBattle.addParticipant(pokemon as PlayerPokemon); } pokemon.resetTurnData(); - this.scene.pushPhase(pokemon.isPlayer() ? new CommandPhase(this.scene, i) : new EnemyCommandPhase(this.scene, i - BattlerIndex.ENEMY)); + globalScene.pushPhase(pokemon.isPlayer() ? new CommandPhase(i) : new EnemyCommandPhase(i - BattlerIndex.ENEMY)); } }); - this.scene.pushPhase(new TurnStartPhase(this.scene)); + globalScene.pushPhase(new TurnStartPhase()); this.end(); } diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index b48b018a046..c64d7ddf526 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -1,9 +1,9 @@ -import BattleScene from "#app/battle-scene"; import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/ability"; import { allMoves, MoveHeaderAttr } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { Stat } from "#app/enums/stat"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { BypassSpeedChanceModifier } from "#app/modifier/modifier"; import { Command } from "#app/ui/command-ui-handler"; import * as Utils from "#app/utils"; @@ -20,10 +20,11 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; import { BattlerIndex } from "#app/battle"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; +import { globalScene } from "#app/global-scene"; export class TurnStartPhase extends FieldPhase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } /** @@ -32,20 +33,20 @@ export class TurnStartPhase extends FieldPhase { * @returns {@linkcode BattlerIndex[]} the battle indices of all pokemon on the field ordered by speed */ getSpeedOrder(): BattlerIndex[] { - const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; - const enemyField = this.scene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; + const playerField = globalScene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; + const enemyField = globalScene.getEnemyField().filter(p => p.isActive()) as Pokemon[]; // We shuffle the list before sorting so speed ties produce random results let orderedTargets: Pokemon[] = playerField.concat(enemyField); // We seed it with the current turn to prevent an inconsistency where it // was varying based on how long since you last reloaded - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { orderedTargets = Utils.randSeedShuffle(orderedTargets); - }, this.scene.currentBattle.turn, this.scene.waveSeed); + }, globalScene.currentBattle.turn, globalScene.waveSeed); // Next, a check for Trick Room is applied to determine sort order. const speedReversed = new Utils.BooleanHolder(false); - this.scene.arena.applyTags(TrickRoomTag, false, speedReversed); + globalScene.arena.applyTags(TrickRoomTag, false, speedReversed); // Adjust the sort function based on whether Trick Room is active. orderedTargets.sort((a: Pokemon, b: Pokemon) => { @@ -70,13 +71,13 @@ export class TurnStartPhase extends FieldPhase { // This occurs before the main loop because of battles with more than two Pokemon const battlerBypassSpeed = {}; - this.scene.getField(true).filter(p => p.summonData).map(p => { + globalScene.getField(true).filter(p => p.summonData).map(p => { const bypassSpeed = new Utils.BooleanHolder(false); const canCheckHeldItems = new Utils.BooleanHolder(true); applyAbAttrs(BypassSpeedChanceAbAttr, p, null, false, bypassSpeed); applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, false, bypassSpeed, canCheckHeldItems); if (canCheckHeldItems.value) { - this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); + globalScene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed); } battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); @@ -85,8 +86,8 @@ export class TurnStartPhase extends FieldPhase { // Non-FIGHT commands (SWITCH, BALL, RUN) have a higher command priority and will always occur before any FIGHT commands. moveOrder = moveOrder.slice(0); moveOrder.sort((a, b) => { - const aCommand = this.scene.currentBattle.turnCommands[a]; - const bCommand = this.scene.currentBattle.turnCommands[b]; + const aCommand = globalScene.currentBattle.turnCommands[a]; + const bCommand = globalScene.currentBattle.turnCommands[b]; if (aCommand?.command !== bCommand?.command) { if (aCommand?.command === Command.FIGHT) { @@ -98,8 +99,8 @@ export class TurnStartPhase extends FieldPhase { const aMove = allMoves[aCommand.move!.move]; const bMove = allMoves[bCommand!.move!.move]; - const aUser = this.scene.getField(true).find(p => p.getBattlerIndex() === a)!; - const bUser = this.scene.getField(true).find(p => p.getBattlerIndex() === b)!; + const aUser = globalScene.getField(true).find(p => p.getBattlerIndex() === a)!; + const bUser = globalScene.getField(true).find(p => p.getBattlerIndex() === b)!; const aPriority = aMove.getPriority(aUser, false); const bPriority = bMove.getPriority(bUser, false); @@ -133,7 +134,7 @@ export class TurnStartPhase extends FieldPhase { start() { super.start(); - const field = this.scene.getField(); + const field = globalScene.getField(); const moveOrder = this.getCommandOrder(); let orderIndex = 0; @@ -141,7 +142,7 @@ export class TurnStartPhase extends FieldPhase { for (const o of moveOrder) { const pokemon = field[o]; - const turnCommand = this.scene.currentBattle.turnCommands[o]; + const turnCommand = globalScene.currentBattle.turnCommands[o]; if (turnCommand?.skip) { continue; @@ -156,29 +157,29 @@ export class TurnStartPhase extends FieldPhase { } const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + globalScene.unshiftPhase(new MoveHeaderPhase(pokemon, move)); } if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? + globalScene.pushPhase(new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); + const playerPhase = new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + globalScene.pushPhase(playerPhase); } } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? + globalScene.pushPhase(new MovePhase(pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? } break; case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + globalScene.unshiftPhase(new AttemptCapturePhase(turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? break; case Command.POKEMON: const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); + globalScene.unshiftPhase(new SwitchSummonPhase(switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); break; case Command.RUN: let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { + if (globalScene.currentBattle.double) { const playerActivePokemon = field.filter(pokemon => { if (!!pokemon) { return pokemon.isPlayer() && pokemon.isActive(); @@ -195,18 +196,18 @@ export class TurnStartPhase extends FieldPhase { runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; } } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + globalScene.unshiftPhase(new AttemptRunPhase(runningPokemon.getFieldIndex())); break; } } - this.scene.pushPhase(new WeatherEffectPhase(this.scene)); - this.scene.pushPhase(new BerryPhase(this.scene)); + globalScene.pushPhase(new WeatherEffectPhase()); + globalScene.pushPhase(new BerryPhase()); /** Add a new phase to check who should be taking status damage */ - this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder)); + globalScene.pushPhase(new CheckStatusEffectPhase(moveOrder)); - this.scene.pushPhase(new TurnEndPhase(this.scene)); + globalScene.pushPhase(new TurnEndPhase()); /** * this.end() will call shiftPhase(), which dumps everything from PrependQueue (aka everything that is unshifted()) to the front diff --git a/src/phases/unavailable-phase.ts b/src/phases/unavailable-phase.ts index 59bfca7875e..c0b5d4224c5 100644 --- a/src/phases/unavailable-phase.ts +++ b/src/phases/unavailable-phase.ts @@ -1,16 +1,16 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; import { LoginPhase } from "./login-phase"; export class UnavailablePhase extends Phase { - constructor(scene: BattleScene) { - super(scene); + constructor() { + super(); } start(): void { - this.scene.ui.setMode(Mode.UNAVAILABLE, () => { - this.scene.unshiftPhase(new LoginPhase(this.scene, true)); + globalScene.ui.setMode(Mode.UNAVAILABLE, () => { + globalScene.unshiftPhase(new LoginPhase(true)); this.end(); }); } diff --git a/src/phases/unlock-phase.ts b/src/phases/unlock-phase.ts index 65060309a6c..2d24ee92baf 100644 --- a/src/phases/unlock-phase.ts +++ b/src/phases/unlock-phase.ts @@ -1,26 +1,27 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Phase } from "#app/phase"; -import { Unlockables, getUnlockableName } from "#app/system/unlockables"; +import type { Unlockables } from "#app/system/unlockables"; +import { getUnlockableName } from "#app/system/unlockables"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; export class UnlockPhase extends Phase { private unlockable: Unlockables; - constructor(scene: BattleScene, unlockable: Unlockables) { - super(scene); + constructor(unlockable: Unlockables) { + super(); this.unlockable = unlockable; } start(): void { - this.scene.time.delayedCall(2000, () => { - this.scene.gameData.unlocks[this.unlockable] = true; + globalScene.time.delayedCall(2000, () => { + globalScene.gameData.unlocks[this.unlockable] = true; // Sound loaded into game as is - this.scene.playSound("level_up_fanfare"); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { - this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true)); + globalScene.playSound("level_up_fanfare"); + globalScene.ui.setMode(Mode.MESSAGE); + globalScene.ui.showText(i18next.t("battle:unlockedSomething", { unlockedThing: getUnlockableName(this.unlockable) }), null, () => { + globalScene.time.delayedCall(1500, () => globalScene.arenaBg.setVisible(true)); this.end(); }, null, true, 1500); }); diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 62479241a6c..8918b664a36 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -1,6 +1,7 @@ -import BattleScene from "#app/battle-scene"; -import { BattlerIndex, BattleType, ClassicFixedBossWaves } from "#app/battle"; -import { CustomModifierSettings, modifierTypes } from "#app/modifier/modifier-type"; +import type { BattlerIndex } from "#app/battle"; +import { BattleType, ClassicFixedBossWaves } from "#app/battle"; +import type { CustomModifierSettings } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { BattleEndPhase } from "./battle-end-phase"; import { NewBattlePhase } from "./new-battle-phase"; import { PokemonPhase } from "./pokemon-phase"; @@ -11,13 +12,14 @@ import { ModifierRewardPhase } from "./modifier-reward-phase"; import { SelectModifierPhase } from "./select-modifier-phase"; import { TrainerVictoryPhase } from "./trainer-victory-phase"; import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { globalScene } from "#app/global-scene"; export class VictoryPhase extends PokemonPhase { /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ isExpOnly: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex | integer, isExpOnly: boolean = false) { - super(scene, battlerIndex); + constructor(battlerIndex: BattlerIndex | integer, isExpOnly: boolean = false) { + super(battlerIndex); this.isExpOnly = isExpOnly; } @@ -25,61 +27,61 @@ export class VictoryPhase extends PokemonPhase { start() { super.start(); - const isMysteryEncounter = this.scene.currentBattle.isBattleMysteryEncounter(); + const isMysteryEncounter = globalScene.currentBattle.isBattleMysteryEncounter(); // update Pokemon defeated count except for MEs that disable it - if (!isMysteryEncounter || !this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { - this.scene.gameData.gameStats.pokemonDefeated++; + if (!isMysteryEncounter || !globalScene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { + globalScene.gameData.gameStats.pokemonDefeated++; } const expValue = this.getPokemon().getExpValue(); - this.scene.applyPartyExp(expValue, true); + globalScene.applyPartyExp(expValue, true); if (isMysteryEncounter) { - handleMysteryEncounterVictory(this.scene, false, this.isExpOnly); + handleMysteryEncounterVictory(false, this.isExpOnly); return this.end(); } - if (!this.scene.getEnemyParty().find(p => this.scene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) { - this.scene.pushPhase(new BattleEndPhase(this.scene, true)); - if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.pushPhase(new TrainerVictoryPhase(this.scene)); + if (!globalScene.getEnemyParty().find(p => globalScene.currentBattle.battleType === BattleType.WILD ? p.isOnField() : !p?.isFainted(true))) { + globalScene.pushPhase(new BattleEndPhase(true)); + if (globalScene.currentBattle.battleType === BattleType.TRAINER) { + globalScene.pushPhase(new TrainerVictoryPhase()); } - if (this.scene.gameMode.isEndless || !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new EggLapsePhase(this.scene)); - if (this.scene.gameMode.isClassic && this.scene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_BOSS_2) { + if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { + globalScene.pushPhase(new EggLapsePhase()); + if (globalScene.gameMode.isClassic && globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_BOSS_2) { // Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.LOCK_CAPSULE)); + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE)); } - if (this.scene.currentBattle.waveIndex % 10) { - this.scene.pushPhase(new SelectModifierPhase(this.scene, undefined, undefined, this.getFixedBattleCustomModifiers())); - } else if (this.scene.gameMode.isDaily) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); - if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + if (globalScene.currentBattle.waveIndex % 10) { + globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers())); + } else if (globalScene.gameMode.isDaily) { + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_CHARM)); + if (globalScene.currentBattle.waveIndex > 10 && !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); } } else { - const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; - if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); + const superExpWave = !globalScene.gameMode.isEndless ? (globalScene.offsetGym ? 0 : 20) : 10; + if (globalScene.gameMode.isEndless && globalScene.currentBattle.waveIndex === 10) { + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.EXP_SHARE)); } - if (this.scene.currentBattle.waveIndex <= 750 && (this.scene.currentBattle.waveIndex <= 500 || (this.scene.currentBattle.waveIndex % 30) === superExpWave)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, (this.scene.currentBattle.waveIndex % 30) !== superExpWave || this.scene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); + if (globalScene.currentBattle.waveIndex <= 750 && (globalScene.currentBattle.waveIndex <= 500 || (globalScene.currentBattle.waveIndex % 30) === superExpWave)) { + globalScene.pushPhase(new ModifierRewardPhase((globalScene.currentBattle.waveIndex % 30) !== superExpWave || globalScene.currentBattle.waveIndex > 250 ? modifierTypes.EXP_CHARM : modifierTypes.SUPER_EXP_CHARM)); } - if (this.scene.currentBattle.waveIndex <= 150 && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); + if (globalScene.currentBattle.waveIndex <= 150 && !(globalScene.currentBattle.waveIndex % 50)) { + globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.GOLDEN_POKEBALL)); } - if (this.scene.gameMode.isEndless && !(this.scene.currentBattle.waveIndex % 50)) { - this.scene.pushPhase(new ModifierRewardPhase(this.scene, !(this.scene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); - this.scene.pushPhase(new AddEnemyBuffModifierPhase(this.scene)); + if (globalScene.gameMode.isEndless && !(globalScene.currentBattle.waveIndex % 50)) { + globalScene.pushPhase(new ModifierRewardPhase(!(globalScene.currentBattle.waveIndex % 250) ? modifierTypes.VOUCHER_PREMIUM : modifierTypes.VOUCHER_PLUS)); + globalScene.pushPhase(new AddEnemyBuffModifierPhase()); } } - this.scene.pushPhase(new NewBattlePhase(this.scene)); + globalScene.pushPhase(new NewBattlePhase()); } else { - this.scene.currentBattle.battleType = BattleType.CLEAR; - this.scene.score += this.scene.gameMode.getClearScoreBonus(); - this.scene.updateScoreText(); - this.scene.pushPhase(new GameOverPhase(this.scene, true)); + globalScene.currentBattle.battleType = BattleType.CLEAR; + globalScene.score += globalScene.gameMode.getClearScoreBonus(); + globalScene.updateScoreText(); + globalScene.pushPhase(new GameOverPhase(true)); } } @@ -91,8 +93,8 @@ export class VictoryPhase extends PokemonPhase { * will pass those settings to the upcoming {@linkcode SelectModifierPhase}`. */ getFixedBattleCustomModifiers(): CustomModifierSettings | undefined { - const gameMode = this.scene.gameMode; - const waveIndex = this.scene.currentBattle.waveIndex; + const gameMode = globalScene.gameMode; + const waveIndex = globalScene.currentBattle.waveIndex; if (gameMode.isFixedBattle(waveIndex)) { return gameMode.getFixedBattle(waveIndex).customModifierRewardSettings; } diff --git a/src/phases/weather-effect-phase.ts b/src/phases/weather-effect-phase.ts index 442bafa0ca7..aa09f8a850d 100644 --- a/src/phases/weather-effect-phase.ts +++ b/src/phases/weather-effect-phase.ts @@ -1,24 +1,26 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability"; import { CommonAnim } from "#app/data/battle-anims"; -import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather"; +import type { Weather } from "#app/data/weather"; +import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { WeatherType } from "#app/enums/weather-type"; -import Pokemon, { HitResult } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { HitResult } from "#app/field/pokemon"; import * as Utils from "#app/utils"; import { CommonAnimPhase } from "./common-anim-phase"; export class WeatherEffectPhase extends CommonAnimPhase { public weather: Weather | null; - constructor(scene: BattleScene) { - super(scene, undefined, undefined, CommonAnim.SUNNY + ((scene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); - this.weather = scene?.arena?.weather; + constructor() { + super(undefined, undefined, CommonAnim.SUNNY + ((globalScene?.arena?.weather?.weatherType || WeatherType.NONE) - 1)); + this.weather = globalScene?.arena?.weather; } start() { // Update weather state with any changes that occurred during the turn - this.weather = this.scene?.arena?.weather; + this.weather = globalScene?.arena?.weather; if (!this.weather) { this.end(); @@ -46,7 +48,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { const damage = Utils.toDmgValue(pokemon.getMaxHp() / 16); - this.scene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? + globalScene.queueMessage(getWeatherDamageMessage(this.weather?.weatherType!, pokemon)!); // TODO: are those bangs correct? pokemon.damageAndUpdate(damage, HitResult.EFFECTIVE, false, false, true); }; @@ -59,7 +61,7 @@ export class WeatherEffectPhase extends CommonAnimPhase { } } - this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { + globalScene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => { this.executeForAll((pokemon: Pokemon) => { if (!pokemon.switchOutStatus) { applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather); diff --git a/src/pipelines/field-sprite.ts b/src/pipelines/field-sprite.ts index c370f5586e5..f81845073c1 100644 --- a/src/pipelines/field-sprite.ts +++ b/src/pipelines/field-sprite.ts @@ -1,4 +1,4 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { TerrainType, getTerrainColor } from "../data/terrain"; import * as Utils from "../utils"; @@ -227,22 +227,21 @@ export default class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines super.onBind(); const sprite = gameObject as Phaser.GameObjects.Sprite | Phaser.GameObjects.NineSlice; - const scene = sprite.scene as BattleScene; const data = sprite.pipelineData; const ignoreTimeTint = data["ignoreTimeTint"] as boolean; const terrainColorRatio = data["terrainColorRatio"] as number || 0; - const time = scene.currentBattle?.waveIndex - ? ((scene.currentBattle.waveIndex + scene.waveCycleOffset) % 40) / 40 // ((new Date().getSeconds() * 1000 + new Date().getMilliseconds()) % 10000) / 10000 + const time = globalScene.currentBattle?.waveIndex + ? ((globalScene.currentBattle.waveIndex + globalScene.waveCycleOffset) % 40) / 40 // ((new Date().getSeconds() * 1000 + new Date().getMilliseconds()) % 10000) / 10000 : Utils.getCurrentTime(); this.set1f("time", time); this.set1i("ignoreTimeTint", ignoreTimeTint ? 1 : 0); - this.set1i("isOutside", scene.arena.isOutside() ? 1 : 0); - this.set3fv("dayTint", scene.arena.getDayTint().map(c => c / 255)); - this.set3fv("duskTint", scene.arena.getDuskTint().map(c => c / 255)); - this.set3fv("nightTint", scene.arena.getNightTint().map(c => c / 255)); - this.set3fv("terrainColor", getTerrainColor(scene.arena.terrain?.terrainType || TerrainType.NONE).map(c => c / 255)); + this.set1i("isOutside", globalScene.arena.isOutside() ? 1 : 0); + this.set3fv("dayTint", globalScene.arena.getDayTint().map(c => c / 255)); + this.set3fv("duskTint", globalScene.arena.getDuskTint().map(c => c / 255)); + this.set3fv("nightTint", globalScene.arena.getNightTint().map(c => c / 255)); + this.set3fv("terrainColor", getTerrainColor(globalScene.arena.terrain?.terrainType || TerrainType.NONE).map(c => c / 255)); this.set1f("terrainColorRatio", terrainColorRatio); } diff --git a/src/pipelines/invert.ts b/src/pipelines/invert.ts index 5d0161e8afa..9c30f2b5cee 100644 --- a/src/pipelines/invert.ts +++ b/src/pipelines/invert.ts @@ -1,4 +1,4 @@ -import { Game } from "phaser"; +import type { Game } from "phaser"; const fragShader = ` precision mediump float; diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index 88d6ce2d387..1fc3b771897 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -1,10 +1,10 @@ -import BattleScene from "../battle-scene"; import { variantColorCache } from "#app/data/variant"; -import Pokemon from "../field/pokemon"; -import Trainer from "../field/trainer"; +import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; +import Pokemon from "#app/field/pokemon"; +import Trainer from "#app/field/trainer"; +import { globalScene } from "#app/global-scene"; +import * as Utils from "#app/utils"; import FieldSpritePipeline from "./field-sprite"; -import * as Utils from "../utils"; -import MysteryEncounterIntroVisuals from "../field/mystery-encounter-intro"; const spriteFragShader = ` #ifdef GL_FRAGMENT_PRECISION_HIGH @@ -384,7 +384,7 @@ export default class SpritePipeline extends FieldSpritePipeline { this.set4fv("tone", tone); this.bindTexture(this.game.textures.get("tera").source[0].glTexture!, 1); // TODO: is this bang correct? - if ((gameObject.scene as BattleScene).fusionPaletteSwaps) { + if (globalScene.fusionPaletteSwaps) { const spriteColors = ((ignoreOverride && data["spriteColorsBase"]) || data["spriteColors"] || []) as number[][]; const fusionSpriteColors = ((ignoreOverride && data["fusionSpriteColorsBase"]) || data["fusionSpriteColors"] || []) as number[][]; diff --git a/src/system/achv.ts b/src/system/achv.ts index a98e396264d..1f5662dbdbe 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -1,14 +1,15 @@ -import { Modifier } from "typescript"; -import BattleScene from "../battle-scene"; +import type { Modifier } from "typescript"; import { TurnHeldItemTransferModifier } from "../modifier/modifier"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import i18next from "i18next"; import * as Utils from "../utils"; import { PlayerGender } from "#enums/player-gender"; -import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; -import { ConditionFn } from "#app/@types/common"; +import type { Challenge } from "#app/data/challenge"; +import { FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; +import type { ConditionFn } from "#app/@types/common"; import { Stat, getShortenedStatKey } from "#app/enums/stat"; import { Challenges } from "#app/enums/challenges"; +import { globalScene } from "#app/global-scene"; export enum AchvTier { COMMON, @@ -66,8 +67,8 @@ export class Achv { return this; } - validate(scene: BattleScene, args?: any[]): boolean { - return !this.conditionFunc || this.conditionFunc(scene, args); + validate(args?: any[]): boolean { + return !this.conditionFunc || this.conditionFunc(args); } getTier(): AchvTier { @@ -91,7 +92,7 @@ export class MoneyAchv extends Achv { moneyAmount: integer; constructor(localizationKey: string, name: string, moneyAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (scene: BattleScene, _args: any[]) => scene.money >= this.moneyAmount); + super(localizationKey, name, "", iconImage, score, (_args: any[]) => globalScene.money >= this.moneyAmount); this.moneyAmount = moneyAmount; } } @@ -100,7 +101,7 @@ export class RibbonAchv extends Achv { ribbonAmount: integer; constructor(localizationKey: string, name: string, ribbonAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (scene: BattleScene, _args: any[]) => scene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount); + super(localizationKey, name, "", iconImage, score, (_args: any[]) => globalScene.gameData.gameStats.ribbonsOwned >= this.ribbonAmount); this.ribbonAmount = ribbonAmount; } } @@ -109,7 +110,7 @@ export class DamageAchv extends Achv { damageAmount: integer; constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount); + super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount); this.damageAmount = damageAmount; } } @@ -118,7 +119,7 @@ export class HealAchv extends Achv { healAmount: integer; constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount); + super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount); this.healAmount = healAmount; } } @@ -127,20 +128,20 @@ export class LevelAchv extends Achv { level: integer; constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level); + super(localizationKey, name, "", iconImage, score, (args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level); this.level = level; } } export class ModifierAchv extends Achv { constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, modifierFunc: (modifier: Modifier) => boolean) { - super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => modifierFunc((args[0] as Modifier))); + super(localizationKey, name, description, iconImage, score, (args: any[]) => modifierFunc((args[0] as Modifier))); } } export class ChallengeAchv extends Achv { - constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge, scene: BattleScene) => boolean) { - super(localizationKey, name, description, iconImage, score, (_scene: BattleScene, args: any[]) => challengeFunc(args[0] as Challenge, _scene)); + constructor(localizationKey: string, name: string, description: string, iconImage: string, score: integer, challengeFunc: (challenge: Challenge) => boolean) { + super(localizationKey, name, description, iconImage, score, (args: any[]) => challengeFunc(args[0] as Challenge)); } } @@ -152,7 +153,7 @@ export class ChallengeAchv extends Achv { */ export function getAchievementDescription(localizationKey: string): string { // We need to get the player gender from the game data to add the correct prefix to the achievement name - const genderIndex = this?.scene?.gameData?.gender ?? PlayerGender.MALE; //TODO: why is `this` being used here!? We are not inside a scope (copied from original) + const genderIndex = globalScene?.gameData?.gender ?? PlayerGender.MALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); switch (localizationKey) { @@ -327,36 +328,36 @@ export const achvs = { HATCH_SHINY: new Achv("HATCH_SHINY", "", "HATCH_SHINY.description", "golden_egg", 100).setSecret(), HIDDEN_ABILITY: new Achv("HIDDEN_ABILITY", "", "HIDDEN_ABILITY.description", "ability_charm", 75), PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100), - CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150, c => c.gameData.gameStats.sessionsWon === 0), - UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, c => c.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)), - MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 1 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 2 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 3 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 4 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 5 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 6 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 7 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 8 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c, scene) => c instanceof SingleGenerationChallenge && c.value === 9 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 1 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 2 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 3 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 4 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 5 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 6 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 7 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 8 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 9 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 10 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 11 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 12 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 13 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 14 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 15 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 16 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 17 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150, (_) => globalScene.gameData.gameStats.sessionsWon === 0), + UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, (_) => globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)), + MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), + FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts index ba37de0ed0e..98ab611ff3c 100644 --- a/src/system/arena-data.ts +++ b/src/system/arena-data.ts @@ -1,6 +1,7 @@ import { Arena } from "../field/arena"; -import { ArenaTag, loadArenaTag } from "../data/arena-tag"; -import { Biome } from "#enums/biome"; +import type { ArenaTag } from "../data/arena-tag"; +import { loadArenaTag } from "../data/arena-tag"; +import type { Biome } from "#enums/biome"; import { Weather } from "../data/weather"; import { Terrain } from "#app/data/terrain"; diff --git a/src/system/challenge-data.ts b/src/system/challenge-data.ts index 394d63867be..76a3392861d 100644 --- a/src/system/challenge-data.ts +++ b/src/system/challenge-data.ts @@ -1,4 +1,5 @@ -import { Challenge, copyChallenge } from "#app/data/challenge"; +import type { Challenge } from "#app/data/challenge"; +import { copyChallenge } from "#app/data/challenge"; export default class ChallengeData { public id: integer; diff --git a/src/system/egg-data.ts b/src/system/egg-data.ts index aa06316a61f..7b75f387c63 100644 --- a/src/system/egg-data.ts +++ b/src/system/egg-data.ts @@ -1,8 +1,8 @@ -import { EggTier } from "#enums/egg-type"; -import { Species } from "#enums/species"; -import { VariantTier } from "#enums/variant-tier"; +import type { EggTier } from "#enums/egg-type"; +import type { Species } from "#enums/species"; +import type { VariantTier } from "#enums/variant-tier"; import { EGG_SEED, Egg } from "../data/egg"; -import { EggSourceType } from "#app/enums/egg-source-types"; +import type { EggSourceType } from "#app/enums/egg-source-types"; export default class EggData { public id: integer; diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 3e3a6ce8f8b..11b98d3fee6 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1,8 +1,12 @@ import i18next from "i18next"; -import BattleScene, { bypassLogin, PokeballCounts } from "#app/battle-scene"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { PokeballCounts } from "#app/battle-scene"; +import { bypassLogin } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import PokemonSpecies, { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import * as Utils from "#app/utils"; import Overrides from "#app/overrides"; @@ -17,7 +21,7 @@ import { trainerConfigs } from "#app/data/trainer-config"; import { resetSettings, setSetting, SettingKeys } from "#app/system/settings/settings"; import { achvs } from "#app/system/achv"; import EggData from "#app/system/egg-data"; -import { Egg } from "#app/data/egg"; +import type { Egg } from "#app/data/egg"; import { vouchers, VoucherType } from "#app/system/voucher"; import { AES, enc } from "crypto-js"; import { Mode } from "#app/ui/ui"; @@ -28,16 +32,17 @@ import { Tutorial } from "#app/tutorial"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { allMoves } from "#app/data/move"; import { TrainerVariant } from "#app/field/trainer"; -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; -import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import type { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { setSettingKeyboard } from "#app/system/settings/settings-keyboard"; import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import * as Modifier from "#app/modifier/modifier"; import { StatusEffect } from "#enums/status-effect"; import ChallengeData from "#app/system/challenge-data"; import { Device } from "#enums/devices"; import { GameDataType } from "#enums/game-data-type"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; @@ -47,7 +52,7 @@ import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { ArenaTrapTag } from "#app/data/arena-tag"; @@ -298,8 +303,6 @@ const systemShortKeys = { }; export class GameData { - private scene: BattleScene; - public trainerId: integer; public secretId: integer; @@ -323,8 +326,7 @@ export class GameData { public eggPity: integer[]; public unlockPity: integer[]; - constructor(scene: BattleScene) { - this.scene = scene; + constructor() { this.loadSettings(); this.loadGamepadSettings(); this.loadMappingConfigs(); @@ -367,7 +369,7 @@ export class GameData { voucherUnlocks: this.voucherUnlocks, voucherCounts: this.voucherCounts, eggs: this.eggs.map(e => new EggData(e)), - gameVersion: this.scene.game.config.gameVersion, + gameVersion: globalScene.game.config.gameVersion, timestamp: new Date().getTime(), eggPity: this.eggPity.slice(0), unlockPity: this.unlockPity.slice(0) @@ -388,7 +390,7 @@ export class GameData { public saveSystem(): Promise { return new Promise(resolve => { - this.scene.ui.savingIcon.show(); + globalScene.ui.savingIcon.show(); const data = this.getSystemSaveData(); const maxIntAttrValue = 0x80000000; @@ -399,11 +401,11 @@ export class GameData { if (!bypassLogin) { pokerogueApi.savedata.system.update({ clientSessionId }, systemData) .then(error => { - this.scene.ui.savingIcon.hide(); + globalScene.ui.savingIcon.hide(); if (error) { if (error.startsWith("session out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new ReloadSessionPhase()); } console.error(error); return resolve(false); @@ -411,7 +413,7 @@ export class GameData { resolve(true); }); } else { - this.scene.ui.savingIcon.hide(); + globalScene.ui.savingIcon.hide(); resolve(true); } @@ -431,10 +433,10 @@ export class GameData { .then(saveDataOrErr => { if (!saveDataOrErr || saveDataOrErr.length === 0 || saveDataOrErr[0] !== "{") { if (saveDataOrErr?.startsWith("sql: no rows in result set")) { - this.scene.queueMessage("Save data could not be found. If this is a new account, you can safely ignore this message.", null, true); + globalScene.queueMessage("Save data could not be found. If this is a new account, you can safely ignore this message.", null, true); return resolve(true); } else if (saveDataOrErr?.includes("Too many connections")) { - this.scene.queueMessage("Too many people are trying to connect and the server is overloaded. Please try again later.", null, true); + globalScene.queueMessage("Too many people are trying to connect and the server is overloaded. Please try again later.", null, true); return resolve(false); } console.error(saveDataOrErr); @@ -574,7 +576,7 @@ export class GameData { * Retrieves current run history data, organized by time stamp. * At the moment, only retrievable from locale cache */ - async getRunHistoryData(scene: BattleScene): Promise { + async getRunHistoryData(): Promise { if (!Utils.isLocal) { /** * Networking Code DO NOT DELETE! @@ -623,13 +625,12 @@ export class GameData { /** * Saves a new entry to Run History - * @param scene: BattleScene object * @param runEntry: most recent SessionSaveData of the run * @param isVictory: result of the run * Arbitrary limit of 25 runs per player - Will delete runs, starting with the oldest one, if needed */ - async saveRunHistory(scene: BattleScene, runEntry : SessionSaveData, isVictory: boolean): Promise { - const runHistoryData = await this.getRunHistoryData(scene); + async saveRunHistory(runEntry : SessionSaveData, isVictory: boolean): Promise { + const runHistoryData = await this.getRunHistoryData(); // runHistoryData should always return run history or {} empty object let timestamps = Object.keys(runHistoryData).map(Number); @@ -707,8 +708,8 @@ export class GameData { const systemData = await pokerogueApi.savedata.system.verify({ clientSessionId }); if (systemData) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene, JSON.stringify(systemData))); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new ReloadSessionPhase(JSON.stringify(systemData))); this.clearLocalData(); return false; } @@ -738,10 +739,10 @@ export class GameData { settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? } - setSetting(this.scene, setting, valueIndex); + setSetting(setting, valueIndex); settings[setting] = valueIndex; - settings["gameVersion"] = this.scene.game.config.gameVersion; + settings["gameVersion"] = globalScene.game.config.gameVersion; localStorage.setItem("settings", JSON.stringify(settings)); @@ -787,7 +788,7 @@ export class GameData { const mappingConfigs = JSON.parse(localStorage.getItem("mappingConfigs")!); // Parse the existing 'mappingConfigs' from localStorage // TODO: is this bang correct? for (const key of Object.keys(mappingConfigs)) {// Iterate over the keys of the mapping configurations - this.scene.inputController.injectConfig(key, mappingConfigs[key]); + globalScene.inputController.injectConfig(key, mappingConfigs[key]); } // Inject each configuration into the input controller for the corresponding key return true; // Return true to indicate the operation was successful @@ -798,7 +799,7 @@ export class GameData { return false; } // If 'mappingConfigs' does not exist, return false localStorage.removeItem("mappingConfigs"); - this.scene.inputController.resetConfigs(); + globalScene.inputController.resetConfigs(); return true; // TODO: is `true` the correct return value? } @@ -823,9 +824,9 @@ export class GameData { } if (device === Device.GAMEPAD) { - setSettingGamepad(this.scene, setting as SettingGamepad, valueIndex); // Set the gamepad setting in the current scene + setSettingGamepad(setting as SettingGamepad, valueIndex); } else if (device === Device.KEYBOARD) { - setSettingKeyboard(this.scene, setting as SettingKeyboard, valueIndex); // Set the keyboard setting in the current scene + setSettingKeyboard(setting as SettingKeyboard, valueIndex); } Object.keys(settingDefaults).forEach(s => { // Iterate over the default gamepad settings @@ -844,7 +845,7 @@ export class GameData { * @returns true if succesful, false if not */ private loadSettings(): boolean { - resetSettings(this.scene); + resetSettings(); if (!localStorage.hasOwnProperty("settings")) { return false; @@ -855,14 +856,14 @@ export class GameData { applySettingsVersionMigration(settings); for (const setting of Object.keys(settings)) { - setSetting(this.scene, setting, settings[setting]); + setSetting(setting, settings[setting]); } return true; // TODO: is `true` the correct return value? } private loadGamepadSettings(): boolean { - Object.values(SettingGamepad).map(setting => setting as SettingGamepad).forEach(setting => setSettingGamepad(this.scene, setting, settingGamepadDefaults[setting])); + Object.values(SettingGamepad).map(setting => setting as SettingGamepad).forEach(setting => setSettingGamepad(setting, settingGamepadDefaults[setting])); if (!localStorage.hasOwnProperty("settingsGamepad")) { return false; @@ -870,7 +871,7 @@ export class GameData { const settingsGamepad = JSON.parse(localStorage.getItem("settingsGamepad")!); // TODO: is this bang correct? for (const setting of Object.keys(settingsGamepad)) { - setSettingGamepad(this.scene, setting as SettingGamepad, settingsGamepad[setting]); + setSettingGamepad(setting as SettingGamepad, settingsGamepad[setting]); } return true; // TODO: is `true` the correct return value? @@ -943,27 +944,27 @@ export class GameData { return ret; } - public getSessionSaveData(scene: BattleScene): SessionSaveData { + public getSessionSaveData(): SessionSaveData { return { - seed: scene.seed, - playTime: scene.sessionPlayTime, - gameMode: scene.gameMode.modeId, - party: scene.getPlayerParty().map(p => new PokemonData(p)), - enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), - modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), - enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), - arena: new ArenaData(scene.arena), - pokeballCounts: scene.pokeballCounts, - money: Math.floor(scene.money), - score: scene.score, - waveIndex: scene.currentBattle.waveIndex, - battleType: scene.currentBattle.battleType, - trainer: scene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, - gameVersion: scene.game.config.gameVersion, + seed: globalScene.seed, + playTime: globalScene.sessionPlayTime, + gameMode: globalScene.gameMode.modeId, + party: globalScene.getPlayerParty().map(p => new PokemonData(p)), + enemyParty: globalScene.getEnemyParty().map(p => new PokemonData(p)), + modifiers: globalScene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), + enemyModifiers: globalScene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + arena: new ArenaData(globalScene.arena), + pokeballCounts: globalScene.pokeballCounts, + money: Math.floor(globalScene.money), + score: globalScene.score, + waveIndex: globalScene.currentBattle.waveIndex, + battleType: globalScene.currentBattle.battleType, + trainer: globalScene.currentBattle.battleType === BattleType.TRAINER ? new TrainerData(globalScene.currentBattle.trainer) : null, + gameVersion: globalScene.game.config.gameVersion, timestamp: new Date().getTime(), - challenges: scene.gameMode.challenges.map(c => new ChallengeData(c)), - mysteryEncounterType: scene.currentBattle.mysteryEncounter?.encounterType ?? -1, - mysteryEncounterSaveData: scene.mysteryEncounterSaveData + challenges: globalScene.gameMode.challenges.map(c => new ChallengeData(c)), + mysteryEncounterType: globalScene.currentBattle.mysteryEncounter?.encounterType ?? -1, + mysteryEncounterSaveData: globalScene.mysteryEncounterSaveData } as SessionSaveData; } @@ -1005,68 +1006,68 @@ export class GameData { }); } - loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise { + loadSession(slotId: integer, sessionData?: SessionSaveData): Promise { return new Promise(async (resolve, reject) => { try { const initSessionFromData = async (sessionData: SessionSaveData) => { console.debug(sessionData); - scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC); + globalScene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC); if (sessionData.challenges) { - scene.gameMode.challenges = sessionData.challenges.map(c => c.toChallenge()); + globalScene.gameMode.challenges = sessionData.challenges.map(c => c.toChallenge()); } - scene.setSeed(sessionData.seed || scene.game.config.seed[0]); - scene.resetSeed(); + globalScene.setSeed(sessionData.seed || globalScene.game.config.seed[0]); + globalScene.resetSeed(); - console.log("Seed:", scene.seed); + console.log("Seed:", globalScene.seed); - scene.sessionPlayTime = sessionData.playTime || 0; - scene.lastSavePlayTime = 0; + globalScene.sessionPlayTime = sessionData.playTime || 0; + globalScene.lastSavePlayTime = 0; const loadPokemonAssets: Promise[] = []; - const party = scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); party.splice(0, party.length); for (const p of sessionData.party) { - const pokemon = p.toPokemon(scene) as PlayerPokemon; + const pokemon = p.toPokemon() as PlayerPokemon; pokemon.setVisible(false); loadPokemonAssets.push(pokemon.loadAssets()); party.push(pokemon); } - Object.keys(scene.pokeballCounts).forEach((key: string) => { - scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0; + Object.keys(globalScene.pokeballCounts).forEach((key: string) => { + globalScene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0; }); if (Overrides.POKEBALL_OVERRIDE.active) { - scene.pokeballCounts = Overrides.POKEBALL_OVERRIDE.pokeballs; + globalScene.pokeballCounts = Overrides.POKEBALL_OVERRIDE.pokeballs; } - scene.money = Math.floor(sessionData.money || 0); - scene.updateMoneyText(); + globalScene.money = Math.floor(sessionData.money || 0); + globalScene.updateMoneyText(); - if (scene.money > this.gameStats.highestMoney) { - this.gameStats.highestMoney = scene.money; + if (globalScene.money > this.gameStats.highestMoney) { + this.gameStats.highestMoney = globalScene.money; } - scene.score = sessionData.score; - scene.updateScoreText(); + globalScene.score = sessionData.score; + globalScene.updateScoreText(); - scene.mysteryEncounterSaveData = new MysteryEncounterSaveData(sessionData.mysteryEncounterSaveData); + globalScene.mysteryEncounterSaveData = new MysteryEncounterSaveData(sessionData.mysteryEncounterSaveData); - scene.newArena(sessionData.arena.biome); + globalScene.newArena(sessionData.arena.biome); const battleType = sessionData.battleType || 0; const trainerConfig = sessionData.trainer ? trainerConfigs[sessionData.trainer.trainerType] : null; const mysteryEncounterType = sessionData.mysteryEncounterType !== -1 ? sessionData.mysteryEncounterType : undefined; - const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1, mysteryEncounterType)!; // TODO: is this bang correct? + const battle = globalScene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfig?.doubleOnly || sessionData.trainer?.variant === TrainerVariant.DOUBLE : sessionData.enemyParty.length > 1, mysteryEncounterType)!; // TODO: is this bang correct? battle.enemyLevels = sessionData.enemyParty.map(p => p.level); - scene.arena.init(); + globalScene.arena.init(); sessionData.enemyParty.forEach((enemyData, e) => { - const enemyPokemon = enemyData.toPokemon(scene, battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon; + const enemyPokemon = enemyData.toPokemon(battleType, e, sessionData.trainer?.variant === TrainerVariant.DOUBLE) as EnemyPokemon; battle.enemyParty[e] = enemyPokemon; if (battleType === BattleType.WILD) { battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); @@ -1075,41 +1076,41 @@ export class GameData { loadPokemonAssets.push(enemyPokemon.loadAssets()); }); - scene.arena.weather = sessionData.arena.weather; - scene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(WeatherType.NONE, scene.arena.weather?.weatherType!, scene.arena.weather?.turnsLeft!)); // TODO: is this bang correct? + globalScene.arena.weather = sessionData.arena.weather; + globalScene.arena.eventTarget.dispatchEvent(new WeatherChangedEvent(WeatherType.NONE, globalScene.arena.weather?.weatherType!, globalScene.arena.weather?.turnsLeft!)); // TODO: is this bang correct? - scene.arena.terrain = sessionData.arena.terrain; - scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? + globalScene.arena.terrain = sessionData.arena.terrain; + globalScene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, globalScene.arena.terrain?.terrainType!, globalScene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? - scene.arena.tags = sessionData.arena.tags; - if (scene.arena.tags) { - for (const tag of scene.arena.tags) { + globalScene.arena.tags = sessionData.arena.tags; + if (globalScene.arena.tags) { + for (const tag of globalScene.arena.tags) { if (tag instanceof ArenaTrapTag) { const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag; - scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); + globalScene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); } else { - scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount)); + globalScene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount)); } } } for (const modifierData of sessionData.modifiers) { - const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]); + const modifier = modifierData.toModifier(Modifier[modifierData.className]); if (modifier) { - scene.addModifier(modifier, true); + globalScene.addModifier(modifier, true); } } - scene.updateModifiers(true); + globalScene.updateModifiers(true); for (const enemyModifierData of sessionData.enemyModifiers) { - const modifier = enemyModifierData.toModifier(scene, Modifier[enemyModifierData.className]); + const modifier = enemyModifierData.toModifier(Modifier[enemyModifierData.className]); if (modifier) { - scene.addEnemyModifier(modifier, true); + globalScene.addEnemyModifier(modifier, true); } } - scene.updateModifiers(false); + globalScene.updateModifiers(false); Promise.all(loadPokemonAssets).then(() => resolve(true)); }; @@ -1150,8 +1151,8 @@ export class GameData { pokerogueApi.savedata.session.delete({ slot: slotId, clientSessionId }).then(error => { if (error) { if (error.startsWith("session out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new ReloadSessionPhase()); } console.error(error); resolve(false); @@ -1172,9 +1173,9 @@ export class GameData { /* Defines a localStorage item 'daily' to check on clears, offline implementation of savedata/newclear API If a GameModes clear other than Daily is checked, newClear = true as usual If a Daily mode is cleared, checks if it was already cleared before, based on seed, and returns true only to new daily clear runs */ - offlineNewClear(scene: BattleScene): Promise { + offlineNewClear(): Promise { return new Promise(resolve => { - const sessionData = this.getSessionSaveData(scene); + const sessionData = this.getSessionSaveData(); const seed = sessionData.seed; let daily: string[] = []; @@ -1205,14 +1206,14 @@ export class GameData { * After session data is removed, attempt to update user info so the menu updates * To delete an unfinished run instead, use {@linkcode deleteSession} */ - async tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { + async tryClearSession(slotId: integer): Promise<[success: boolean, newClear: boolean]> { let result: [boolean, boolean] = [ false, false ]; if (bypassLogin) { localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); result = [ true, true ]; } else { - const sessionData = this.getSessionSaveData(scene); + const sessionData = this.getSessionSaveData(); const { trainerId } = this; const jsonResponse = await pokerogueApi.savedata.session.clear({ slot: slotId, trainerId, clientSessionId }, sessionData); @@ -1224,8 +1225,8 @@ export class GameData { localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); } else { if (jsonResponse && jsonResponse.error?.startsWith("session out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new ReloadSessionPhase()); } console.error(jsonResponse); @@ -1240,12 +1241,6 @@ export class GameData { parseSessionData(dataStr: string): SessionSaveData { const sessionData = JSON.parse(dataStr, (k: string, v: any) => { - /*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ]; - - if (versions[0] !== versions[1]) { - const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v))); - }*/ - if (k === "party" || k === "enemyParty") { const ret: PokemonData[] = []; if (v === null) { @@ -1310,16 +1305,18 @@ export class GameData { return sessionData; } - saveAll(scene: BattleScene, skipVerification: boolean = false, sync: boolean = false, useCachedSession: boolean = false, useCachedSystem: boolean = false): Promise { + saveAll(skipVerification: boolean = false, sync: boolean = false, useCachedSession: boolean = false, useCachedSystem: boolean = false): Promise { return new Promise(resolve => { Utils.executeIf(!skipVerification, updateUserInfo).then(success => { if (success !== null && !success) { return resolve(false); } if (sync) { - this.scene.ui.savingIcon.show(); + globalScene.ui.savingIcon.show(); } - const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser?.username}`)!, bypassLogin)) : this.getSessionSaveData(scene); // TODO: is this bang correct? + const sessionData = useCachedSession ? + this.parseSessionData(decrypt(localStorage.getItem(`sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`)!, bypassLogin)) // TODO: is this bang correct? + : this.getSessionSaveData(); const maxIntAttrValue = 0x80000000; const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin)) : this.getSystemSaveData(); // TODO: is this bang correct? @@ -1327,13 +1324,13 @@ export class GameData { const request = { system: systemData, session: sessionData, - sessionSlotId: scene.sessionSlotId, + sessionSlotId: globalScene.sessionSlotId, clientSessionId: clientSessionId }; localStorage.setItem(`data_${loggedInUser?.username}`, encrypt(JSON.stringify(systemData, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v), bypassLogin)); - localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser?.username}`, encrypt(JSON.stringify(sessionData), bypassLogin)); + localStorage.setItem(`sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`, encrypt(JSON.stringify(sessionData), bypassLogin)); console.debug("Session data saved"); @@ -1341,13 +1338,13 @@ export class GameData { pokerogueApi.savedata.updateAll(request) .then(error => { if (sync) { - this.scene.lastSavePlayTime = 0; - this.scene.ui.savingIcon.hide(); + globalScene.lastSavePlayTime = 0; + globalScene.ui.savingIcon.hide(); } if (error) { if (error.startsWith("session out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); + globalScene.clearPhaseQueue(); + globalScene.unshiftPhase(new ReloadSessionPhase()); } console.error(error); return resolve(false); @@ -1356,7 +1353,7 @@ export class GameData { }); } else { this.verify().then(success => { - this.scene.ui.savingIcon.hide(); + globalScene.ui.savingIcon.hide(); resolve(success); }); } @@ -1462,15 +1459,15 @@ export class GameData { console.error(ex); } - const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500)); + const displayError = (error: string) => globalScene.ui.showText(error, null, () => globalScene.ui.showText("", 0), Utils.fixedInt(1500)); dataName = dataName!; // tell TS compiler that dataName is defined! if (!valid) { - return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText("", 0), Utils.fixedInt(1500)); + return globalScene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => globalScene.ui.showText("", 0), Utils.fixedInt(1500)); } - this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => { - this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + globalScene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => { + globalScene.ui.setOverlayMode(Mode.CONFIRM, () => { localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin)); if (!bypassLogin && dataType < GameDataType.SETTINGS) { @@ -1498,8 +1495,8 @@ export class GameData { window.location = window.location; } }, () => { - this.scene.ui.revertMode(); - this.scene.ui.showText("", 0); + globalScene.ui.revertMode(); + globalScene.ui.showText("", 0); }, false, -98); }); }; @@ -1509,9 +1506,6 @@ export class GameData { } ); saveFile.click(); - /*(this.scene.plugins.get('rexfilechooserplugin') as FileChooserPlugin).open({ accept: '.prsv' }) - .then(result => { - });*/ } private initDexData(): void { @@ -1527,7 +1521,7 @@ export class GameData { const defaultStarterNatures: Nature[] = []; - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { const neutralNatures = [ Nature.HARDY, Nature.DOCILE, Nature.SERIOUS, Nature.BASHFUL, Nature.QUIRKY ]; for (let s = 0; s < defaultStarterSpecies.length; s++) { defaultStarterNatures.push(Utils.randSeedItem(neutralNatures)); @@ -1571,7 +1565,7 @@ export class GameData { setPokemonSeen(pokemon: Pokemon, incrementCount: boolean = true, trainer: boolean = false): void { // Some Mystery Encounters block updates to these stats - if (this.scene.currentBattle?.isBattleMysteryEncounter() && this.scene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { + if (globalScene.currentBattle?.isBattleMysteryEncounter() && globalScene.currentBattle.mysteryEncounter?.preventGameStatsUpdates) { return; } const dexEntry = this.dexData[pokemon.species.speciesId]; @@ -1604,7 +1598,7 @@ export class GameData { // If incrementCount === false (not a catch scenario), only update the pokemon's dex data if the Pokemon has already been marked as caught in dex // Prevents form changes, nature changes, etc. from unintentionally updating the dex data of a "rental" pokemon const speciesRootForm = pokemon.species.getRootSpeciesId(); - if (!incrementCount && !this.scene.gameData.dexData[speciesRootForm].caughtAttr) { + if (!incrementCount && !globalScene.gameData.dexData[speciesRootForm].caughtAttr) { return Promise.resolve(false); } else { return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg, showMessage); @@ -1677,7 +1671,7 @@ export class GameData { } } - if (!hasPrevolution && (!pokemon.scene.gameMode.isDaily || hasNewAttr || fromEgg)) { + if (!hasPrevolution && (!globalScene.gameMode.isDaily || hasNewAttr || fromEgg)) { this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * (1 << (pokemon.variant ?? 0)) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1)); } } @@ -1696,8 +1690,8 @@ export class GameData { resolve(true); return; } - this.scene.playSound("level_up_fanfare"); - this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(true), null, true); + globalScene.playSound("level_up_fanfare"); + globalScene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(true), null, true); } else { checkPrevolution(false); } @@ -1712,25 +1706,25 @@ export class GameData { } if (!this.starterData[speciesIdToIncrement].classicWinCount) { - this.scene.gameData.gameStats.ribbonsOwned++; + globalScene.gameData.gameStats.ribbonsOwned++; } - const ribbonsInStats: integer = this.scene.gameData.gameStats.ribbonsOwned; + const ribbonsInStats: integer = globalScene.gameData.gameStats.ribbonsOwned; if (ribbonsInStats >= 100) { - this.scene.validateAchv(achvs._100_RIBBONS); + globalScene.validateAchv(achvs._100_RIBBONS); } if (ribbonsInStats >= 75) { - this.scene.validateAchv(achvs._75_RIBBONS); + globalScene.validateAchv(achvs._75_RIBBONS); } if (ribbonsInStats >= 50) { - this.scene.validateAchv(achvs._50_RIBBONS); + globalScene.validateAchv(achvs._50_RIBBONS); } if (ribbonsInStats >= 25) { - this.scene.validateAchv(achvs._25_RIBBONS); + globalScene.validateAchv(achvs._25_RIBBONS); } if (ribbonsInStats >= 10) { - this.scene.validateAchv(achvs._10_RIBBONS); + globalScene.validateAchv(achvs._10_RIBBONS); } return ++this.starterData[speciesIdToIncrement].classicWinCount; @@ -1745,8 +1739,8 @@ export class GameData { addStarterCandy(species: PokemonSpecies, count: integer): void { // Only gain candies if the Pokemon has already been marked as caught in dex (ignore "rental" pokemon) const speciesRootForm = species.getRootSpeciesId(); - if (this.scene.gameData.dexData[speciesRootForm].caughtAttr) { - this.scene.candyBar.showStarterSpeciesCandy(species.speciesId, count); + if (globalScene.gameData.dexData[speciesRootForm].caughtAttr) { + globalScene.candyBar.showStarterSpeciesCandy(species.speciesId, count); this.starterData[species.speciesId].candyCount += count; } } @@ -1782,12 +1776,12 @@ export class GameData { resolve(true); return; } - this.scene.playSound("level_up_fanfare"); + globalScene.playSound("level_up_fanfare"); const moveName = allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name; let message = prependSpeciesToMessage ? species.getName() + " " : ""; message += eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }); - this.scene.ui.showText(message, null, () => resolve(true), null, true); + globalScene.ui.showText(message, null, () => resolve(true), null, true); }); } @@ -1820,7 +1814,7 @@ export class GameData { updateSpeciesDexIvs(speciesId: Species, ivs: integer[]): void { let dexEntry: DexEntry; do { - dexEntry = this.scene.gameData.dexData[speciesId]; + dexEntry = globalScene.gameData.dexData[speciesId]; const dexIvs = dexEntry.ivs; for (let i = 0; i < dexIvs.length; i++) { if (dexIvs[i] < ivs[i]) { @@ -1828,7 +1822,7 @@ export class GameData { } } if (dexIvs.filter(iv => iv === 31).length === 6) { - this.scene.validateAchv(achvs.PERFECT_IVS); + globalScene.validateAchv(achvs.PERFECT_IVS); } } while (pokemonPrevolutions.hasOwnProperty(speciesId) && (speciesId = pokemonPrevolutions[speciesId])); } @@ -1966,7 +1960,7 @@ export class GameData { } const cost = new Utils.NumberHolder(value); - applyChallenges(this.scene.gameMode, ChallengeType.STARTER_COST, speciesId, cost); + applyChallenges(globalScene.gameMode, ChallengeType.STARTER_COST, speciesId, cost); return cost.value; } diff --git a/src/system/game-speed.ts b/src/system/game-speed.ts index 861c3d4705d..4b34a04b5f8 100644 --- a/src/system/game-speed.ts +++ b/src/system/game-speed.ts @@ -1,7 +1,8 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import FadeIn from "phaser3-rex-plugins/plugins/audio/fade/FadeIn"; -import FadeOut from "phaser3-rex-plugins/plugins/audio/fade/FadeOut"; -import BattleScene from "../battle-scene"; +import type FadeIn from "phaser3-rex-plugins/plugins/audio/fade/FadeIn"; +import type FadeOut from "phaser3-rex-plugins/plugins/audio/fade/FadeOut"; +import type BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; type FadeIn = typeof FadeIn; @@ -98,7 +99,7 @@ export function initGameSpeed() { sound: Phaser.Sound.BaseSound, duration: number, destroy?: boolean - ) => originalFadeOut(scene, sound, transformValue(duration), destroy)) as FadeOut; + ) => originalFadeOut(globalScene, sound, transformValue(duration), destroy)) as FadeOut; const originalFadeIn = SoundFade.fadeIn; SoundFade.fadeIn = (( @@ -107,5 +108,5 @@ export function initGameSpeed() { duration: number, endVolume?: number, startVolume?: number - ) => originalFadeIn(scene, sound, transformValue(duration), endVolume, startVolume)) as FadeIn; + ) => originalFadeIn(globalScene, sound, transformValue(duration), endVolume, startVolume)) as FadeIn; } diff --git a/src/system/modifier-data.ts b/src/system/modifier-data.ts index c68f9ccb47d..d153236c93e 100644 --- a/src/system/modifier-data.ts +++ b/src/system/modifier-data.ts @@ -1,6 +1,7 @@ -import BattleScene from "../battle-scene"; -import { PersistentModifier } from "../modifier/modifier"; -import { GeneratedPersistentModifierType, ModifierType, ModifierTypeGenerator, getModifierTypeFuncById } from "../modifier/modifier-type"; +import { globalScene } from "#app/global-scene"; +import { PersistentModifier } from "#app/modifier/modifier"; +import type { GeneratedPersistentModifierType, ModifierType } from "#app/modifier/modifier-type"; +import { ModifierTypeGenerator, getModifierTypeFuncById } from "#app/modifier/modifier-type"; export default class ModifierData { public player: boolean; @@ -27,7 +28,7 @@ export default class ModifierData { this.className = sourceModifier ? sourceModifier.constructor.name : source.className; } - toModifier(scene: BattleScene, constructor: any): PersistentModifier | null { + toModifier(constructor: any): PersistentModifier | null { const typeFunc = getModifierTypeFuncById(this.typeId); if (!typeFunc) { return null; @@ -38,13 +39,13 @@ export default class ModifierData { type.id = this.typeId; if (type instanceof ModifierTypeGenerator) { - type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getPlayerParty() : scene.getEnemyField(), this.typePregenArgs); + type = (type as ModifierTypeGenerator).generateType(this.player ? globalScene.getPlayerParty() : globalScene.getEnemyField(), this.typePregenArgs); } const ret = Reflect.construct(constructor, ([ type ] as any[]).concat(this.args).concat(this.stackCount)) as PersistentModifier; - if (ret.stackCount > ret.getMaxStackCount(scene)) { - ret.stackCount = ret.getMaxStackCount(scene); + if (ret.stackCount > ret.getMaxStackCount()) { + ret.stackCount = ret.getMaxStackCount(); } return ret; diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 64801cc0ff1..92eca5c3e9f 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -1,17 +1,17 @@ import { BattleType } from "../battle"; -import BattleScene from "../battle-scene"; -import { Gender } from "../data/gender"; -import { Nature } from "#enums/nature"; -import { PokeballType } from "#enums/pokeball"; +import { globalScene } from "#app/global-scene"; +import type { Gender } from "../data/gender"; +import type { Nature } from "#enums/nature"; +import type { PokeballType } from "#enums/pokeball"; import { getPokemonSpecies } from "../data/pokemon-species"; import { Status } from "../data/status-effect"; import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon"; import { TrainerSlot } from "../data/trainer-config"; -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { loadBattlerTag } from "../data/battler-tags"; -import { Biome } from "#enums/biome"; +import type { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { @@ -163,15 +163,15 @@ export default class PokemonData { } } - toPokemon(scene: BattleScene, battleType?: BattleType, partyMemberIndex: integer = 0, double: boolean = false): Pokemon { + toPokemon(battleType?: BattleType, partyMemberIndex: integer = 0, double: boolean = false): Pokemon { const species = getPokemonSpecies(this.species); const ret: Pokemon = this.player - ? scene.addPlayerPokemon(species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this, (playerPokemon) => { + ? globalScene.addPlayerPokemon(species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this.variant, this.ivs, this.nature, this, (playerPokemon) => { if (this.nickname) { playerPokemon.nickname = this.nickname; } }) - : scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this); + : globalScene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this); if (this.summonData) { ret.primeSummonData(this.summonData); } diff --git a/src/system/session-history.ts b/src/system/session-history.ts index 85a300110d9..e6901b2fa93 100644 --- a/src/system/session-history.ts +++ b/src/system/session-history.ts @@ -1,6 +1,6 @@ -import { GameModes } from "../game-mode"; -import PokemonData from "./pokemon-data"; -import PersistentModifierData from "./modifier-data"; +import type { GameModes } from "../game-mode"; +import type PokemonData from "./pokemon-data"; +import type PersistentModifierData from "./modifier-data"; export enum SessionHistoryResult { ACTIVE, diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index 322b2baac9e..18973666766 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -1,9 +1,9 @@ -import BattleScene from "../../battle-scene"; -import SettingsGamepadUiHandler from "../../ui/settings/settings-gamepad-ui-handler"; +import type SettingsGamepadUiHandler from "../../ui/settings/settings-gamepad-ui-handler"; import { Mode } from "../../ui/ui"; import { truncateString } from "../../utils"; import { Button } from "#enums/buttons"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { globalScene } from "#app/global-scene"; export enum SettingGamepad { Controller = "CONTROLLER", @@ -80,12 +80,12 @@ export const settingGamepadBlackList = [ SettingKeyboard.Button_Right, ]; -export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { +export function setSettingGamepad(setting: SettingGamepad, value: integer): boolean { switch (setting) { case SettingGamepad.Gamepad_Support: // if we change the value of the gamepad support, we call a method in the inputController to // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); + globalScene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); break; case SettingGamepad.Button_Action: case SettingGamepad.Button_Cancel: @@ -101,13 +101,13 @@ export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, v case SettingGamepad.Button_Slow_Down: case SettingGamepad.Button_Submit: if (value) { - if (scene.ui) { + if (globalScene.ui) { const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + globalScene.ui.revertMode(); + (globalScene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); return success; }; - scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + globalScene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { target: setting, cancelHandler: cancelHandler, }); @@ -116,20 +116,20 @@ export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, v break; case SettingGamepad.Controller: if (value) { - const gp = scene.inputController.getGamepadsName(); - if (scene.ui && gp) { + const gp = globalScene.inputController.getGamepadsName(); + if (globalScene.ui && gp) { const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + globalScene.ui.revertMode(); + (globalScene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); + (globalScene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); return false; }; const changeGamepadHandler = (gamepad: string) => { - scene.inputController.setChosenGamepad(gamepad); + globalScene.inputController.setChosenGamepad(gamepad); cancelHandler(); return true; }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: [ ...gp.map((g: string) => ({ label: truncateString(g, 30), // Truncate the gamepad name for display handler: () => changeGamepadHandler(g) diff --git a/src/system/settings/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts index 97990f61c86..f4adc30ee52 100644 --- a/src/system/settings/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -1,8 +1,8 @@ import { Button } from "#enums/buttons"; -import BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; -import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; +import type SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; export enum SettingKeyboard { // Default_Layout = "DEFAULT_LAYOUT", @@ -133,7 +133,7 @@ export const settingKeyboardBlackList = [ ]; -export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { +export function setSettingKeyboard(setting: SettingKeyboard, value: integer): boolean { switch (setting) { case SettingKeyboard.Button_Up: case SettingKeyboard.Button_Down: @@ -169,40 +169,19 @@ export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, case SettingKeyboard.Alt_Button_Slow_Down: case SettingKeyboard.Alt_Button_Submit: if (value) { - if (scene.ui) { + if (globalScene.ui) { const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + globalScene.ui.revertMode(); + (globalScene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); return success; }; - scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + globalScene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { target: setting, cancelHandler: cancelHandler, }); } } break; - // case SettingKeyboard.Default_Layout: - // if (value && scene.ui) { - // const cancelHandler = () => { - // scene.ui.revertMode(); - // (scene.ui.getHandler() as SettingsKeyboardUiHandler).setOptionCursor(Object.values(SettingKeyboard).indexOf(SettingKeyboard.Default_Layout), 0, true); - // (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); - // return false; - // }; - // const changeKeyboardHandler = (keyboardLayout: string) => { - // scene.inputController.setChosenKeyboardLayout(keyboardLayout); - // cancelHandler(); - // return true; - // }; - // scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - // options: [{ - // label: 'Default', - // handler: changeKeyboardHandler, - // }] - // }); - // return false; - // } } return true; diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 0a11648c171..ed8e49ffe37 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -1,29 +1,21 @@ import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { hasTouchscreen } from "#app/touch-controls"; import { updateWindowType } from "#app/ui/ui-theme"; import { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; -import SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; +import type SettingsUiHandler from "#app/ui/settings/settings-ui-handler"; import { EaseType } from "#enums/ease-type"; import { MoneyFormat } from "#enums/money-format"; import { PlayerGender } from "#enums/player-gender"; -import { getIsInitialized, initI18n } from "#app/plugins/i18n"; -import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; - -function getTranslation(key: string): string { - if (!getIsInitialized()) { - initI18n(); - } - return i18next.t(key); -} +import { ShopCursorTarget } from "#enums/shop-cursor-target"; const VOLUME_OPTIONS: SettingOption[] = new Array(11).fill(null).map((_, i) => i ? { value: (i * 10).toString(), label: (i * 10).toString(), } : { value: "Mute", - label: getTranslation("settings:mute") + label: i18next.t("settings:mute") }); const SHOP_OVERLAY_OPACITY_OPTIONS: SettingOption[] = new Array(9).fill(null).map((_, i) => { @@ -710,174 +702,172 @@ export function settingIndex(key: string) { /** * Resets all settings to their defaults - * @param scene current BattleScene */ -export function resetSettings(scene: BattleScene) { - Setting.forEach(s => setSetting(scene, s.key, s.default)); +export function resetSettings() { + Setting.forEach(s => setSetting(s.key, s.default)); } /** - * Updates a setting for current BattleScene - * @param scene current BattleScene + * Updates a setting * @param setting string ideally from SettingKeys * @param value value to update setting with * @returns true if successful, false if not */ -export function setSetting(scene: BattleScene, setting: string, value: integer): boolean { +export function setSetting(setting: string, value: integer): boolean { const index: number = settingIndex(setting); if (index === -1) { return false; } switch (Setting[index].key) { case SettingKeys.Game_Speed: - scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); + globalScene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); break; case SettingKeys.Master_Volume: - scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); + globalScene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + globalScene.updateSoundVolume(); break; case SettingKeys.BGM_Volume: - scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); + globalScene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + globalScene.updateSoundVolume(); break; case SettingKeys.Field_Volume: - scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); + globalScene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + globalScene.updateSoundVolume(); break; case SettingKeys.SE_Volume: - scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); + globalScene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + globalScene.updateSoundVolume(); break; case SettingKeys.UI_Volume: - scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + globalScene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; break; case SettingKeys.Music_Preference: - scene.musicPreference = value; + globalScene.musicPreference = value; break; case SettingKeys.Damage_Numbers: - scene.damageNumbersMode = value; + globalScene.damageNumbersMode = value; break; case SettingKeys.UI_Theme: - scene.uiTheme = value; + globalScene.uiTheme = value; break; case SettingKeys.Window_Type: - updateWindowType(scene, parseInt(Setting[index].options[value].value)); + updateWindowType(parseInt(Setting[index].options[value].value)); break; case SettingKeys.Tutorials: - scene.enableTutorials = Setting[index].options[value].value === "On"; + globalScene.enableTutorials = Setting[index].options[value].value === "On"; break; case SettingKeys.Move_Info: - scene.enableMoveInfo = Setting[index].options[value].value === "On"; + globalScene.enableMoveInfo = Setting[index].options[value].value === "On"; break; case SettingKeys.Enable_Retries: - scene.enableRetries = Setting[index].options[value].value === "On"; + globalScene.enableRetries = Setting[index].options[value].value === "On"; break; case SettingKeys.Hide_IVs: - scene.hideIvs = Setting[index].options[value].value === "On"; + globalScene.hideIvs = Setting[index].options[value].value === "On"; break; case SettingKeys.Skip_Seen_Dialogues: - scene.skipSeenDialogues = Setting[index].options[value].value === "On"; + globalScene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; case SettingKeys.Egg_Skip: - scene.eggSkipPreference = value; + globalScene.eggSkipPreference = value; break; case SettingKeys.Battle_Style: - scene.battleStyle = value; + globalScene.battleStyle = value; break; case SettingKeys.Show_BGM_Bar: - scene.showBgmBar = Setting[index].options[value].value === "On"; + globalScene.showBgmBar = Setting[index].options[value].value === "On"; break; case SettingKeys.Candy_Upgrade_Notification: - if (scene.candyUpgradeNotification === value) { + if (globalScene.candyUpgradeNotification === value) { break; } - scene.candyUpgradeNotification = value; - scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + globalScene.candyUpgradeNotification = value; + globalScene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); break; case SettingKeys.Candy_Upgrade_Display: - scene.candyUpgradeDisplay = value; + globalScene.candyUpgradeDisplay = value; case SettingKeys.Money_Format: switch (Setting[index].options[value].value) { case "Normal": - scene.moneyFormat = MoneyFormat.NORMAL; + globalScene.moneyFormat = MoneyFormat.NORMAL; break; case "Abbreviated": - scene.moneyFormat = MoneyFormat.ABBREVIATED; + globalScene.moneyFormat = MoneyFormat.ABBREVIATED; break; } - scene.updateMoneyText(false); + globalScene.updateMoneyText(false); break; case SettingKeys.Sprite_Set: - scene.experimentalSprites = !!value; + globalScene.experimentalSprites = !!value; if (value) { - scene.initExpSprites(); + globalScene.initExpSprites(); } break; case SettingKeys.Move_Animations: - scene.moveAnimations = Setting[index].options[value].value === "On"; + globalScene.moveAnimations = Setting[index].options[value].value === "On"; break; case SettingKeys.Show_Moveset_Flyout: - scene.showMovesetFlyout = Setting[index].options[value].value === "On"; + globalScene.showMovesetFlyout = Setting[index].options[value].value === "On"; break; case SettingKeys.Show_Arena_Flyout: - scene.showArenaFlyout = Setting[index].options[value].value === "On"; + globalScene.showArenaFlyout = Setting[index].options[value].value === "On"; break; case SettingKeys.Show_Time_Of_Day_Widget: - scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; + globalScene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; break; case SettingKeys.Time_Of_Day_Animation: - scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; + globalScene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; break; case SettingKeys.Show_Stats_on_Level_Up: - scene.showLevelUpStats = Setting[index].options[value].value === "On"; + globalScene.showLevelUpStats = Setting[index].options[value].value === "On"; break; case SettingKeys.Shop_Cursor_Target: const selectedValue = shopCursorTargetIndexMap[value]; - scene.shopCursorTarget = selectedValue; + globalScene.shopCursorTarget = selectedValue; break; case SettingKeys.Command_Cursor_Memory: - scene.commandCursorMemory = Setting[index].options[value].value === "On"; + globalScene.commandCursorMemory = Setting[index].options[value].value === "On"; break; case SettingKeys.EXP_Gains_Speed: - scene.expGainsSpeed = value; + globalScene.expGainsSpeed = value; break; case SettingKeys.EXP_Party_Display: - scene.expParty = value; + globalScene.expParty = value; break; case SettingKeys.HP_Bar_Speed: - scene.hpBarSpeed = value; + globalScene.hpBarSpeed = value; break; case SettingKeys.Fusion_Palette_Swaps: - scene.fusionPaletteSwaps = !!value; + globalScene.fusionPaletteSwaps = !!value; break; case SettingKeys.Player_Gender: - if (scene.gameData) { + if (globalScene.gameData) { const female = Setting[index].options[value].value === "Girl"; - scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; - scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); + globalScene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; + globalScene.trainer.setTexture(globalScene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); } else { return false; } break; case SettingKeys.Touch_Controls: - scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + globalScene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); const touchControls = document.getElementById("touchControls"); if (touchControls) { - touchControls.classList.toggle("visible", scene.enableTouchControls); + touchControls.classList.toggle("visible", globalScene.enableTouchControls); } break; case SettingKeys.Vibration: - scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + globalScene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); break; case SettingKeys.Type_Hints: - scene.typeHints = Setting[index].options[value].value === "On"; + globalScene.typeHints = Setting[index].options[value].value === "On"; break; case SettingKeys.Language: if (value) { - if (scene.ui) { + if (globalScene.ui) { const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true); + globalScene.ui.revertMode(); + (globalScene.ui.getHandler() as SettingsUiHandler).setOptionCursor(-1, 0, true); }; const changeLocaleHandler = (locale: string): boolean => { try { @@ -892,7 +882,7 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): return false; } }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: [ { label: "English", @@ -950,7 +940,7 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): } break; case SettingKeys.Shop_Overlay_Opacity: - scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); + globalScene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); break; } diff --git a/src/system/trainer-data.ts b/src/system/trainer-data.ts index fc8a14488cc..51dfdde9ec1 100644 --- a/src/system/trainer-data.ts +++ b/src/system/trainer-data.ts @@ -1,5 +1,4 @@ -import BattleScene from "../battle-scene"; -import { TrainerType } from "#enums/trainer-type"; +import type { TrainerType } from "#enums/trainer-type"; import Trainer, { TrainerVariant } from "../field/trainer"; export default class TrainerData { @@ -18,7 +17,7 @@ export default class TrainerData { this.partnerName = source.partnerName; } - toTrainer(scene: BattleScene): Trainer { - return new Trainer(scene, this.trainerType, this.variant, this.partyTemplateIndex, this.name, this.partnerName); + toTrainer(): Trainer { + return new Trainer(this.trainerType, this.variant, this.partyTemplateIndex, this.name, this.partnerName); } } diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index d0c69dc3213..aee84805143 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -1,4 +1,4 @@ -import { SessionSaveData, SystemSaveData } from "../game-data"; +import type { SessionSaveData, SystemSaveData } from "../game-data"; import { version } from "../../../package.json"; // --- v1.0.4 (and below) PATCHES --- // diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index 95f0337ecdd..2795e940866 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -1,5 +1,6 @@ import { SettingKeys } from "#app/system/settings/settings"; -import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "#app/system/game-data"; +import type { SystemSaveData, SessionSaveData } from "#app/system/game-data"; +import { AbilityAttr, defaultStarterSpecies, DexAttr } from "#app/system/game-data"; import { allSpecies } from "#app/data/pokemon-species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils"; diff --git a/src/system/voucher.ts b/src/system/voucher.ts index b38fd528c9f..e6bee131797 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -1,9 +1,8 @@ -import BattleScene from "../battle-scene"; import i18next from "i18next"; import { AchvTier, achvs, getAchievementDescription } from "./achv"; -import { PlayerGender } from "#enums/player-gender"; +import type { PlayerGender } from "#enums/player-gender"; import { TrainerType } from "#enums/trainer-type"; -import { ConditionFn } from "#app/@types/common"; +import type { ConditionFn } from "#app/@types/common"; import { trainerConfigs } from "#app/data/trainer-config"; export enum VoucherType { @@ -26,8 +25,8 @@ export class Voucher { this.conditionFunc = conditionFunc; } - validate(scene: BattleScene, args?: any[]): boolean { - return !this.conditionFunc || this.conditionFunc(scene, args); + validate(args?: any[]): boolean { + return !this.conditionFunc || this.conditionFunc(args); } /** diff --git a/src/test/abilities/aroma_veil.test.ts b/src/test/abilities/aroma_veil.test.ts index e74d0ff5a39..4ba4d16504b 100644 --- a/src/test/abilities/aroma_veil.test.ts +++ b/src/test/abilities/aroma_veil.test.ts @@ -7,7 +7,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { BattlerTagType } from "#enums/battler-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerIndex } from "#app/battle"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; describe("Moves - Aroma Veil", () => { let phaserGame: Phaser.Game; diff --git a/src/test/abilities/commander.test.ts b/src/test/abilities/commander.test.ts index 99b3d50797c..70568639b61 100644 --- a/src/test/abilities/commander.test.ts +++ b/src/test/abilities/commander.test.ts @@ -1,7 +1,8 @@ import { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; -import { EffectiveStat, Stat } from "#enums/stat"; +import type { EffectiveStat } from "#enums/stat"; +import { Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { WeatherType } from "#enums/weather-type"; import { MoveResult } from "#app/field/pokemon"; diff --git a/src/test/abilities/dancer.test.ts b/src/test/abilities/dancer.test.ts index 8fa3d444f37..842b2196441 100644 --- a/src/test/abilities/dancer.test.ts +++ b/src/test/abilities/dancer.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import { MovePhase } from "#app/phases/move-phase"; +import type { MovePhase } from "#app/phases/move-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 01b68d0c89d..bbb103c63e6 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; diff --git a/src/test/abilities/infiltrator.test.ts b/src/test/abilities/infiltrator.test.ts index 01c5cef7796..8ea72b55b0c 100644 --- a/src/test/abilities/infiltrator.test.ts +++ b/src/test/abilities/infiltrator.test.ts @@ -75,7 +75,7 @@ describe("Abilities - Infiltrator", () => { }); // TODO: fix this interaction to pass this test - it.skip("should bypass the target's Mist", async () => { + it.todo("should bypass the target's Mist", async () => { await game.classicMode.startBattle([ Species.MAGIKARP ]); const player = game.scene.getPlayerPokemon()!; diff --git a/src/test/abilities/libero.test.ts b/src/test/abilities/libero.test.ts index e7bc9eeeb63..42627da51a3 100644 --- a/src/test/abilities/libero.test.ts +++ b/src/test/abilities/libero.test.ts @@ -1,7 +1,7 @@ import { allMoves } from "#app/data/move"; import { Type } from "#enums/type"; import { Weather } from "#app/data/weather"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -54,6 +54,7 @@ describe("Abilities - Libero", () => { }, ); + // Test for Gen9+ functionality, we are using previous funcionality test.skip( "ability applies only once per switch in", async () => { diff --git a/src/test/abilities/protean.test.ts b/src/test/abilities/protean.test.ts index 0d7202e3f6d..787834f8a9d 100644 --- a/src/test/abilities/protean.test.ts +++ b/src/test/abilities/protean.test.ts @@ -1,7 +1,7 @@ import { allMoves } from "#app/data/move"; import { Type } from "#enums/type"; import { Weather } from "#app/data/weather"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -54,6 +54,7 @@ describe("Abilities - Protean", () => { }, ); + // Test for Gen9+ functionality, we are using previous funcionality test.skip( "ability applies only once per switch in", async () => { diff --git a/src/test/abilities/speed_boost.test.ts b/src/test/abilities/speed_boost.test.ts index ff5184eedae..74ee6a8cb92 100644 --- a/src/test/abilities/speed_boost.test.ts +++ b/src/test/abilities/speed_boost.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { Command } from "#app/ui/command-ui-handler"; import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; diff --git a/src/test/abilities/sturdy.test.ts b/src/test/abilities/sturdy.test.ts index 07ccbbb68e5..8f134338f12 100644 --- a/src/test/abilities/sturdy.test.ts +++ b/src/test/abilities/sturdy.test.ts @@ -1,4 +1,4 @@ -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/abilities/unburden.test.ts b/src/test/abilities/unburden.test.ts index ba14c7fdcd0..a652f55d591 100644 --- a/src/test/abilities/unburden.test.ts +++ b/src/test/abilities/unburden.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { PostItemLostAbAttr } from "#app/data/ability"; import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/test/achievements/achievement.test.ts b/src/test/achievements/achievement.test.ts index a3669c60463..b515c6bafa8 100644 --- a/src/test/achievements/achievement.test.ts +++ b/src/test/achievements/achievement.test.ts @@ -58,11 +58,11 @@ describe("Achv", () => { }); it("should validate the achievement based on the condition function", () => { - const conditionFunc = vi.fn((scene: BattleScene, args: any[]) => args[0] === 10); + const conditionFunc = vi.fn((args: any[]) => args[0] === 10); const achv = new Achv("", "Test Achievement", "Test Description", "test_icon", 10, conditionFunc); - expect(achv.validate(new BattleScene(), [ 5 ])).toBe(false); - expect(achv.validate(new BattleScene(), [ 10 ])).toBe(true); + expect(achv.validate([ 5 ])).toBe(false); + expect(achv.validate([ 10 ])).toBe(true); expect(conditionFunc).toHaveBeenCalledTimes(2); }); }); @@ -79,10 +79,10 @@ describe("MoneyAchv", () => { const scene = new BattleScene(); scene.money = 5000; - expect(moneyAchv.validate(scene, [])).toBe(false); + expect(moneyAchv.validate([])).toBe(false); scene.money = 15000; - expect(moneyAchv.validate(scene, [])).toBe(true); + expect(moneyAchv.validate([])).toBe(true); }); }); @@ -122,10 +122,10 @@ describe("RibbonAchv", () => { const ribbonAchv = new RibbonAchv("", "Test Ribbon Achievement", 10, "ribbon_icon", 10); scene.gameData.gameStats.ribbonsOwned = 5; - expect(ribbonAchv.validate(scene, [])).toBe(false); + expect(ribbonAchv.validate([])).toBe(false); scene.gameData.gameStats.ribbonsOwned = 15; - expect(ribbonAchv.validate(scene, [])).toBe(true); + expect(ribbonAchv.validate([])).toBe(true); }); }); @@ -138,13 +138,12 @@ describe("DamageAchv", () => { it("should validate the achievement based on the damage amount", () => { const damageAchv = new DamageAchv("", "Test Damage Achievement", 250, "damage_icon", 10); - const scene = new BattleScene(); const numberHolder = new NumberHolder(200); - expect(damageAchv.validate(scene, [ numberHolder ])).toBe(false); + expect(damageAchv.validate([ numberHolder ])).toBe(false); numberHolder.value = 300; - expect(damageAchv.validate(scene, [ numberHolder ])).toBe(true); + expect(damageAchv.validate([ numberHolder ])).toBe(true); }); }); @@ -157,13 +156,12 @@ describe("HealAchv", () => { it("should validate the achievement based on the heal amount", () => { const healAchv = new HealAchv("", "Test Heal Achievement", 250, "heal_icon", 10); - const scene = new BattleScene(); const numberHolder = new NumberHolder(200); - expect(healAchv.validate(scene, [ numberHolder ])).toBe(false); + expect(healAchv.validate([ numberHolder ])).toBe(false); numberHolder.value = 300; - expect(healAchv.validate(scene, [ numberHolder ])).toBe(true); + expect(healAchv.validate([ numberHolder ])).toBe(true); }); }); @@ -176,13 +174,12 @@ describe("LevelAchv", () => { it("should validate the achievement based on the level", () => { const levelAchv = new LevelAchv("", "Test Level Achievement", 100, "level_icon", 10); - const scene = new BattleScene(); const integerHolder = new IntegerHolder(50); - expect(levelAchv.validate(scene, [ integerHolder ])).toBe(false); + expect(levelAchv.validate([ integerHolder ])).toBe(false); integerHolder.value = 150; - expect(levelAchv.validate(scene, [ integerHolder ])).toBe(true); + expect(levelAchv.validate([ integerHolder ])).toBe(true); }); }); @@ -195,10 +192,9 @@ describe("ModifierAchv", () => { it("should validate the achievement based on the modifier function", () => { const modifierAchv = new ModifierAchv("", "Test Modifier Achievement", "Test Description", "modifier_icon", 10, () => true); - const scene = new BattleScene(); const modifier = new TurnHeldItemTransferModifier(null!, 3, 1); - expect(modifierAchv.validate(scene, [ modifier ])).toBe(true); + expect(modifierAchv.validate([ modifier ])).toBe(true); }); }); diff --git a/src/test/battle/battle.test.ts b/src/test/battle/battle.test.ts index d2b074acce0..bd84cb2fd3b 100644 --- a/src/test/battle/battle.test.ts +++ b/src/test/battle/battle.test.ts @@ -188,8 +188,8 @@ describe("Test Battle Phase", () => { game.onNextPrompt("TitlePhase", Mode.TITLE, () => { game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(game.scene); - const selectStarterPhase = new SelectStarterPhase(game.scene); - game.scene.pushPhase(new EncounterPhase(game.scene, false)); + const selectStarterPhase = new SelectStarterPhase(); + game.scene.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); await game.phaseInterceptor.runFrom(SelectGenderPhase).to(SummonPhase); diff --git a/src/test/battle/error-handling.test.ts b/src/test/battle/error-handling.test.ts deleted file mode 100644 index 208463e7064..00000000000 --- a/src/test/battle/error-handling.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Abilities } from "#enums/abilities"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import GameManager from "#test/utils/gameManager"; -import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; - -describe("Error Handling", () => { - let phaserGame: Phaser.Game; - let game: GameManager; - const moveToUse = Moves.SPLASH; - - beforeAll(() => { - phaserGame = new Phaser.Game({ - type: Phaser.HEADLESS, - }); - }); - - afterEach(() => { - game.phaseInterceptor.restoreOg(); - }); - - beforeEach(() => { - game = new GameManager(phaserGame); - game.override - .battleType("single") - .startingWave(3); - game.override.starterSpecies(Species.MEWTWO); - game.override.enemySpecies(Species.RATTATA); - game.override.enemyAbility(Abilities.HYDRATION); - game.override.ability(Abilities.ZEN_MODE); - game.override.startingLevel(2000); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); - }); - - it.skip("to next turn", async () => { - await game.startBattle(); - const turn = game.scene.currentBattle.turn; - game.move.select(moveToUse); - await game.toNextTurn(); - expect(game.scene.currentBattle.turn).toBeGreaterThan(turn); - }, 20000); -}); - diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index 9efce220fe8..4bf0257af8f 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -1,23 +1,34 @@ -import BattleScene from "#app/battle-scene"; -import { describe, expect, it, vi } from "vitest"; -import Pokemon from "#app/field/pokemon"; +import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import type Pokemon from "#app/field/pokemon"; import { BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; - -vi.mock("#app/battle-scene.js"); +import GameManager from "#test/utils/gameManager"; describe("BattlerTag - OctolockTag", () => { describe("lapse behavior", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + game = new GameManager(phaserGame); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + it("unshifts a StatStageChangePhase with expected stat stage changes", async () => { const mockPokemon = { - scene: new BattleScene(), getBattlerIndex: () => 0, } as Pokemon; const subject = new OctolockTag(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(-1); expect((phase as StatStageChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]); @@ -25,7 +36,7 @@ describe("BattlerTag - OctolockTag", () => { subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END); - expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.unshiftPhase).toBeCalledTimes(1); }); }); diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts index dab189853c5..13a4227853d 100644 --- a/src/test/battlerTags/stockpiling.test.ts +++ b/src/test/battlerTags/stockpiling.test.ts @@ -1,28 +1,42 @@ -import BattleScene from "#app/battle-scene"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import Pokemon, { PokemonSummonData } from "#app/field/pokemon"; import { StockpilingTag } from "#app/data/battler-tags"; -import { Stat } from "#enums/stat"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonSummonData } from "#app/field/pokemon"; import * as messages from "#app/messages"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/utils/gameManager"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; beforeEach(() => { vi.spyOn(messages, "getPokemonNameWithAffix").mockImplementation(() => ""); }); describe("BattlerTag - StockpilingTag", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + game = new GameManager(phaserGame); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + describe("onAdd", () => { it("unshifts a StatStageChangePhase with expected stat stage changes on add", async () => { const mockPokemon = { - scene: vi.mocked(new BattleScene()) as BattleScene, getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); const subject = new StockpilingTag(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -32,24 +46,23 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); - expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.unshiftPhase).toBeCalledTimes(1); }); it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", async () => { const mockPokemon = { - scene: new BattleScene(), summonData: new PokemonSummonData(), getBattlerIndex: () => 0, - } as Pokemon; + } as unknown as Pokemon; - vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); mockPokemon.summonData.statStages[Stat.DEF - 1] = 6; mockPokemon.summonData.statStages[Stat.SPD - 1] = 5; const subject = new StockpilingTag(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -59,22 +72,21 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); - expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.unshiftPhase).toBeCalledTimes(1); }); }); describe("onOverlap", () => { it("unshifts a StatStageChangePhase with expected stat changes on overlap", async () => { const mockPokemon = { - scene: new BattleScene(), getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); const subject = new StockpilingTag(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementation(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -84,26 +96,25 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); - expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); + expect(game.scene.unshiftPhase).toBeCalledTimes(1); }); }); describe("stack limit, stat tracking, and removal", () => { it("can be added up to three times, even when one stat does not change", async () => { const mockPokemon = { - scene: new BattleScene(), summonData: new PokemonSummonData(), getBattlerIndex: () => 0, } as Pokemon; - vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); + vi.spyOn(game.scene, "queueMessage").mockImplementation(() => {}); mockPokemon.summonData.statStages[Stat.DEF - 1] = 5; mockPokemon.summonData.statStages[Stat.SPD - 1] = 4; const subject = new StockpilingTag(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -115,7 +126,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onAdd(mockPokemon); expect(subject.stockpiledCount).toBe(1); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -127,7 +138,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); expect(subject.stockpiledCount).toBe(2); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(1); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); @@ -138,7 +149,7 @@ describe("BattlerTag - StockpilingTag", () => { subject.onOverlap(mockPokemon); expect(subject.stockpiledCount).toBe(3); - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(_phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(_phase => { throw new Error("Should not be called a fourth time"); }); @@ -148,14 +159,14 @@ describe("BattlerTag - StockpilingTag", () => { expect(subject.statChangeCounts).toMatchObject({ [Stat.DEF]: 0, [Stat.SPDEF]: 2 }); // removing tag should reverse stat changes - vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { + vi.spyOn(game.scene, "unshiftPhase").mockImplementationOnce(phase => { expect(phase).toBeInstanceOf(StatStageChangePhase); expect((phase as StatStageChangePhase)["stages"]).toEqual(-2); expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.SPDEF ])); }); subject.onRemove(mockPokemon); - expect(mockPokemon.scene.unshiftPhase).toHaveBeenCalledOnce(); // note that re-spying each add/overlap has been refreshing call count + expect(game.scene.unshiftPhase).toHaveBeenCalledOnce(); // note that re-spying each add/overlap has been refreshing call count }); }); }); diff --git a/src/test/battlerTags/substitute.test.ts b/src/test/battlerTags/substitute.test.ts index af0aa63af89..4bf7e584ed3 100644 --- a/src/test/battlerTags/substitute.test.ts +++ b/src/test/battlerTags/substitute.test.ts @@ -1,14 +1,14 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import Pokemon, { MoveResult, PokemonTurnData, TurnMove, PokemonMove } from "#app/field/pokemon"; +import type { PokemonTurnData, TurnMove, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import BattleScene from "#app/battle-scene"; import { BattlerTagLapseType, BindTag, SubstituteTag } from "#app/data/battler-tags"; import { Moves } from "#app/enums/moves"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; import * as messages from "#app/messages"; import { allMoves } from "#app/data/move"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; - -vi.mock("#app/battle-scene.js"); +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; describe("BattlerTag - SubstituteTag", () => { let mockPokemon: Pokemon; @@ -26,10 +26,10 @@ describe("BattlerTag - SubstituteTag", () => { expect(tagFilter(trapTag)).toBeTruthy(); return true; }) as Pokemon["findAndRemoveTags"] - } as Pokemon; + } as unknown as Pokemon; vi.spyOn(messages, "getPokemonNameWithAffix").mockReturnValue(""); - vi.spyOn(mockPokemon.scene, "getPokemonById").mockImplementation(pokemonId => mockPokemon.id === pokemonId ? mockPokemon : null); + vi.spyOn(mockPokemon.scene as BattleScene, "getPokemonById").mockImplementation(pokemonId => mockPokemon.id === pokemonId ? mockPokemon : null); }); it( @@ -37,8 +37,8 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); @@ -51,20 +51,20 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockImplementation( + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockImplementation( (pokemon, battleAnimType, fieldAssets?, delayed?) => { expect(battleAnimType).toBe(PokemonAnimType.SUBSTITUTE_ADD); return true; } ); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); expect(subject.sourceInFocus).toBeFalsy(); - expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); } ); @@ -73,7 +73,7 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); subject.onAdd(mockPokemon); expect(mockPokemon.findAndRemoveTags).toHaveBeenCalledTimes(1); @@ -88,7 +88,7 @@ describe("BattlerTag - SubstituteTag", () => { hp: 101, id: 0, isFainted: vi.fn().mockReturnValue(false) as Pokemon["isFainted"] - } as Pokemon; + } as unknown as Pokemon; vi.spyOn(messages, "getPokemonNameWithAffix").mockReturnValue(""); }); @@ -99,19 +99,19 @@ describe("BattlerTag - SubstituteTag", () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); subject.sourceInFocus = false; - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockImplementation( + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockImplementation( (pokemon, battleAnimType, fieldAssets?, delayed?) => { expect(battleAnimType).toBe(PokemonAnimType.SUBSTITUTE_REMOVE); return true; } ); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); subject.onRemove(mockPokemon); - expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); } ); }); @@ -124,7 +124,7 @@ describe("BattlerTag - SubstituteTag", () => { id: 0, turnData: { acted: true } as PokemonTurnData, getLastXMoves: vi.fn().mockReturnValue([ { move: Moves.TACKLE, result: MoveResult.SUCCESS } as TurnMove ]) as Pokemon["getLastXMoves"], - } as Pokemon; + } as unknown as Pokemon; vi.spyOn(messages, "getPokemonNameWithAffix").mockReturnValue(""); }); @@ -134,20 +134,20 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockImplementation( + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockImplementation( (pokemon, battleAnimType, fieldAssets?, delayed?) => { expect(battleAnimType).toBe(PokemonAnimType.SUBSTITUTE_PRE_MOVE); return true; } ); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.PRE_MOVE)).toBeTruthy(); expect(subject.sourceInFocus).toBeTruthy(); - expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect(mockPokemon.scene.queueMessage).not.toHaveBeenCalled(); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); } ); @@ -156,31 +156,31 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockImplementation( + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockImplementation( (pokemon, battleAnimType, fieldAssets?, delayed?) => { expect(battleAnimType).toBe(PokemonAnimType.SUBSTITUTE_POST_MOVE); return true; } ); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.AFTER_MOVE)).toBeTruthy(); expect(subject.sourceInFocus).toBeFalsy(); - expect(mockPokemon.scene.triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); - expect(mockPokemon.scene.queueMessage).not.toHaveBeenCalled(); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); } ); - /** TODO: Figure out how to mock a MoveEffectPhase correctly for this test */ - it.skip( + // TODO: Figure out how to mock a MoveEffectPhase correctly for this test + it.todo( "HIT lapse triggers on-hit message", async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); const pokemonMove = { getMove: vi.fn().mockReturnValue(allMoves[Moves.TACKLE]) as PokemonMove["getMove"] @@ -191,13 +191,13 @@ describe("BattlerTag - SubstituteTag", () => { getUserPokemon: vi.fn().mockReturnValue(undefined) as MoveEffectPhase["getUserPokemon"] } as MoveEffectPhase; - vi.spyOn(mockPokemon.scene, "getCurrentPhase").mockReturnValue(moveEffectPhase); + vi.spyOn(mockPokemon.scene as BattleScene, "getCurrentPhase").mockReturnValue(moveEffectPhase); vi.spyOn(allMoves[Moves.TACKLE], "hitsSubstitute").mockReturnValue(true); expect(subject.lapse(mockPokemon, BattlerTagLapseType.HIT)).toBeTruthy(); - expect(mockPokemon.scene.triggerPokemonBattleAnim).not.toHaveBeenCalled(); - expect(mockPokemon.scene.queueMessage).toHaveBeenCalledTimes(1); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).not.toHaveBeenCalled(); + expect((mockPokemon.scene as BattleScene).queueMessage).toHaveBeenCalledTimes(1); } ); @@ -206,8 +206,8 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.CUSTOM)).toBeFalsy(); } @@ -218,13 +218,13 @@ describe("BattlerTag - SubstituteTag", () => { async () => { const subject = new SubstituteTag(Moves.SUBSTITUTE, mockPokemon.id); - vi.spyOn(mockPokemon.scene, "triggerPokemonBattleAnim").mockReturnValue(true); - vi.spyOn(mockPokemon.scene, "queueMessage").mockReturnValue(); + vi.spyOn(mockPokemon.scene as BattleScene, "triggerPokemonBattleAnim").mockReturnValue(true); + vi.spyOn(mockPokemon.scene as BattleScene, "queueMessage").mockReturnValue(); expect(subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END)).toBeTruthy(); - expect(mockPokemon.scene.triggerPokemonBattleAnim).not.toHaveBeenCalled(); - expect(mockPokemon.scene.queueMessage).not.toHaveBeenCalled(); + expect((mockPokemon.scene as BattleScene).triggerPokemonBattleAnim).not.toHaveBeenCalled(); + expect((mockPokemon.scene as BattleScene).queueMessage).not.toHaveBeenCalled(); } ); }); diff --git a/src/test/boss-pokemon.test.ts b/src/test/boss-pokemon.test.ts index 840b65f3cc6..389b42a2963 100644 --- a/src/test/boss-pokemon.test.ts +++ b/src/test/boss-pokemon.test.ts @@ -5,7 +5,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { EFFECTIVE_STATS } from "#app/enums/stat"; -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { toDmgValue } from "#app/utils"; describe("Boss Pokemon / Shields", () => { diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index 1a33b4eff7c..3394771d84a 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -33,20 +33,18 @@ describe("Egg Generation Tests", () => { }); it("should return Kyogre for the 10th of June", () => { - const scene = game.scene; const timestamp = new Date(2024, 5, 10, 15, 0, 0, 0).getTime(); const expectedSpecies = Species.KYOGRE; - const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); + const result = getLegendaryGachaSpeciesForTimestamp(timestamp); expect(result).toBe(expectedSpecies); }); it("should return Kyogre for the 10th of July", () => { - const scene = game.scene; const timestamp = new Date(2024, 6, 10, 15, 0, 0, 0).getTime(); const expectedSpecies = Species.KYOGRE; - const result = getLegendaryGachaSpeciesForTimestamp(scene, timestamp); + const result = getLegendaryGachaSpeciesForTimestamp(timestamp); expect(result).toBe(expectedSpecies); }); @@ -57,7 +55,7 @@ describe("Egg Generation Tests", () => { let gachaSpeciesCount = 0; for (let i = 0; i < EGG_HATCH_COUNT; i++) { - const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId; + const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon().species.speciesId; if (result === expectedSpecies) { gachaSpeciesCount++; } @@ -76,7 +74,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedSpecies = Species.ARCEUS; - const result = new Egg({ scene, species: expectedSpecies }).generatePlayerPokemon(scene).species.speciesId; + const result = new Egg({ scene, species: expectedSpecies }).generatePlayerPokemon().species.speciesId; expect(result).toBe(expectedSpecies); }); @@ -140,7 +138,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedResult = true; - const result = new Egg({ scene, isShiny: expectedResult, species: Species.BULBASAUR }).generatePlayerPokemon(scene).isShiny(); + const result = new Egg({ scene, isShiny: expectedResult, species: Species.BULBASAUR }).generatePlayerPokemon().isShiny(); expect(result).toBe(expectedResult); }); @@ -148,7 +146,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedVariantTier = VariantTier.STANDARD; - const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon(scene).variant; + const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon().variant; expect(result).toBe(expectedVariantTier); }); @@ -156,7 +154,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedVariantTier = VariantTier.RARE; - const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon(scene).variant; + const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon().variant; expect(result).toBe(expectedVariantTier); }); @@ -164,7 +162,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedVariantTier = VariantTier.EPIC; - const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon(scene).variant; + const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon().variant; expect(result).toBe(expectedVariantTier); }); @@ -187,7 +185,7 @@ describe("Egg Generation Tests", () => { it("should return a hatched pokemon with a hidden ability", () => { const scene = game.scene; - const playerPokemon = new Egg({ scene, overrideHiddenAbility: true, species: Species.BULBASAUR }).generatePlayerPokemon(scene); + const playerPokemon = new Egg({ scene, overrideHiddenAbility: true, species: Species.BULBASAUR }).generatePlayerPokemon(); const expectedAbilityIndex = playerPokemon.species.ability2 ? 2 : 1; const result = playerPokemon.abilityIndex; @@ -332,7 +330,7 @@ describe("Egg Generation Tests", () => { scene.resetSeed(); const firstEgg = new Egg({ scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON }); - const firstHatch = firstEgg.generatePlayerPokemon(scene); + const firstHatch = firstEgg.generatePlayerPokemon(); let diffEggMove = false; let diffSpecies = false; let diffShiny = false; @@ -343,7 +341,7 @@ describe("Egg Generation Tests", () => { scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed const newEgg = new Egg({ scene, sourceType: EggSourceType.GACHA_SHINY, tier: EggTier.COMMON }); - const newHatch = newEgg.generatePlayerPokemon(scene); + const newHatch = newEgg.generatePlayerPokemon(); diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex); diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId); diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny); @@ -362,7 +360,7 @@ describe("Egg Generation Tests", () => { scene.resetSeed(); const firstEgg = new Egg({ scene, species: Species.BULBASAUR }); - const firstHatch = firstEgg.generatePlayerPokemon(scene); + const firstHatch = firstEgg.generatePlayerPokemon(); let diffEggMove = false; let diffSpecies = false; let diffShiny = false; @@ -372,7 +370,7 @@ describe("Egg Generation Tests", () => { scene.resetSeed(); // Make sure that eggs are unpredictable even if using same seed const newEgg = new Egg({ scene, species: Species.BULBASAUR }); - const newHatch = newEgg.generatePlayerPokemon(scene); + const newHatch = newEgg.generatePlayerPokemon(); diffEggMove = diffEggMove || (newEgg.eggMoveIndex !== firstEgg.eggMoveIndex); diffSpecies = diffSpecies || (newHatch.species.speciesId !== firstHatch.species.speciesId); diffShiny = diffShiny || (newHatch.shiny !== firstHatch.shiny); diff --git a/src/test/eggs/manaphy-egg.test.ts b/src/test/eggs/manaphy-egg.test.ts index 3b2c40ae84a..4285476a913 100644 --- a/src/test/eggs/manaphy-egg.test.ts +++ b/src/test/eggs/manaphy-egg.test.ts @@ -48,7 +48,7 @@ describe("Manaphy Eggs", () => { rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT); const newEgg = new Egg({ scene, tier: EggTier.COMMON, sourceType: EggSourceType.GACHA_SHINY, id: 204 }); - const newHatch = newEgg.generatePlayerPokemon(scene); + const newHatch = newEgg.generatePlayerPokemon(); if (newHatch.species.speciesId === Species.MANAPHY) { manaphyCount++; } else if (newHatch.species.speciesId === Species.PHIONE) { @@ -74,7 +74,7 @@ describe("Manaphy Eggs", () => { rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT); const newEgg = new Egg({ scene, species: Species.PHIONE, sourceType: EggSourceType.SAME_SPECIES_EGG }); - const newHatch = newEgg.generatePlayerPokemon(scene); + const newHatch = newEgg.generatePlayerPokemon(); if (newHatch.species.speciesId === Species.MANAPHY) { manaphyCount++; } else if (newHatch.species.speciesId === Species.PHIONE) { @@ -100,7 +100,7 @@ describe("Manaphy Eggs", () => { rngSweepProgress = (2 * i + 1) / (2 * EGG_HATCH_COUNT); const newEgg = new Egg({ scene, species: Species.MANAPHY, sourceType: EggSourceType.SAME_SPECIES_EGG }); - const newHatch = newEgg.generatePlayerPokemon(scene); + const newHatch = newEgg.generatePlayerPokemon(); if (newHatch.species.speciesId === Species.MANAPHY) { manaphyCount++; } else if (newHatch.species.speciesId === Species.PHIONE) { diff --git a/src/test/enemy_command.test.ts b/src/test/enemy_command.test.ts index 49419f5b34d..647c0be279a 100644 --- a/src/test/enemy_command.test.ts +++ b/src/test/enemy_command.test.ts @@ -1,21 +1,23 @@ +import type BattleScene from "#app/battle-scene"; import { allMoves, MoveCategory } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; import { Species } from "#app/enums/species"; -import { AiType, EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import { AiType } from "#app/field/pokemon"; import { randSeedInt } from "#app/utils"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; - +let globalScene: BattleScene; const NUM_TRIALS = 300; type MoveChoiceSet = { [key: number]: number }; function getEnemyMoveChoices(pokemon: EnemyPokemon, moveChoices: MoveChoiceSet): void { // Use an unseeded random number generator in place of the mocked-out randBattleSeedInt - vi.spyOn(pokemon.scene, "randBattleSeedInt").mockImplementation((range, min?) => { + vi.spyOn(globalScene, "randBattleSeedInt").mockImplementation((range, min?) => { return randSeedInt(range, min); }); for (let i = 0; i < NUM_TRIALS; i++) { @@ -44,6 +46,7 @@ describe("Enemy Commands - Move Selection", () => { beforeEach(() => { game = new GameManager(phaserGame); + globalScene = game.scene; game.override .ability(Abilities.BALL_FETCH) diff --git a/src/test/escape-calculations.test.ts b/src/test/escape-calculations.test.ts index cc18fd78066..419a6b4c19a 100644 --- a/src/test/escape-calculations.test.ts +++ b/src/test/escape-calculations.test.ts @@ -1,5 +1,5 @@ import { AttemptRunPhase } from "#app/phases/attempt-run-phase"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { Command } from "#app/ui/command-ui-handler"; import * as Utils from "#app/utils"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/field/pokemon.test.ts b/src/test/field/pokemon.test.ts index 0bfbd03e9d9..1e3769a35b1 100644 --- a/src/test/field/pokemon.test.ts +++ b/src/test/field/pokemon.test.ts @@ -2,7 +2,7 @@ import { Species } from "#app/enums/species"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import GameManager from "../utils/gameManager"; import { PokeballType } from "#enums/pokeball"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Moves } from "#app/enums/moves"; describe("Spec - Pokemon", () => { diff --git a/src/test/game-mode.test.ts b/src/test/game-mode.test.ts index 11994a102af..2c8184a30ef 100644 --- a/src/test/game-mode.test.ts +++ b/src/test/game-mode.test.ts @@ -1,4 +1,5 @@ -import { GameMode, GameModes, getGameMode } from "#app/game-mode"; +import type { GameMode } from "#app/game-mode"; +import { GameModes, getGameMode } from "#app/game-mode"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as Utils from "../utils"; import GameManager from "./utils/gameManager"; diff --git a/src/test/items/dire_hit.test.ts b/src/test/items/dire_hit.test.ts index 601552de7f1..3c82ebd3a47 100644 --- a/src/test/items/dire_hit.test.ts +++ b/src/test/items/dire_hit.test.ts @@ -7,7 +7,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { BattleEndPhase } from "#app/phases/battle-end-phase"; import { TempCritBoosterModifier } from "#app/modifier/modifier"; import { Mode } from "#app/ui/ui"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { Button } from "#app/enums/buttons"; import { CommandPhase } from "#app/phases/command-phase"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; diff --git a/src/test/items/double_battle_chance_booster.test.ts b/src/test/items/double_battle_chance_booster.test.ts index 8d2bd7c9179..cccd8d4765e 100644 --- a/src/test/items/double_battle_chance_booster.test.ts +++ b/src/test/items/double_battle_chance_booster.test.ts @@ -6,7 +6,7 @@ import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { Mode } from "#app/ui/ui"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { Button } from "#app/enums/buttons"; describe("Items - Double Battle Chance Boosters", () => { diff --git a/src/test/items/exp_booster.test.ts b/src/test/items/exp_booster.test.ts index 36107329706..7441dcaeb73 100644 --- a/src/test/items/exp_booster.test.ts +++ b/src/test/items/exp_booster.test.ts @@ -34,7 +34,7 @@ describe("EXP Modifier Items", () => { const partyMember = game.scene.getPlayerPokemon()!; partyMember.exp = 100; const expHolder = new Utils.NumberHolder(partyMember.exp); - partyMember.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, expHolder); + game.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, expHolder); expect(expHolder.value).toBe(440); }, 20000); }); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 2909549af87..e0cbeb95b6f 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; -import Pokemon from "#app/field/pokemon"; -import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; +import type Pokemon from "#app/field/pokemon"; +import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; import { Abilities } from "#enums/abilities"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; diff --git a/src/test/items/light_ball.test.ts b/src/test/items/light_ball.test.ts index fe79b6a2045..987a5ab8b0c 100644 --- a/src/test/items/light_ball.test.ts +++ b/src/test/items/light_ball.test.ts @@ -75,17 +75,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); const spAtkValue = new Utils.NumberHolder(spAtkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -114,17 +114,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); const spAtkValue = new Utils.NumberHolder(spAtkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -153,17 +153,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); const spAtkValue = new Utils.NumberHolder(spAtkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); expect(atkValue.value / atkStat).toBe(2); expect(spAtkValue.value / spAtkStat).toBe(2); @@ -181,17 +181,17 @@ describe("Items - Light Ball", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue); const spAtkValue = new Utils.NumberHolder(spAtkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "LIGHT_BALL" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue); expect(atkValue.value / atkStat).toBe(1); expect(spAtkValue.value / spAtkStat).toBe(1); diff --git a/src/test/items/lock_capsule.test.ts b/src/test/items/lock_capsule.test.ts index 0b6534b5eaf..2d95cea5847 100644 --- a/src/test/items/lock_capsule.test.ts +++ b/src/test/items/lock_capsule.test.ts @@ -34,7 +34,7 @@ describe("Items - Lock Capsule", () => { it("doesn't set the cost of common tier items to 0", async () => { await game.classicMode.startBattle(); - game.scene.overridePhase(new SelectModifierPhase(game.scene, 0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false })); + game.scene.overridePhase(new SelectModifierPhase(0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false })); game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => { const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase; diff --git a/src/test/items/metal_powder.test.ts b/src/test/items/metal_powder.test.ts index 86e7d329ecb..42ef9c1bb16 100644 --- a/src/test/items/metal_powder.test.ts +++ b/src/test/items/metal_powder.test.ts @@ -74,13 +74,13 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new Utils.NumberHolder(defStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); }, 20000); @@ -107,13 +107,13 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new Utils.NumberHolder(defStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); }, 20000); @@ -140,13 +140,13 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new Utils.NumberHolder(defStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(2); }, 20000); @@ -162,13 +162,13 @@ describe("Items - Metal Powder", () => { // Making sure modifier is not applied without holding item const defValue = new Utils.NumberHolder(defStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "METAL_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue); expect(defValue.value / defStat).toBe(1); }, 20000); diff --git a/src/test/items/quick_powder.test.ts b/src/test/items/quick_powder.test.ts index 905d023ad8b..d30111cbd6a 100644 --- a/src/test/items/quick_powder.test.ts +++ b/src/test/items/quick_powder.test.ts @@ -74,13 +74,13 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new Utils.NumberHolder(spdStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); }, 20000); @@ -107,13 +107,13 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new Utils.NumberHolder(spdStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); }, 20000); @@ -140,13 +140,13 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new Utils.NumberHolder(spdStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(2); }, 20000); @@ -162,13 +162,13 @@ describe("Items - Quick Powder", () => { // Making sure modifier is not applied without holding item const spdValue = new Utils.NumberHolder(spdStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "QUICK_POWDER" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue); expect(spdValue.value / spdStat).toBe(1); }, 20000); diff --git a/src/test/items/temp_stat_stage_booster.test.ts b/src/test/items/temp_stat_stage_booster.test.ts index 6186799623a..3e496d1bbf8 100644 --- a/src/test/items/temp_stat_stage_booster.test.ts +++ b/src/test/items/temp_stat_stage_booster.test.ts @@ -9,7 +9,7 @@ import { Abilities } from "#app/enums/abilities"; import { TempStatStageBoosterModifier } from "#app/modifier/modifier"; import { Mode } from "#app/ui/ui"; import { Button } from "#app/enums/buttons"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; diff --git a/src/test/items/thick_club.test.ts b/src/test/items/thick_club.test.ts index d349a1ad7b9..08b19250ea7 100644 --- a/src/test/items/thick_club.test.ts +++ b/src/test/items/thick_club.test.ts @@ -74,13 +74,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); }, 20000); @@ -96,13 +96,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); }, 20000); @@ -118,13 +118,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); }, 20000); @@ -155,13 +155,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); }, 20000); @@ -192,13 +192,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(2); }, 20000); @@ -214,13 +214,13 @@ describe("Items - Thick Club", () => { // Making sure modifier is not applied without holding item const atkValue = new Utils.NumberHolder(atkStat); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); // Giving Eviolite to party member and testing if it applies - partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); - partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); + game.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType([], [ "THICK_CLUB" ])!.newModifier(partyMember), true); + game.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue); expect(atkValue.value / atkStat).toBe(1); }, 20000); diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index 2d7484b4eb5..721f682f778 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -1,7 +1,9 @@ +import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; -import Move, { allMoves } from "#app/data/move"; +import type Move from "#app/data/move"; +import { allMoves } from "#app/data/move"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; @@ -12,6 +14,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +let globalScene: BattleScene; describe("Moves - Aurora Veil", () => { let phaserGame: Phaser.Game; @@ -31,6 +34,7 @@ describe("Moves - Aurora Veil", () => { beforeEach(() => { game = new GameManager(phaserGame); + globalScene = game.scene; game.override.battleType("single"); game.override.ability(Abilities.NONE); game.override.moveset([ Moves.ABSORB, Moves.ROCK_SLIDE, Moves.TACKLE ]); @@ -110,8 +114,8 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const multiplierHolder = new NumberHolder(1); const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder); + if (globalScene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) { + globalScene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/burning_jealousy.test.ts b/src/test/moves/burning_jealousy.test.ts index fe2735cfa96..abe2c09bb72 100644 --- a/src/test/moves/burning_jealousy.test.ts +++ b/src/test/moves/burning_jealousy.test.ts @@ -82,7 +82,8 @@ describe("Moves - Burning Jealousy", () => { expect(enemy.status?.effect).toBeUndefined(); }); - it.skip("should ignore weakness policy", async () => { // TODO: Make this test if WP is implemented + // TODO: Make this test if WP is implemented + it.todo("should ignore weakness policy", async () => { await game.classicMode.startBattle(); }); diff --git a/src/test/moves/destiny_bond.test.ts b/src/test/moves/destiny_bond.test.ts index 4b4c8782862..e668aee2191 100644 --- a/src/test/moves/destiny_bond.test.ts +++ b/src/test/moves/destiny_bond.test.ts @@ -1,4 +1,5 @@ -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import type { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#app/data/arena-tag"; import { allMoves } from "#app/data/move"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; diff --git a/src/test/moves/dragon_rage.test.ts b/src/test/moves/dragon_rage.test.ts index d5536ff9d2f..a2350960546 100644 --- a/src/test/moves/dragon_rage.test.ts +++ b/src/test/moves/dragon_rage.test.ts @@ -1,7 +1,7 @@ import { Stat } from "#enums/stat"; import { Type } from "#enums/type"; import { Species } from "#app/enums/species"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; diff --git a/src/test/moves/dynamax_cannon.test.ts b/src/test/moves/dynamax_cannon.test.ts index 269374f7514..033d8960bad 100644 --- a/src/test/moves/dynamax_cannon.test.ts +++ b/src/test/moves/dynamax_cannon.test.ts @@ -81,7 +81,7 @@ describe("Moves - Dynamax Cannon", () => { const phase = game.scene.getCurrentPhase() as MoveEffectPhase; expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 - vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); + vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(120); }, 20000); @@ -98,7 +98,7 @@ describe("Moves - Dynamax Cannon", () => { const phase = game.scene.getCurrentPhase() as MoveEffectPhase; expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 - vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); + vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(140); }, 20000); @@ -115,7 +115,7 @@ describe("Moves - Dynamax Cannon", () => { const phase = game.scene.getCurrentPhase() as MoveEffectPhase; expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 - vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); + vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(160); }, 20000); @@ -132,7 +132,7 @@ describe("Moves - Dynamax Cannon", () => { const phase = game.scene.getCurrentPhase() as MoveEffectPhase; expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 - vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); + vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(180); }, 20000); @@ -149,7 +149,7 @@ describe("Moves - Dynamax Cannon", () => { const phase = game.scene.getCurrentPhase() as MoveEffectPhase; expect(phase.move.moveId).toBe(dynamaxCannon.id); // Force level cap to be 100 - vi.spyOn(phase.getFirstTarget()!.scene, "getMaxExpLevel").mockReturnValue(100); + vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100); await game.phaseInterceptor.to(DamageAnimPhase, false); expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200); }, 20000); diff --git a/src/test/moves/effectiveness.test.ts b/src/test/moves/effectiveness.test.ts index 5ad258d7990..7742178f595 100644 --- a/src/test/moves/effectiveness.test.ts +++ b/src/test/moves/effectiveness.test.ts @@ -23,7 +23,7 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE); if (teraType !== undefined) { - overrideHeldItems(game.scene, target, false); + overrideHeldItems(target, false); } expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected); diff --git a/src/test/moves/fissure.test.ts b/src/test/moves/fissure.test.ts index 15dabb971cc..0975a87b2b1 100644 --- a/src/test/moves/fissure.test.ts +++ b/src/test/moves/fissure.test.ts @@ -1,6 +1,6 @@ import { Stat } from "#enums/stat"; import { Species } from "#app/enums/species"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/flame_burst.test.ts b/src/test/moves/flame_burst.test.ts index feedee3b7bc..9dea930d7e8 100644 --- a/src/test/moves/flame_burst.test.ts +++ b/src/test/moves/flame_burst.test.ts @@ -1,6 +1,6 @@ import { allAbilities } from "#app/data/ability"; import { Abilities } from "#app/enums/abilities"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/geomancy.test.ts b/src/test/moves/geomancy.test.ts index 6e2f40b9144..9ff3a1e7a7d 100644 --- a/src/test/moves/geomancy.test.ts +++ b/src/test/moves/geomancy.test.ts @@ -1,4 +1,5 @@ -import { EffectiveStat, Stat } from "#enums/stat"; +import type { EffectiveStat } from "#enums/stat"; +import { Stat } from "#enums/stat"; import { MoveResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index af14d9273e6..424f43e155c 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -1,8 +1,10 @@ +import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; -import Move, { allMoves } from "#app/data/move"; +import type Move from "#app/data/move"; +import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { NumberHolder } from "#app/utils"; import { Moves } from "#enums/moves"; @@ -11,6 +13,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +let globalScene: BattleScene; describe("Moves - Light Screen", () => { let phaserGame: Phaser.Game; @@ -30,6 +33,7 @@ describe("Moves - Light Screen", () => { beforeEach(() => { game = new GameManager(phaserGame); + globalScene = game.scene; game.override.battleType("single"); game.override.ability(Abilities.NONE); game.override.moveset([ Moves.ABSORB, Moves.DAZZLING_GLEAM, Moves.TACKLE ]); @@ -93,8 +97,8 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const multiplierHolder = new NumberHolder(1); const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder); + if (globalScene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) { + globalScene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/order_up.test.ts b/src/test/moves/order_up.test.ts index d0b52dc1a9d..a9281b121b2 100644 --- a/src/test/moves/order_up.test.ts +++ b/src/test/moves/order_up.test.ts @@ -1,7 +1,8 @@ import { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; -import { EffectiveStat, Stat } from "#enums/stat"; +import type { EffectiveStat } from "#enums/stat"; +import { Stat } from "#enums/stat"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/parting_shot.test.ts b/src/test/moves/parting_shot.test.ts index cfdf6c15966..30ad3660a32 100644 --- a/src/test/moves/parting_shot.test.ts +++ b/src/test/moves/parting_shot.test.ts @@ -75,7 +75,7 @@ describe("Moves - Parting Shot", () => { } ); - it.skip( // TODO: fix this bug to pass the test! + it.todo( // TODO: fix this bug to pass the test! "Parting shot should fail if target is -6/-6 de-buffed", async () => { game.override.moveset([ Moves.PARTING_SHOT, Moves.MEMENTO, Moves.SPLASH ]); @@ -117,7 +117,7 @@ describe("Moves - Parting Shot", () => { } ); - it.skip( // TODO: fix this bug to pass the test! + it.todo( // TODO: fix this bug to pass the test! "Parting shot shouldn't allow switch out when mist is active", async () => { game.override @@ -138,7 +138,7 @@ describe("Moves - Parting Shot", () => { } ); - it.skip( // TODO: fix this bug to pass the test! + it.todo( // TODO: fix this bug to pass the test! "Parting shot shouldn't allow switch out against clear body ability", async () => { game.override @@ -158,7 +158,7 @@ describe("Moves - Parting Shot", () => { } ); - it.skip( // TODO: fix this bug to pass the test! + it.todo( // TODO: fix this bug to pass the test! "Parting shot should de-buff and not fail if no party available to switch - party size 1", async () => { await game.startBattle([ Species.MURKROW ]); @@ -175,7 +175,7 @@ describe("Moves - Parting Shot", () => { } ); - it.skip( // TODO: fix this bug to pass the test! + it.todo( // TODO: fix this bug to pass the test! "Parting shot regularly not fail if no party available to switch - party fainted", async () => { await game.startBattle([ Species.MURKROW, Species.MEOWTH ]); diff --git a/src/test/moves/purify.test.ts b/src/test/moves/purify.test.ts index 171f94a611a..d72b4a87d2a 100644 --- a/src/test/moves/purify.test.ts +++ b/src/test/moves/purify.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { Status } from "#app/data/status-effect"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index 3bf415ea75c..aa9f2095c89 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -1,8 +1,10 @@ +import type BattleScene from "#app/battle-scene"; import { ArenaTagSide } from "#app/data/arena-tag"; -import Move, { allMoves } from "#app/data/move"; +import type Move from "#app/data/move"; +import { allMoves } from "#app/data/move"; import { Abilities } from "#app/enums/abilities"; import { ArenaTagType } from "#app/enums/arena-tag-type"; -import Pokemon from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { NumberHolder } from "#app/utils"; import { Moves } from "#enums/moves"; @@ -11,6 +13,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +let globalScene: BattleScene; describe("Moves - Reflect", () => { let phaserGame: Phaser.Game; @@ -30,6 +33,7 @@ describe("Moves - Reflect", () => { beforeEach(() => { game = new GameManager(phaserGame); + globalScene = game.scene; game.override.battleType("single"); game.override.ability(Abilities.NONE); game.override.moveset([ Moves.ABSORB, Moves.ROCK_SLIDE, Moves.TACKLE ]); @@ -93,8 +97,8 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) = const multiplierHolder = new NumberHolder(1); const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { - defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); + if (globalScene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) { + globalScene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, attacker, move.category, multiplierHolder); } return move.power * multiplierHolder.value; diff --git a/src/test/moves/round.test.ts b/src/test/moves/round.test.ts index fd318d30c1e..1d7c91bcbd4 100644 --- a/src/test/moves/round.test.ts +++ b/src/test/moves/round.test.ts @@ -1,6 +1,6 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; -import { MoveEffectPhase } from "#app/phases/move-effect-phase"; +import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index 8e418858e8d..fd21bb3c6c1 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -2,7 +2,8 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { allMoves } from "#app/data/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { MoveResult, TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; diff --git a/src/test/moves/steamroller.test.ts b/src/test/moves/steamroller.test.ts index 9d16643ec5d..f641c58c2d1 100644 --- a/src/test/moves/steamroller.test.ts +++ b/src/test/moves/steamroller.test.ts @@ -1,7 +1,7 @@ import { BattlerIndex } from "#app/battle"; import { allMoves } from "#app/data/move"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { DamageCalculationResult } from "#app/field/pokemon"; +import type { DamageCalculationResult } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index d3239856ed7..e50fe041b0a 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -1,6 +1,7 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; -import { MoveResult, TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/substitute.test.ts b/src/test/moves/substitute.test.ts index 14ab4ab5054..18b0c6ea536 100644 --- a/src/test/moves/substitute.test.ts +++ b/src/test/moves/substitute.test.ts @@ -3,7 +3,7 @@ import { ArenaTagSide } from "#app/data/arena-tag"; import { SubstituteTag, TrappedTag } from "#app/data/battler-tags"; import { allMoves, StealHeldItemChanceAttr } from "#app/data/move"; import { MoveResult } from "#app/field/pokemon"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import GameManager from "#app/test/utils/gameManager"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index 2aee4d2604a..c154d3c7c2c 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -1,7 +1,8 @@ import { Stat } from "#enums/stat"; import { StockpilingTag } from "#app/data/battler-tags"; import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { MoveResult, TurnMove } from "#app/field/pokemon"; +import type { TurnMove } from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { MovePhase } from "#app/phases/move-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { Abilities } from "#enums/abilities"; diff --git a/src/test/moves/tera_blast.test.ts b/src/test/moves/tera_blast.test.ts index 311ac0f0d0e..44dc29f68b5 100644 --- a/src/test/moves/tera_blast.test.ts +++ b/src/test/moves/tera_blast.test.ts @@ -87,7 +87,7 @@ describe("Moves - Tera Blast", () => { }); // Currently abilities are bugged and can't see when a move's category is changed - it.skip("uses the higher stat of the user's Atk and SpAtk for damage calculation", async () => { + it.todo("uses the higher stat of the user's Atk and SpAtk for damage calculation", async () => { game.override.enemyAbility(Abilities.TOXIC_DEBRIS); await game.startBattle(); diff --git a/src/test/moves/thunder_wave.test.ts b/src/test/moves/thunder_wave.test.ts index 5551451e59b..21e215a51f2 100644 --- a/src/test/moves/thunder_wave.test.ts +++ b/src/test/moves/thunder_wave.test.ts @@ -1,4 +1,4 @@ -import { EnemyPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon } from "#app/field/pokemon"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index bdd59ed0ac8..c2d1c5aaee8 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -1,5 +1,7 @@ -import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; -import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data"; +import type { ArenaTrapTag } from "#app/data/arena-tag"; +import { ArenaTagSide } from "#app/data/arena-tag"; +import type { SessionSaveData } from "#app/system/game-data"; +import { decrypt, encrypt, GameData } from "#app/system/game-data"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { Moves } from "#enums/moves"; @@ -116,8 +118,7 @@ describe("Moves - Toxic Spikes", () => { it("should persist through reload", async () => { game.override.startingWave(1); - const scene = game.scene; - const gameData = new GameData(scene); + const gameData = new GameData(); await game.classicMode.runToSummon([ Species.MIGHTYENA ]); @@ -128,10 +129,10 @@ describe("Moves - Toxic Spikes", () => { await game.phaseInterceptor.to("BattleEndPhase"); await game.toNextWave(); - const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene); + const sessionData : SessionSaveData = gameData["getSessionSaveData"](); localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true)); const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true)); - gameData.loadSession(game.scene, 0, recoveredData); + gameData.loadSession(0, recoveredData); expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); localStorage.removeItem("sessionTestData"); diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index ee67f1b5d39..69fa9b5465f 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -4,15 +4,15 @@ import { CommandPhase } from "#app/phases/command-phase"; import { MessagePhase } from "#app/phases/message-phase"; import { MysteryEncounterBattlePhase, MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import { VictoryPhase } from "#app/phases/victory-phase"; -import MessageUiHandler from "#app/ui/message-ui-handler"; -import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; -import PartyUiHandler from "#app/ui/party-ui-handler"; -import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import type MessageUiHandler from "#app/ui/message-ui-handler"; +import type MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; +import type PartyUiHandler from "#app/ui/party-ui-handler"; +import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { isNullOrUndefined } from "#app/utils"; import { Button } from "#enums/buttons"; import { StatusEffect } from "#enums/status-effect"; -import GameManager from "#test/utils/gameManager"; +import type GameManager from "#test/utils/gameManager"; import { expect, vi } from "vitest"; /** @@ -52,7 +52,7 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { game.scene.clearPhaseQueue(); game.scene.clearPhaseQueueSplice(); - game.scene.unshiftPhase(new VictoryPhase(game.scene, 0)); + game.scene.unshiftPhase(new VictoryPhase(0)); game.endPhase(); }); @@ -170,7 +170,7 @@ export async function skipBattleRunMysteryEncounterRewardsPhase(game: GameManage p.status = new Status(StatusEffect.FAINT); game.scene.field.remove(p); }); - game.scene.pushPhase(new VictoryPhase(game.scene, 0)); + game.scene.pushPhase(new VictoryPhase(0)); game.phaseInterceptor.superEndPhase(); game.setMode(Mode.MESSAGE); await game.phaseInterceptor.to(MysteryEncounterRewardsPhase, runRewardsPhase); diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index 7d783958422..5a01b8a7379 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -7,7 +7,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; @@ -78,8 +78,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(ATrainersTestEncounter.onInit).toBeDefined(); - ATrainersTestEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + ATrainersTestEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(ATrainersTestEncounter.dialogueTokens?.statTrainerName).toBeDefined(); expect(ATrainersTestEncounter.misc.trainerType).toBeDefined(); diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 61d8aaa9f5a..797c062dafe 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -5,7 +5,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 727a3993d9b..9a6dae53901 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -7,7 +7,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { AnOfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -91,8 +91,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(AnOfferYouCantRefuseEncounter.onInit).toBeDefined(); - AnOfferYouCantRefuseEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + AnOfferYouCantRefuseEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined(); @@ -130,7 +130,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { const price = scene.currentBattle.mysteryEncounter!.misc.price; - expect(updateMoneySpy).toHaveBeenCalledWith(scene, price); + expect(updateMoneySpy).toHaveBeenCalledWith(price); expect(scene.money).toBe(initialMoney + price); }); @@ -220,7 +220,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { const price = scene.currentBattle.mysteryEncounter!.misc.price; - expect(updateMoneySpy).toHaveBeenCalledWith(scene, price); + expect(updateMoneySpy).toHaveBeenCalledWith(price); expect(scene.money).toBe(initialMoney + price); }); diff --git a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 2507b94e5ae..f980b0cb20a 100644 --- a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -5,7 +5,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { BerryModifier } from "#app/modifier/modifier"; @@ -80,8 +80,8 @@ describe("Berries Abound - Mystery Encounter", () => { expect(BerriesAboundEncounter.onInit).toBeDefined(); - BerriesAboundEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + BerriesAboundEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); const config = BerriesAboundEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); @@ -192,7 +192,7 @@ describe("Berries Abound - Mystery Encounter", () => { // Should be enraged expect(enemyField[0].summonData.statStages).toEqual([ 0, 1, 0, 1, 1, 0, 0 ]); - expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}:option.2.selected_bad`); + expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selected_bad`); }); it("should start battle if fastest pokemon is slower than boss above wave 50", async () => { @@ -216,7 +216,7 @@ describe("Berries Abound - Mystery Encounter", () => { // Should be enraged expect(enemyField[0].summonData.statStages).toEqual([ 1, 1, 1, 1, 1, 0, 0 ]); - expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}:option.2.selected_bad`); + expect(encounterTextSpy).toHaveBeenCalledWith(`${namespace}:option.2.selected_bad`); }); it("Should skip battle when fastest pokemon is faster than boss", async () => { @@ -241,7 +241,7 @@ describe("Berries Abound - Mystery Encounter", () => { expect(option.modifierTypeOption.type.id).toContain("BERRY"); } - expect(EncounterDialogueUtils.showEncounterText).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}:option.2.selected`); + expect(EncounterDialogueUtils.showEncounterText).toHaveBeenCalledWith(`${namespace}:option.2.selected`); expect(EncounterPhaseUtils.leaveEncounterWithoutBattle).toBeCalled(); }); }); diff --git a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index e0f37c7e045..1ff523909da 100644 --- a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -209,8 +209,8 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(BugTypeSuperfanEncounter.onInit).toBeDefined(); - BugTypeSuperfanEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + BugTypeSuperfanEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); const config = BugTypeSuperfanEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); @@ -384,7 +384,7 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { await game.phaseInterceptor.run(MysteryEncounterRewardsPhase); expect(selectOptionSpy).toHaveBeenCalledTimes(1); - const optionData = selectOptionSpy.mock.calls[0][1]; + const optionData = selectOptionSpy.mock.calls[0][0]; expect(PHYSICAL_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[0].label)).toBe(true); expect(SPECIAL_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[1].label)).toBe(true); expect(STATUS_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[2].label)).toBe(true); diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index e7ea6eea0ea..562b8322baa 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -10,8 +10,9 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; +import type BattleScene from "#app/battle-scene"; +import type Pokemon from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -22,11 +23,12 @@ import { TrainerType } from "#enums/trainer-type"; import { Abilities } from "#enums/abilities"; import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { Button } from "#enums/buttons"; -import PartyUiHandler from "#app/ui/party-ui-handler"; -import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; -import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import type PartyUiHandler from "#app/ui/party-ui-handler"; +import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { Type } from "#enums/type"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; @@ -105,8 +107,8 @@ describe("Clowning Around - Mystery Encounter", () => { expect(ClowningAroundEncounter.onInit).toBeDefined(); - ClowningAroundEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + ClowningAroundEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); const config = ClowningAroundEncounter.enemyPartyConfigs[0]; expect(config.doubleBattle).toBe(true); @@ -255,29 +257,29 @@ describe("Clowning Around - Mystery Encounter", () => { // 2 Sitrus Berries on lead scene.modifiers = []; - let itemType = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType; + let itemType = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ]) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); // 2 Ganlon Berries on lead - itemType = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ]) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); // 5 Golden Punch on lead (ultra) - itemType = generateModifierType(scene, modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); // 5 Lucky Egg on lead (ultra) - itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); // 3 Soothe Bell on lead (great tier, but counted as ultra by this ME) - itemType = generateModifierType(scene, modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType); // 5 Soul Dew on lead (rogue) - itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType); // 2 Golden Egg on lead (rogue) - itemType = generateModifierType(scene, modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[0], 2, itemType); // 5 Soul Dew on second party pokemon (these should not change) - itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; + itemType = generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getPlayerParty()[1], 5, itemType); await runMysteryEncounterToEnd(game, 2); diff --git a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index 147930f05d1..795e6b6650b 100644 --- a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -5,7 +5,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; diff --git a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index c226d60a9b4..f99aa24805e 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -5,12 +5,12 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import type { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { BerryModifier, HealingBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; @@ -100,7 +100,7 @@ describe("Delibird-y - Mystery Encounter", () => { const price = (scene.currentBattle.mysteryEncounter?.options[0].requirements[0] as MoneyRequirement).requiredMoney; - expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false); + expect(updateMoneySpy).toHaveBeenCalledWith(-price, true, false); expect(scene.money).toBe(initialMoney - price); }); @@ -121,7 +121,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Max Amulet Coins scene.modifiers = []; - const amuletCoin = generateModifierType(scene, modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; + const amuletCoin = generateModifierType(modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; amuletCoin.stackCount = 5; await scene.addModifier(amuletCoin, true, false, false, true); await scene.updateModifiers(true); @@ -190,7 +190,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 2 Sitrus berries on party lead scene.modifiers = []; - const sitrus = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ])!; + const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; sitrusMod.stackCount = 2; await scene.addModifier(sitrusMod, true, false, false, true); @@ -211,7 +211,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Reviver Seed on party lead scene.modifiers = []; - const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED)!; + const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -232,10 +232,10 @@ describe("Delibird-y - Mystery Encounter", () => { // 99 Candy Jars scene.modifiers = []; - const candyJar = generateModifierType(scene, modifierTypes.CANDY_JAR)!.newModifier() as LevelIncrementBoosterModifier; + const candyJar = generateModifierType(modifierTypes.CANDY_JAR)!.newModifier() as LevelIncrementBoosterModifier; candyJar.stackCount = 99; await scene.addModifier(candyJar, true, false, false, true); - const sitrus = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ])!; + const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; // Sitrus berries on party const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; @@ -261,12 +261,12 @@ describe("Delibird-y - Mystery Encounter", () => { // 3 Berry Pouches scene.modifiers = []; - const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; + const healingCharm = generateModifierType(modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; healingCharm.stackCount = 3; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED)!; + const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -290,7 +290,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]); await scene.addModifier(modifier, true, false, false, true); await scene.updateModifiers(true); @@ -318,7 +318,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED)!; + const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -352,7 +352,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 2 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 2; await scene.addModifier(modifier, true, false, false, true); @@ -373,7 +373,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -394,12 +394,12 @@ describe("Delibird-y - Mystery Encounter", () => { // 5 Healing Charms scene.modifiers = []; - const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; + const healingCharm = generateModifierType(modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; healingCharm.stackCount = 5; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Soul Dew on party lead - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -423,7 +423,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Reviver Seed on party lead scene.modifiers = []; - const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED)!; + const revSeed = generateModifierType(modifierTypes.REVIVER_SEED)!; const modifier = revSeed.newModifier(scene.getPlayerParty()[0]); await scene.addModifier(modifier, true, false, false, true); await scene.updateModifiers(true); @@ -452,7 +452,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); diff --git a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 1869a4d3c3c..e30aaadbf85 100644 --- a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter"; diff --git a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts index a6f925274c3..bc9be246e10 100644 --- a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -5,7 +5,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 215cab5c65a..2f668dd7f50 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -11,7 +11,7 @@ import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { Type } from "#enums/type"; import { Status } from "#app/data/status-effect"; @@ -104,8 +104,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(FieryFalloutEncounter.onInit).toBeDefined(); - FieryFalloutEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + FieryFalloutEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([ { diff --git a/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts b/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts index a5098f682eb..8c869812f39 100644 --- a/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fight-or-flight-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; @@ -75,8 +75,8 @@ describe("Fight or Flight - Mystery Encounter", () => { expect(FightOrFlightEncounter.onInit).toBeDefined(); - FightOrFlightEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + FightOrFlightEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); const config = FightOrFlightEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); diff --git a/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts b/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts index 2f4f6bed288..44ddbb8f7ba 100644 --- a/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fun-and-games-encounter.test.ts @@ -6,7 +6,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -95,7 +95,7 @@ describe("Fun And Games! - Mystery Encounter", () => { expect(encounter.onInit).toBeDefined(); - const onInitResult = onInit!(scene); + const onInitResult = onInit!(); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts index 2c226df3c8c..fb5801c941a 100644 --- a/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/global-trade-system-encounter.test.ts @@ -5,7 +5,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; @@ -221,7 +221,7 @@ describe("Global Trade System - Mystery Encounter", () => { // Set 2 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 2; await scene.addModifier(modifier, true, false, false, true); @@ -246,7 +246,7 @@ describe("Global Trade System - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW)!; + const soulDew = generateModifierType(modifierTypes.SOUL_DEW)!; const modifier = soulDew.newModifier(scene.getPlayerParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 51f759c9268..17e324f29f0 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -11,7 +11,7 @@ import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "../en import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; import i18next from "i18next"; @@ -83,8 +83,8 @@ describe("Lost at Sea - Mystery Encounter", () => { expect(LostAtSeaEncounter.onInit).toBeDefined(); - LostAtSeaEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + LostAtSeaEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25"); expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name")); diff --git a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 7bbc505dd8e..7729fa40599 100644 --- a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -6,7 +6,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -89,8 +89,8 @@ describe("Mysterious Challengers - Mystery Encounter", () => { expect(encounter.onInit).toBeDefined(); - encounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + encounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(encounter.enemyPartyConfigs).toBeDefined(); expect(encounter.enemyPartyConfigs.length).toBe(3); diff --git a/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts b/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts index e063a4f3349..e8f2af0de5f 100644 --- a/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/part-timer-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -107,7 +107,7 @@ describe("Part-Timer - Mystery Encounter", () => { }); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }); - expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(1), true, false); + expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(1), true, false); // Expect PP of mon's moves to have been reduced to 2 const moves = scene.getPlayerParty()[0].moveset; for (const move of moves) { @@ -127,7 +127,7 @@ describe("Part-Timer - Mystery Encounter", () => { }); await runMysteryEncounterToEnd(game, 1, { pokemonNo: 2 }); - expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(4), true, false); + expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(4), true, false); // Expect PP of mon's moves to have been reduced to 2 const moves = scene.getPlayerParty()[1].moveset; for (const move of moves) { @@ -172,7 +172,7 @@ describe("Part-Timer - Mystery Encounter", () => { }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 3 }); - expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(1), true, false); + expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(1), true, false); // Expect PP of mon's moves to have been reduced to 2 const moves = scene.getPlayerParty()[2].moveset; for (const move of moves) { @@ -192,7 +192,7 @@ describe("Part-Timer - Mystery Encounter", () => { }); await runMysteryEncounterToEnd(game, 2, { pokemonNo: 4 }); - expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(4), true, false); + expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(4), true, false); // Expect PP of mon's moves to have been reduced to 2 const moves = scene.getPlayerParty()[3].moveset; for (const move of moves) { @@ -259,7 +259,7 @@ describe("Part-Timer - Mystery Encounter", () => { scene.getPlayerParty()[0].moveset = [ new PokemonMove(Moves.ATTRACT) ]; await runMysteryEncounterToEnd(game, 3); - expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene, scene.getWaveMoneyAmount(2.5), true, false); + expect(EncounterPhaseUtils.updatePlayerMoney).toHaveBeenCalledWith(scene.getWaveMoneyAmount(2.5), true, false); // Expect PP of mon's moves to have been reduced to 2 const moves = scene.getPlayerParty()[0].moveset; for (const move of moves) { diff --git a/src/test/mystery-encounter/encounters/safari-zone.test.ts b/src/test/mystery-encounter/encounters/safari-zone.test.ts index 5699afe254f..a807805b81c 100644 --- a/src/test/mystery-encounter/encounters/safari-zone.test.ts +++ b/src/test/mystery-encounter/encounters/safari-zone.test.ts @@ -5,7 +5,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; @@ -85,8 +85,8 @@ describe("Safari Zone - Mystery Encounter", () => { expect(encounter.onInit).toBeDefined(); - encounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + encounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 02375d83b98..a00cca5083c 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { Abilities } from "#enums/abilities"; @@ -115,8 +115,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(TeleportingHijinksEncounter.onInit).toBeDefined(); - TeleportingHijinksEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + TeleportingHijinksEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(TeleportingHijinksEncounter.misc.price).toBeDefined(); expect(TeleportingHijinksEncounter.dialogueTokens.price).toBeDefined(); diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index 87ccff71e22..e6f8127b776 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -6,7 +6,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; @@ -96,8 +96,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(encounter.onInit).toBeDefined(); - encounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + encounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(encounter.enemyPartyConfigs).toBeDefined(); expect(encounter.enemyPartyConfigs.length).toBe(1); diff --git a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index e90bc4efe56..4fd96f8d5bc 100644 --- a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PlayerPokemon } from "#app/field/pokemon"; import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { getSalesmanSpeciesOffer, ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter"; @@ -88,8 +88,8 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(ThePokemonSalesmanEncounter.onInit).toBeDefined(); - ThePokemonSalesmanEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + ThePokemonSalesmanEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(ThePokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined(); expect(ThePokemonSalesmanEncounter.dialogueTokens?.price).toBeDefined(); @@ -133,7 +133,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { const price = scene.currentBattle.mysteryEncounter!.misc.price; - expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false); + expect(updateMoneySpy).toHaveBeenCalledWith(-price, true, false); expect(scene.money).toBe(initialMoney - price); }); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 5c965b13bd4..3c0e75a2195 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -9,7 +9,7 @@ import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; import { Nature } from "#enums/nature"; import { BerryType } from "#enums/berry-type"; @@ -97,8 +97,8 @@ describe("The Strong Stuff - Mystery Encounter", () => { expect(TheStrongStuffEncounter.onInit).toBeDefined(); - TheStrongStuffEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + TheStrongStuffEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([ { diff --git a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts index 701a3c94add..e087bc5c180 100644 --- a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -6,7 +6,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -101,8 +101,8 @@ describe("The Winstrate Challenge - Mystery Encounter", () => { expect(encounter.onInit).toBeDefined(); - encounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + encounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(encounter.enemyPartyConfigs).toBeDefined(); expect(encounter.enemyPartyConfigs.length).toBe(5); @@ -363,7 +363,7 @@ async function skipBattleToNextBattle(game: GameManager, isFinalBattle: boolean game.scene.field.remove(p); }); game.phaseInterceptor["onHold"] = []; - game.scene.pushPhase(new VictoryPhase(game.scene, 0)); + game.scene.pushPhase(new VictoryPhase(0)); game.phaseInterceptor.superEndPhase(); if (isFinalBattle) { await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); diff --git a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index f8d96487092..395e33e818a 100644 --- a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -9,7 +9,7 @@ import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PokemonMove } from "#app/field/pokemon"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; @@ -81,8 +81,8 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(TrashToTreasureEncounter.onInit).toBeDefined(); - TrashToTreasureEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + TrashToTreasureEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([ { diff --git a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index c811bda673d..39904c030a3 100644 --- a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -22,7 +22,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { BerryType } from "#enums/berry-type"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; -import { BerryModifier } from "#app/modifier/modifier"; +import type { BerryModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Abilities } from "#enums/abilities"; @@ -85,8 +85,8 @@ describe("Uncommon Breed - Mystery Encounter", () => { expect(UncommonBreedEncounter.onInit).toBeDefined(); - UncommonBreedEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + UncommonBreedEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); const config = UncommonBreedEncounter.enemyPartyConfigs[0]; expect(config).toBeDefined(); @@ -213,11 +213,11 @@ describe("Uncommon Breed - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); // Berries on party lead - const sitrus = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.SITRUS ])!; + const sitrus = generateModifierType(modifierTypes.BERRY, [ BerryType.SITRUS ])!; const sitrusMod = sitrus.newModifier(scene.getPlayerParty()[0]) as BerryModifier; sitrusMod.stackCount = 2; await scene.addModifier(sitrusMod, true, false, false, true); - const ganlon = generateModifierType(scene, modifierTypes.BERRY, [ BerryType.GANLON ])!; + const ganlon = generateModifierType(modifierTypes.BERRY, [ BerryType.GANLON ])!; const ganlonMod = ganlon.newModifier(scene.getPlayerParty()[0]) as BerryModifier; ganlonMod.stackCount = 3; await scene.addModifier(ganlonMod, true, false, false, true); diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index ec9cf1509b1..669a99b92cd 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -6,7 +6,7 @@ import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -84,8 +84,8 @@ describe("Weird Dream - Mystery Encounter", () => { expect(WeirdDreamEncounter.onInit).toBeDefined(); - WeirdDreamEncounter.populateDialogueTokensFromRequirements(scene); - const onInitResult = onInit!(scene); + WeirdDreamEncounter.populateDialogueTokensFromRequirements(); + const onInitResult = onInit!(); expect(loadBgmSpy).toHaveBeenCalled(); expect(onInitResult).toBe(true); diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index d17b5c8a587..f0057fea7f0 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { speciesStarterCosts } from "#app/data/balance/starters"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { getEncounterText, queueEncounterMessage, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -39,12 +39,12 @@ describe("Mystery Encounter Utils", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); - let result = getRandomPlayerPokemon(scene); + let result = getRandomPlayerPokemon(); expect(result.species.speciesId).toBe(Species.MANAPHY); game.override.seed("random2"); - result = getRandomPlayerPokemon(scene); + result = getRandomPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -59,12 +59,12 @@ describe("Mystery Encounter Utils", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); - let result = getRandomPlayerPokemon(scene); + let result = getRandomPlayerPokemon(); expect(result.species.speciesId).toBe(Species.MANAPHY); game.override.seed("random2"); - result = getRandomPlayerPokemon(scene); + result = getRandomPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -78,12 +78,12 @@ describe("Mystery Encounter Utils", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); - let result = getRandomPlayerPokemon(scene, true); + let result = getRandomPlayerPokemon(true); expect(result.species.speciesId).toBe(Species.MANAPHY); game.override.seed("random2"); - result = getRandomPlayerPokemon(scene, true); + result = getRandomPlayerPokemon(true); expect(result.species.speciesId).toBe(Species.MANAPHY); }); @@ -97,12 +97,12 @@ describe("Mystery Encounter Utils", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); - let result = getRandomPlayerPokemon(scene, true, false); + let result = getRandomPlayerPokemon(true, false); expect(result.species.speciesId).toBe(Species.MANAPHY); game.override.seed("random2"); - result = getRandomPlayerPokemon(scene, true, false); + result = getRandomPlayerPokemon(true, false); expect(result.species.speciesId).toBe(Species.MANAPHY); }); @@ -116,12 +116,12 @@ describe("Mystery Encounter Utils", () => { // Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal) game.override.seed("random"); - let result = getRandomPlayerPokemon(scene, true, false, true); + let result = getRandomPlayerPokemon(true, false, true); expect(result.species.speciesId).toBe(Species.ARCEUS); game.override.seed("random2"); - result = getRandomPlayerPokemon(scene, true, false, true); + result = getRandomPlayerPokemon(true, false, true); expect(result.species.speciesId).toBe(Species.ARCEUS); }); }); @@ -131,7 +131,7 @@ describe("Mystery Encounter Utils", () => { const party = scene.getPlayerParty(); party[0].level = 100; - const result = getHighestLevelPlayerPokemon(scene); + const result = getHighestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -139,7 +139,7 @@ describe("Mystery Encounter Utils", () => { const party = scene.getPlayerParty(); party[1].level = 100; - const result = getHighestLevelPlayerPokemon(scene); + const result = getHighestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.MANAPHY); }); @@ -148,7 +148,7 @@ describe("Mystery Encounter Utils", () => { party[0].level = 100; party[1].level = 100; - const result = getHighestLevelPlayerPokemon(scene); + const result = getHighestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -160,7 +160,7 @@ describe("Mystery Encounter Utils", () => { party[0].updateInfo(); party[1].level = 10; - const result = getHighestLevelPlayerPokemon(scene, true); + const result = getHighestLevelPlayerPokemon(true); expect(result.species.speciesId).toBe(Species.MANAPHY); }); }); @@ -170,7 +170,7 @@ describe("Mystery Encounter Utils", () => { const party = scene.getPlayerParty(); party[0].level = 100; - const result = getLowestLevelPlayerPokemon(scene); + const result = getLowestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.MANAPHY); }); @@ -178,7 +178,7 @@ describe("Mystery Encounter Utils", () => { const party = scene.getPlayerParty(); party[1].level = 100; - const result = getLowestLevelPlayerPokemon(scene); + const result = getLowestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -187,7 +187,7 @@ describe("Mystery Encounter Utils", () => { party[0].level = 100; party[1].level = 100; - const result = getLowestLevelPlayerPokemon(scene); + const result = getLowestLevelPlayerPokemon(); expect(result.species.speciesId).toBe(Species.ARCEUS); }); @@ -199,7 +199,7 @@ describe("Mystery Encounter Utils", () => { party[0].updateInfo(); party[1].level = 100; - const result = getLowestLevelPlayerPokemon(scene, true); + const result = getLowestLevelPlayerPokemon(true); expect(result.species.speciesId).toBe(Species.MANAPHY); }); }); @@ -244,7 +244,7 @@ describe("Mystery Encounter Utils", () => { arceus.hp = 100; expect(arceus.isAllowedInBattle()).toBe(true); - koPlayerPokemon(scene, arceus); + koPlayerPokemon(arceus); expect(arceus.isAllowedInBattle()).toBe(false); }); }); @@ -254,7 +254,7 @@ describe("Mystery Encounter Utils", () => { scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); - const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); + const result = getEncounterText("mysteryEncounter:unit_test_dialogue"); expect(result).toEqual("mysteryEncounter:unit_test_dialogue"); }); @@ -263,7 +263,7 @@ describe("Mystery Encounter Utils", () => { scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new"); - const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); + const result = getEncounterText("mysteryEncounter:unit_test_dialogue"); expect(result).toEqual("mysteryEncounter:unit_test_dialogue"); }); }); @@ -275,7 +275,7 @@ describe("Mystery Encounter Utils", () => { const spy = vi.spyOn(game.scene, "queueMessage"); const phaseSpy = vi.spyOn(game.scene, "unshiftPhase"); - queueEncounterMessage(scene, "mysteryEncounter:unit_test_dialogue"); + queueEncounterMessage("mysteryEncounter:unit_test_dialogue"); expect(spy).toHaveBeenCalledWith("mysteryEncounter:unit_test_dialogue", null, true); expect(phaseSpy).toHaveBeenCalledWith(expect.any(MessagePhase)); }); @@ -287,7 +287,7 @@ describe("Mystery Encounter Utils", () => { scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const spy = vi.spyOn(game.scene.ui, "showText"); - await showEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); + await showEncounterText("mysteryEncounter:unit_test_dialogue"); expect(spy).toHaveBeenCalledWith("mysteryEncounter:unit_test_dialogue", null, expect.any(Function), 0, true, null); }); }); @@ -298,7 +298,7 @@ describe("Mystery Encounter Utils", () => { scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const spy = vi.spyOn(game.scene.ui, "showDialogue"); - await showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue"); + await showEncounterDialogue("mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue"); expect(spy).toHaveBeenCalledWith("mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue", null, expect.any(Function), 0); }); }); diff --git a/src/test/mystery-encounter/mystery-encounter.test.ts b/src/test/mystery-encounter/mystery-encounter.test.ts index eaf6e04a639..7958fc1cd46 100644 --- a/src/test/mystery-encounter/mystery-encounter.test.ts +++ b/src/test/mystery-encounter/mystery-encounter.test.ts @@ -4,7 +4,7 @@ import Phaser from "phaser"; import { Species } from "#enums/species"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; describe("Mystery Encounters", () => { let phaserGame: Phaser.Game; diff --git a/src/test/phases/form-change-phase.test.ts b/src/test/phases/form-change-phase.test.ts index 3c0016260a3..07e59cafe1c 100644 --- a/src/test/phases/form-change-phase.test.ts +++ b/src/test/phases/form-change-phase.test.ts @@ -44,7 +44,7 @@ describe("Form Change Phase", () => { expect(zacian.calculateBaseStats()).toStrictEqual([ 92, 120, 115, 80, 115, 138 ]); // Give Zacian a Rusted Sword - const rustedSwordType = generateModifierType(game.scene, modifierTypes.RARE_FORM_CHANGE_ITEM)!; + const rustedSwordType = generateModifierType( modifierTypes.RARE_FORM_CHANGE_ITEM)!; const rustedSword = rustedSwordType.newModifier(zacian); await game.scene.addModifier(rustedSword); diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index 32e31ce1c94..507862534af 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -5,9 +5,9 @@ import { Species } from "#enums/species"; import { MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { Mode } from "#app/ui/ui"; import { Button } from "#enums/buttons"; -import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; +import type MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; -import MessageUiHandler from "#app/ui/message-ui-handler"; +import type MessageUiHandler from "#app/ui/message-ui-handler"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import i18next from "i18next"; diff --git a/src/test/phases/phases.test.ts b/src/test/phases/phases.test.ts index 5ef25361a3f..36a405b8d15 100644 --- a/src/test/phases/phases.test.ts +++ b/src/test/phases/phases.test.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import { LoginPhase } from "#app/phases/login-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { UnavailablePhase } from "#app/phases/unavailable-phase"; @@ -29,7 +29,7 @@ describe("Phases", () => { describe("LoginPhase", () => { it("should start the login phase", async () => { - const loginPhase = new LoginPhase(scene); + const loginPhase = new LoginPhase(); scene.unshiftPhase(loginPhase); await game.phaseInterceptor.run(LoginPhase); expect(scene.ui.getMode()).to.equal(Mode.MESSAGE); @@ -38,7 +38,7 @@ describe("Phases", () => { describe("TitlePhase", () => { it("should start the title phase", async () => { - const titlePhase = new TitlePhase(scene); + const titlePhase = new TitlePhase(); scene.unshiftPhase(titlePhase); await game.phaseInterceptor.run(TitlePhase); expect(scene.ui.getMode()).to.equal(Mode.TITLE); @@ -47,7 +47,7 @@ describe("Phases", () => { describe("UnavailablePhase", () => { it("should start the unavailable phase", async () => { - const unavailablePhase = new UnavailablePhase(scene); + const unavailablePhase = new UnavailablePhase(); scene.unshiftPhase(unavailablePhase); await game.phaseInterceptor.run(UnavailablePhase); expect(scene.ui.getMode()).to.equal(Mode.UNAVAILABLE); diff --git a/src/test/phases/select-modifier-phase.test.ts b/src/test/phases/select-modifier-phase.test.ts index 60f81f3ad54..23fab729838 100644 --- a/src/test/phases/select-modifier-phase.test.ts +++ b/src/test/phases/select-modifier-phase.test.ts @@ -1,17 +1,21 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import Phaser from "phaser"; -import GameManager from "#app/test/utils/gameManager"; -import { initSceneWithoutEncounterPhase } from "#app/test/utils/gameManagerUtils"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import * as Utils from "#app/utils"; -import { CustomModifierSettings, ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; -import BattleScene from "#app/battle-scene"; -import { Species } from "#enums/species"; -import { Mode } from "#app/ui/ui"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type BattleScene from "#app/battle-scene"; import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { PlayerPokemon } from "#app/field/pokemon"; +import { ModifierTier } from "#app/modifier/modifier-tier"; +import type { CustomModifierSettings } from "#app/modifier/modifier-type"; +import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { Mode } from "#app/ui/ui"; +import { shiftCharCodes } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { Button } from "#enums/buttons"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("SelectModifierPhase", () => { let phaserGame: Phaser.Game; @@ -28,7 +32,11 @@ describe("SelectModifierPhase", () => { game = new GameManager(phaserGame); scene = game.scene; - initSceneWithoutEncounterPhase(scene, [ Species.ABRA, Species.VOLCARONA ]); + game.override + .moveset([ Moves.FISSURE, Moves.SPLASH ]) + .ability(Abilities.NO_GUARD) + .startingLevel(200) + .enemySpecies(Species.MAGIKARP); }); afterEach(() => { @@ -38,7 +46,8 @@ describe("SelectModifierPhase", () => { }); it("should start a select modifier phase", async () => { - const selectModifierPhase = new SelectModifierPhase(scene); + initSceneWithoutEncounterPhase(scene, [ Species.ABRA, Species.VOLCARONA ]); + const selectModifierPhase = new SelectModifierPhase(); scene.pushPhase(selectModifierPhase); await game.phaseInterceptor.run(SelectModifierPhase); @@ -46,10 +55,9 @@ describe("SelectModifierPhase", () => { }); it("should generate random modifiers", async () => { - const selectModifierPhase = new SelectModifierPhase(scene); - scene.pushPhase(selectModifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); - + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + game.move.select(Moves.FISSURE); + await game.phaseInterceptor.to("SelectModifierPhase"); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; @@ -57,64 +65,63 @@ describe("SelectModifierPhase", () => { }); it("should modify reroll cost", async () => { + initSceneWithoutEncounterPhase(scene, [ Species.ABRA, Species.VOLCARONA ]); const options = [ new ModifierTypeOption(modifierTypes.POTION(), 0, 100), new ModifierTypeOption(modifierTypes.ETHER(), 0, 400), new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000) ]; - const selectModifierPhase1 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options }); - const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 }); + const selectModifierPhase1 = new SelectModifierPhase(0, undefined, { guaranteedModifierTypeOptions: options }); + const selectModifierPhase2 = new SelectModifierPhase(0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 }); const cost1 = selectModifierPhase1.getRerollCost(false); const cost2 = selectModifierPhase2.getRerollCost(false); expect(cost2).toEqual(cost1 * 2); }); - it("should generate random modifiers from reroll", async () => { - let selectModifierPhase = new SelectModifierPhase(scene); - scene.pushPhase(selectModifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); + it.todo("should generate random modifiers from reroll", async () => { + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; + scene.shopCursorTarget = 0; + game.move.select(Moves.FISSURE); + await game.phaseInterceptor.to("SelectModifierPhase"); + + // TODO: nagivate the ui to reroll somehow + //const smphase = scene.getCurrentPhase() as SelectModifierPhase; expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); - // Simulate selecting reroll - selectModifierPhase = new SelectModifierPhase(scene, 1, [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ]); - scene.unshiftPhase(selectModifierPhase); - scene.ui.setMode(Mode.MESSAGE).then(() => game.endPhase()); - await game.phaseInterceptor.run(SelectModifierPhase); + modifierSelectHandler.processInput(Button.ACTION); + expect(scene.money).toBe(1000000 - 250); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(modifierSelectHandler.options.length).toEqual(3); }); - it("should generate random modifiers of same tier for reroll with reroll lock", async () => { + it.todo("should generate random modifiers of same tier for reroll with reroll lock", async () => { + game.override.startingModifier([{ name: "LOCK_CAPSULE" }]); + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; // Just use fully random seed for this test vi.spyOn(scene, "resetSeed").mockImplementation(() => { - scene.waveSeed = Utils.shiftCharCodes(scene.seed, 5); + scene.waveSeed = shiftCharCodes(scene.seed, 5); Phaser.Math.RND.sow([ scene.waveSeed ]); console.log("Wave Seed:", scene.waveSeed, 5); scene.rngCounter = 0; }); - let selectModifierPhase = new SelectModifierPhase(scene); - scene.pushPhase(selectModifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); + game.move.select(Moves.FISSURE); + await game.phaseInterceptor.to("SelectModifierPhase"); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(3); const firstRollTiers: ModifierTier[] = modifierSelectHandler.options.map(o => o.modifierTypeOption.type.tier); - // Simulate selecting reroll with lock - scene.lockModifierTiers = true; - scene.reroll = true; - selectModifierPhase = new SelectModifierPhase(scene, 1, firstRollTiers); - scene.unshiftPhase(selectModifierPhase); - scene.ui.setMode(Mode.MESSAGE).then(() => game.endPhase()); - await game.phaseInterceptor.run(SelectModifierPhase); + // TODO: nagivate ui to reroll with lock capsule enabled expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(modifierSelectHandler.options.length).toEqual(3); @@ -125,13 +132,15 @@ describe("SelectModifierPhase", () => { }); it("should generate custom modifiers", async () => { + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_ULTRA, modifierTypes.LEFTOVERS, modifierTypes.AMULET_COIN, modifierTypes.GOLDEN_PUNCH ] }; - const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers); - scene.pushPhase(selectModifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); - + const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + scene.unshiftPhase(selectModifierPhase); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("SelectModifierPhase"); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; @@ -144,10 +153,12 @@ describe("SelectModifierPhase", () => { }); it("should generate custom modifier tiers that can upgrade from luck", async () => { + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.ULTRA, ModifierTier.ROGUE, ModifierTier.MASTER ] }; - const pokemon = new PlayerPokemon(scene, getPokemonSpecies(Species.BULBASAUR), 10, undefined, 0, undefined, true, 2, undefined, undefined, undefined); + const pokemon = new PlayerPokemon(getPokemonSpecies(Species.BULBASAUR), 10, undefined, 0, undefined, true, 2); // Fill party with max shinies while (scene.getPlayerParty().length > 0) { @@ -155,10 +166,10 @@ describe("SelectModifierPhase", () => { } scene.getPlayerParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon); - const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers); - scene.pushPhase(selectModifierPhase); - await game.phaseInterceptor.run(SelectModifierPhase); - + const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + scene.unshiftPhase(selectModifierPhase); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("SelectModifierPhase"); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; @@ -171,12 +182,15 @@ describe("SelectModifierPhase", () => { }); it("should generate custom modifiers and modifier tiers together", async () => { + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON ], guaranteedModifierTiers: [ ModifierTier.MASTER, ModifierTier.MASTER ] }; - const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers); - scene.pushPhase(selectModifierPhase); + const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + scene.unshiftPhase(selectModifierPhase); + game.move.select(Moves.SPLASH); await game.phaseInterceptor.run(SelectModifierPhase); @@ -190,13 +204,16 @@ describe("SelectModifierPhase", () => { }); it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => { + await game.classicMode.startBattle([ Species.ABRA, Species.VOLCARONA ]); + scene.money = 1000000; const customModifiers: CustomModifierSettings = { guaranteedModifierTypeFuncs: [ modifierTypes.MEMORY_MUSHROOM ], guaranteedModifierTiers: [ ModifierTier.MASTER ], fillRemaining: true }; - const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers); - scene.pushPhase(selectModifierPhase); + const selectModifierPhase = new SelectModifierPhase(0, undefined, customModifiers); + scene.unshiftPhase(selectModifierPhase); + game.move.select(Moves.SPLASH); await game.phaseInterceptor.run(SelectModifierPhase); diff --git a/src/test/reload.test.ts b/src/test/reload.test.ts index 3b29cadf8e7..b5f66630606 100644 --- a/src/test/reload.test.ts +++ b/src/test/reload.test.ts @@ -1,13 +1,13 @@ import { GameModes } from "#app/game-mode"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; -import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { Biome } from "#enums/biome"; import { Button } from "#enums/buttons"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; -import { MockClock } from "#test/utils/mocks/mockClock"; +import type { MockClock } from "#test/utils/mocks/mockClock"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Reload", () => { diff --git a/src/test/settingMenu/rebinding_setting.test.ts b/src/test/settingMenu/rebinding_setting.test.ts index 37cf8032897..cae2df363d8 100644 --- a/src/test/settingMenu/rebinding_setting.test.ts +++ b/src/test/settingMenu/rebinding_setting.test.ts @@ -1,6 +1,6 @@ import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; import { getKeyWithKeycode, getKeyWithSettingName } from "#app/configs/inputs/configHandler"; -import { InterfaceConfig } from "#app/inputs-controller"; +import type { InterfaceConfig } from "#app/inputs-controller"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { deepCopy } from "#app/utils"; import { Button } from "#enums/buttons"; diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts index 1e349470302..ad24c40f445 100644 --- a/src/test/system/game_data.test.ts +++ b/src/test/system/game_data.test.ts @@ -1,6 +1,6 @@ import * as BattleScene from "#app/battle-scene"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; -import { SessionSaveData } from "#app/system/game-data"; +import type { SessionSaveData } from "#app/system/game-data"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import GameManager from "#test/utils/gameManager"; @@ -41,7 +41,7 @@ describe("System - Game Data", () => { it("should return [true, true] if bypassLogin is true", async () => { vi.spyOn(BattleScene, "bypassLogin", "get").mockReturnValue(true); - const result = await game.scene.gameData.tryClearSession(game.scene, 0); + const result = await game.scene.gameData.tryClearSession(0); expect(result).toEqual([ true, true ]); }); @@ -49,7 +49,7 @@ describe("System - Game Data", () => { it("should return [true, true] if successful", async () => { vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ success: true }); - const result = await game.scene.gameData.tryClearSession(game.scene, 0); + const result = await game.scene.gameData.tryClearSession(0); expect(result).toEqual([ true, true ]); expect(account.updateUserInfo).toHaveBeenCalled(); @@ -58,7 +58,7 @@ describe("System - Game Data", () => { it("should return [true, false] if not successful", async () => { vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ success: false }); - const result = await game.scene.gameData.tryClearSession(game.scene, 0); + const result = await game.scene.gameData.tryClearSession(0); expect(result).toEqual([ true, false ]); expect(account.updateUserInfo).toHaveBeenCalled(); @@ -67,7 +67,7 @@ describe("System - Game Data", () => { it("should return [false, false] session is out of date", async () => { vi.spyOn(pokerogueApi.savedata.session, "clear").mockResolvedValue({ error: "session out of date" }); - const result = await game.scene.gameData.tryClearSession(game.scene, 0); + const result = await game.scene.gameData.tryClearSession(0); expect(result).toEqual([ false, false ]); expect(account.updateUserInfo).toHaveBeenCalled(); diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index 15af3619ed3..f80990f6dc3 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -4,11 +4,11 @@ import { allSpecies } from "#app/data/pokemon-species"; import { GameModes } from "#app/game-mode"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; -import { TitlePhase } from "#app/phases/title-phase"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; -import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; -import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; -import StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; +import type { TitlePhase } from "#app/phases/title-phase"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; +import type OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler"; +import type StarterSelectUiHandler from "#app/ui/starter-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; import { Button } from "#enums/buttons"; diff --git a/src/test/ui/transfer-item.test.ts b/src/test/ui/transfer-item.test.ts index 0fbd4a52c61..762db7fc7ce 100644 --- a/src/test/ui/transfer-item.test.ts +++ b/src/test/ui/transfer-item.test.ts @@ -9,7 +9,7 @@ import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index 2977262dda7..9046d82c1df 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -7,7 +7,7 @@ import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import MockText from "#test/utils/mocks/mocksContainer/mockText"; +import type MockText from "#test/utils/mocks/mocksContainer/mockText"; import i18next from "i18next"; describe("UI - Type Hints", () => { diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index fe8d06c2c3b..9fda3afa56e 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -2,7 +2,7 @@ import { updateUserInfo } from "#app/account"; import { BattlerIndex } from "#app/battle"; import BattleScene from "#app/battle-scene"; import { getMoveTargets } from "#app/data/move"; -import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import Trainer from "#app/field/trainer"; import { GameModes, getGameMode } from "#app/game-mode"; import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; @@ -17,28 +17,28 @@ import { MovePhase } from "#app/phases/move-phase"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { NewBattlePhase } from "#app/phases/new-battle-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; -import { SelectTargetPhase } from "#app/phases/select-target-phase"; +import type { SelectTargetPhase } from "#app/phases/select-target-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { TurnStartPhase } from "#app/phases/turn-start-phase"; import ErrorInterceptor from "#app/test/utils/errorInterceptor"; -import InputsHandler from "#app/test/utils/inputsHandler"; -import BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; -import CommandUiHandler from "#app/ui/command-ui-handler"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import PartyUiHandler from "#app/ui/party-ui-handler"; -import TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; +import type InputsHandler from "#app/test/utils/inputsHandler"; +import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler"; +import type CommandUiHandler from "#app/ui/command-ui-handler"; +import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import type PartyUiHandler from "#app/ui/party-ui-handler"; +import type TargetSelectUiHandler from "#app/ui/target-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { isNullOrUndefined } from "#app/utils"; import { BattleStyle } from "#enums/battle-style"; import { Button } from "#enums/buttons"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { ExpNotification } from "#enums/exp-notification"; -import { Moves } from "#enums/moves"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { Moves } from "#enums/moves"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PlayerGender } from "#enums/player-gender"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; import { generateStarter, waitUntil } from "#test/utils/gameManagerUtils"; import GameWrapper from "#test/utils/gameWrapper"; import { ChallengeModeHelper } from "#test/utils/helpers/challengeModeHelper"; @@ -173,8 +173,8 @@ export default class GameManager { this.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.scene.gameMode = getGameMode(mode); const starters = generateStarter(this.scene, species); - const selectStarterPhase = new SelectStarterPhase(this.scene); - this.scene.pushPhase(new EncounterPhase(this.scene, false)); + const selectStarterPhase = new SelectStarterPhase(); + this.scene.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); @@ -206,8 +206,8 @@ export default class GameManager { this.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.scene, species); - const selectStarterPhase = new SelectStarterPhase(this.scene); - this.scene.pushPhase(new EncounterPhase(this.scene, false)); + const selectStarterPhase = new SelectStarterPhase(); + this.scene.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }, () => this.isCurrentPhase(EncounterPhase)); @@ -375,7 +375,7 @@ export default class GameManager { exportSaveToTest(): Promise { const saveKey = "x0i2O7WRiANTqPmZ"; return new Promise(async (resolve) => { - const sessionSaveData = this.scene.gameData.getSessionSaveData(this.scene); + const sessionSaveData = this.scene.gameData.getSessionSaveData(); const encryptedSaveData = AES.encrypt(JSON.stringify(sessionSaveData), saveKey).toString(); resolve(encryptedSaveData); }); @@ -403,7 +403,7 @@ export default class GameManager { async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) { return new Promise(async (resolve, reject) => { pokemon.hp = 0; - this.scene.pushPhase(new FaintPhase(this.scene, pokemon.getBattlerIndex(), true)); + this.scene.pushPhase(new FaintPhase(pokemon.getBattlerIndex(), true)); await this.phaseInterceptor.to(FaintPhase).catch((e) => reject(e)); resolve(); }); diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts index 0c70bcf7f18..757874f0c17 100644 --- a/src/test/utils/gameManagerUtils.ts +++ b/src/test/utils/gameManagerUtils.ts @@ -1,13 +1,14 @@ -import BattleScene from "#app/battle-scene"; +import Battle, { BattleType } from "#app/battle"; +import type BattleScene from "#app/battle-scene"; import { getDailyRunStarters } from "#app/data/daily-run"; import { Gender } from "#app/data/gender"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; -import { Moves } from "#app/enums/moves"; import { PlayerPokemon } from "#app/field/pokemon"; import { GameModes, getGameMode } from "#app/game-mode"; -import { Starter } from "#app/ui/starter-select-ui-handler"; -import { Species } from "#enums/species"; -import Battle, { BattleType } from "#app/battle"; +import type { StarterMoveset } from "#app/system/game-data"; +import type { Starter } from "#app/ui/starter-select-ui-handler"; +import { Moves } from "#enums/moves"; +import type { Species } from "#enums/species"; /** Function to convert Blob to string */ export function blobToString(blob) { @@ -31,9 +32,9 @@ export function holdOn(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export function generateStarter(scene, species?: Species[]) { +export function generateStarter(scene: BattleScene, species?: Species[]): Starter[] { const seed = "test"; - const starters = getTestRunStarters(scene, seed, species); + const starters = getTestRunStarters(seed, species); const startingLevel = scene.gameMode.getStartingLevel(); for (const starter of starters) { const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); @@ -42,14 +43,18 @@ export function generateStarter(scene, species?: Species[]) { ? !starterProps.female ? Gender.MALE : Gender.FEMALE : Gender.GENDERLESS; const starterPokemon = scene.addPlayerPokemon(starter.species, startingLevel, starter.abilityIndex, starterFormIndex, starterGender, starterProps.shiny, starterProps.variant, undefined, starter.nature); - starter.moveset = starterPokemon.moveset; + const moveset: Moves[] = []; + starterPokemon.moveset.forEach((move) => { + moveset.push(move!.getMove().id); + }); + starter.moveset = moveset as StarterMoveset; } return starters; } -function getTestRunStarters(scene, seed, species) { +function getTestRunStarters(seed: string, species?: Species[]): Starter[] { if (!species) { - return getDailyRunStarters(scene, seed); + return getDailyRunStarters(seed); } const starters: Starter[] = []; const startingLevel = getGameMode(GameModes.CLASSIC).getStartingLevel(); @@ -57,7 +62,7 @@ function getTestRunStarters(scene, seed, species) { for (const specie of species) { const starterSpeciesForm = getPokemonSpeciesForm(specie, 0); const starterSpecies = getPokemonSpecies(starterSpeciesForm.speciesId); - const pokemon = new PlayerPokemon(scene, starterSpecies, startingLevel, undefined, 0, undefined, undefined, undefined, undefined, undefined, undefined); + const pokemon = new PlayerPokemon(starterSpecies, startingLevel, undefined, 0); const starter: Starter = { species: starterSpecies, dexAttr: pokemon.getDexAttr(), @@ -71,7 +76,7 @@ function getTestRunStarters(scene, seed, species) { return starters; } -export function waitUntil(truth) { +export function waitUntil(truth): Promise { return new Promise(resolve => { const interval = setInterval(() => { if (truth()) { @@ -83,7 +88,7 @@ export function waitUntil(truth) { } /** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex` */ -export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) { +export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves): number { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp()); @@ -93,10 +98,8 @@ export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: M /** * Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase - * @param scene - * @param species */ -export function initSceneWithoutEncounterPhase(scene: BattleScene, species?: Species[]) { +export function initSceneWithoutEncounterPhase(scene: BattleScene, species?: Species[]): void { const starters = generateStarter(scene, species); starters.forEach((starter) => { const starterProps = scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr); diff --git a/src/test/utils/helpers/challengeModeHelper.ts b/src/test/utils/helpers/challengeModeHelper.ts index 5210d942d5a..4b5a38e72dc 100644 --- a/src/test/utils/helpers/challengeModeHelper.ts +++ b/src/test/utils/helpers/challengeModeHelper.ts @@ -1,15 +1,15 @@ import { BattleStyle } from "#app/enums/battle-style"; -import { Species } from "#app/enums/species"; +import type { Species } from "#app/enums/species"; import overrides from "#app/overrides"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { Mode } from "#app/ui/ui"; import { generateStarter } from "../gameManagerUtils"; import { GameManagerHelper } from "./gameManagerHelper"; -import { Challenge } from "#app/data/challenge"; +import type { Challenge } from "#app/data/challenge"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import { Challenges } from "#enums/challenges"; +import type { Challenges } from "#enums/challenges"; import { copyChallenge } from "data/challenge"; /** @@ -45,8 +45,8 @@ export class ChallengeModeHelper extends GameManagerHelper { this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode.challenges = this.challenges; const starters = generateStarter(this.game.scene, species); - const selectStarterPhase = new SelectStarterPhase(this.game.scene); - this.game.scene.pushPhase(new EncounterPhase(this.game.scene, false)); + const selectStarterPhase = new SelectStarterPhase(); + this.game.scene.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 41a21a52b72..40c3d518f1c 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -1,5 +1,5 @@ import { BattleStyle } from "#app/enums/battle-style"; -import { Species } from "#app/enums/species"; +import type { Species } from "#app/enums/species"; import { GameModes, getGameMode } from "#app/game-mode"; import overrides from "#app/overrides"; import { CommandPhase } from "#app/phases/command-phase"; @@ -30,8 +30,8 @@ export class ClassicModeHelper extends GameManagerHelper { this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.game.scene, species); - const selectStarterPhase = new SelectStarterPhase(this.game.scene); - this.game.scene.pushPhase(new EncounterPhase(this.game.scene, false)); + const selectStarterPhase = new SelectStarterPhase(); + this.game.scene.pushPhase(new EncounterPhase(false)); selectStarterPhase.initBattle(starters); }); diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index 813544f85df..bd8c6ebb920 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -5,7 +5,7 @@ import { CommandPhase } from "#app/phases/command-phase"; import { EncounterPhase } from "#app/phases/encounter-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; +import type SaveSlotSelectUiHandler from "#app/ui/save-slot-select-ui-handler"; import { Mode } from "#app/ui/ui"; import { GameManagerHelper } from "./gameManagerHelper"; @@ -26,7 +26,7 @@ export class DailyModeHelper extends GameManagerHelper { } this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { - const titlePhase = new TitlePhase(this.game.scene); + const titlePhase = new TitlePhase(); titlePhase.initDailyRun(); }); diff --git a/src/test/utils/helpers/gameManagerHelper.ts b/src/test/utils/helpers/gameManagerHelper.ts index 432e6fdf853..85d7bc0df97 100644 --- a/src/test/utils/helpers/gameManagerHelper.ts +++ b/src/test/utils/helpers/gameManagerHelper.ts @@ -1,4 +1,4 @@ -import GameManager from "../gameManager"; +import type GameManager from "../gameManager"; /** * Base class for defining all game helpers. diff --git a/src/test/utils/helpers/modifiersHelper.ts b/src/test/utils/helpers/modifiersHelper.ts index c38bf5770a8..22500c87906 100644 --- a/src/test/utils/helpers/modifiersHelper.ts +++ b/src/test/utils/helpers/modifiersHelper.ts @@ -1,6 +1,7 @@ import { expect } from "vitest"; import { GameManagerHelper } from "./gameManagerHelper"; -import { itemPoolChecks, ModifierTypeKeys } from "#app/modifier/modifier-type"; +import type { ModifierTypeKeys } from "#app/modifier/modifier-type"; +import { itemPoolChecks } from "#app/modifier/modifier-type"; export class ModifierHelper extends GameManagerHelper { /** diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index 68d3b3d51d7..4b2069ee881 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,8 +1,8 @@ -import { BattlerIndex } from "#app/battle"; +import type { BattlerIndex } from "#app/battle"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 1c05f92a334..9af811561b7 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -1,15 +1,17 @@ -import { Variant } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; import { Weather } from "#app/data/weather"; import { Abilities } from "#app/enums/abilities"; import * as GameMode from "#app/game-mode"; -import { GameModes, getGameMode } from "#app/game-mode"; -import { ModifierOverride } from "#app/modifier/modifier-type"; -import Overrides, { BattleStyle } from "#app/overrides"; -import { Unlockables } from "#app/system/unlockables"; +import type { GameModes } from "#app/game-mode"; +import { getGameMode } from "#app/game-mode"; +import type { ModifierOverride } from "#app/modifier/modifier-type"; +import type { BattleStyle } from "#app/overrides"; +import Overrides from "#app/overrides"; +import type { Unlockables } from "#app/system/unlockables"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; -import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import { StatusEffect } from "#enums/status-effect"; import type { WeatherType } from "#enums/weather-type"; diff --git a/src/test/utils/helpers/reloadHelper.ts b/src/test/utils/helpers/reloadHelper.ts index e0e538120cc..5f516584873 100644 --- a/src/test/utils/helpers/reloadHelper.ts +++ b/src/test/utils/helpers/reloadHelper.ts @@ -5,8 +5,8 @@ import { vi } from "vitest"; import { BattleStyle } from "#app/enums/battle-style"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import { SessionSaveData } from "#app/system/game-data"; -import GameManager from "../gameManager"; +import type { SessionSaveData } from "#app/system/game-data"; +import type GameManager from "../gameManager"; /** * Helper to allow reloading sessions in unit tests. @@ -18,9 +18,9 @@ export class ReloadHelper extends GameManagerHelper { super(game); // Whenever the game saves the session, save it to the reloadHelper instead - vi.spyOn(game.scene.gameData, "saveAll").mockImplementation((scene) => { + vi.spyOn(game.scene.gameData, "saveAll").mockImplementation(() => { return new Promise((resolve, reject) => { - this.sessionData = scene.gameData.getSessionSaveData(scene); + this.sessionData = game.scene.gameData.getSessionSaveData(); resolve(true); }); }); @@ -33,7 +33,7 @@ export class ReloadHelper extends GameManagerHelper { */ async reloadSession() : Promise { const scene = this.game.scene; - const titlePhase = new TitlePhase(scene); + const titlePhase = new TitlePhase(); scene.clearPhaseQueue(); diff --git a/src/test/utils/inputsHandler.ts b/src/test/utils/inputsHandler.ts index bf690d5d74c..8b8a89e45dc 100644 --- a/src/test/utils/inputsHandler.ts +++ b/src/test/utils/inputsHandler.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; +import type BattleScene from "#app/battle-scene"; import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; -import { InputsController } from "#app/inputs-controller"; +import type { InputsController } from "#app/inputs-controller"; import TouchControl from "#app/touch-controls"; import { holdOn } from "#test/utils/gameManagerUtils"; import fs from "fs"; @@ -58,7 +58,7 @@ export default class InputsHandler { } init(): void { - const touchControl = new TouchControl(this.scene); + const touchControl = new TouchControl(); touchControl.deactivatePressedKey(); //test purpose this.events = this.inputController.events; this.scene.input.gamepad?.emit("connected", this.fakePad); diff --git a/src/test/utils/mocks/mockGameObjectCreator.ts b/src/test/utils/mocks/mockGameObjectCreator.ts index 27860be0cec..caf98b79a9f 100644 --- a/src/test/utils/mocks/mockGameObjectCreator.ts +++ b/src/test/utils/mocks/mockGameObjectCreator.ts @@ -1,5 +1,5 @@ import MockGraphics from "./mocksContainer/mockGraphics"; -import MockTextureManager from "./mockTextureManager"; +import type MockTextureManager from "./mockTextureManager"; export class MockGameObjectCreator { private readonly textureManager: MockTextureManager; diff --git a/src/test/utils/mocks/mockTextureManager.ts b/src/test/utils/mocks/mockTextureManager.ts index ce19d6b6432..39066561f25 100644 --- a/src/test/utils/mocks/mockTextureManager.ts +++ b/src/test/utils/mocks/mockTextureManager.ts @@ -6,7 +6,7 @@ import MockRectangle from "#test/utils/mocks/mocksContainer/mockRectangle"; import MockSprite from "#test/utils/mocks/mocksContainer/mockSprite"; import MockText from "#test/utils/mocks/mocksContainer/mockText"; import MockTexture from "#test/utils/mocks/mocksContainer/mockTexture"; -import { MockGameObject } from "./mockGameObject"; +import type { MockGameObject } from "./mockGameObject"; import { MockVideoGameObject } from "./mockVideoGameObject"; /** diff --git a/src/test/utils/mocks/mockVideoGameObject.ts b/src/test/utils/mocks/mockVideoGameObject.ts index d11fb5a44ce..74b05626616 100644 --- a/src/test/utils/mocks/mockVideoGameObject.ts +++ b/src/test/utils/mocks/mockVideoGameObject.ts @@ -1,4 +1,4 @@ -import { MockGameObject } from "./mockGameObject"; +import type { MockGameObject } from "./mockGameObject"; /** Mocks video-related stuff */ export class MockVideoGameObject implements MockGameObject { diff --git a/src/test/utils/mocks/mocksContainer/mockContainer.ts b/src/test/utils/mocks/mocksContainer/mockContainer.ts index 05dad327dc6..f0198535e7b 100644 --- a/src/test/utils/mocks/mocksContainer/mockContainer.ts +++ b/src/test/utils/mocks/mocksContainer/mockContainer.ts @@ -1,5 +1,5 @@ -import MockTextureManager from "#test/utils/mocks/mockTextureManager"; -import { MockGameObject } from "../mockGameObject"; +import type MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import type { MockGameObject } from "../mockGameObject"; export default class MockContainer implements MockGameObject { protected x; diff --git a/src/test/utils/mocks/mocksContainer/mockGraphics.ts b/src/test/utils/mocks/mocksContainer/mockGraphics.ts index b20faf4ed6a..2b4a33b5e4c 100644 --- a/src/test/utils/mocks/mocksContainer/mockGraphics.ts +++ b/src/test/utils/mocks/mocksContainer/mockGraphics.ts @@ -1,4 +1,4 @@ -import { MockGameObject } from "../mockGameObject"; +import type { MockGameObject } from "../mockGameObject"; export default class MockGraphics implements MockGameObject { private scene; diff --git a/src/test/utils/mocks/mocksContainer/mockRectangle.ts b/src/test/utils/mocks/mocksContainer/mockRectangle.ts index 48cd2cb1380..e4bca76b8fc 100644 --- a/src/test/utils/mocks/mocksContainer/mockRectangle.ts +++ b/src/test/utils/mocks/mocksContainer/mockRectangle.ts @@ -1,4 +1,4 @@ -import { MockGameObject } from "../mockGameObject"; +import type { MockGameObject } from "../mockGameObject"; export default class MockRectangle implements MockGameObject { private fillColor; diff --git a/src/test/utils/mocks/mocksContainer/mockSprite.ts b/src/test/utils/mocks/mocksContainer/mockSprite.ts index e726fec454d..6d851330a5e 100644 --- a/src/test/utils/mocks/mocksContainer/mockSprite.ts +++ b/src/test/utils/mocks/mocksContainer/mockSprite.ts @@ -1,5 +1,5 @@ import Phaser from "phaser"; -import { MockGameObject } from "../mockGameObject"; +import type { MockGameObject } from "../mockGameObject"; import Sprite = Phaser.GameObjects.Sprite; import Frame = Phaser.Textures.Frame; diff --git a/src/test/utils/mocks/mocksContainer/mockText.ts b/src/test/utils/mocks/mocksContainer/mockText.ts index faa32ff8a06..604679af372 100644 --- a/src/test/utils/mocks/mocksContainer/mockText.ts +++ b/src/test/utils/mocks/mocksContainer/mockText.ts @@ -1,5 +1,5 @@ import UI from "#app/ui/ui"; -import { MockGameObject } from "../mockGameObject"; +import type { MockGameObject } from "../mockGameObject"; export default class MockText implements MockGameObject { private phaserText; diff --git a/src/test/utils/mocks/mocksContainer/mockTexture.ts b/src/test/utils/mocks/mocksContainer/mockTexture.ts index bedd1d2c84a..57c87df23be 100644 --- a/src/test/utils/mocks/mocksContainer/mockTexture.ts +++ b/src/test/utils/mocks/mocksContainer/mockTexture.ts @@ -1,5 +1,5 @@ -import MockTextureManager from "#test/utils/mocks/mockTextureManager"; -import { MockGameObject } from "../mockGameObject"; +import type MockTextureManager from "#test/utils/mocks/mockTextureManager"; +import type { MockGameObject } from "../mockGameObject"; /** diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 13750609004..d60e0e78373 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -510,7 +510,13 @@ export default class PhaseInterceptor { const currentHandler = this.scene.ui.getHandler(); if (expireFn) { this.prompts.shift(); - } else if (currentMode === actionForNextPrompt.mode && currentPhase === actionForNextPrompt.phaseTarget && currentHandler.active && (!actionForNextPrompt.awaitingActionInput || (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput))) { + } else if ( + currentMode === actionForNextPrompt.mode + && currentPhase === actionForNextPrompt.phaseTarget + && currentHandler.active + && (!actionForNextPrompt.awaitingActionInput + || (actionForNextPrompt.awaitingActionInput && currentHandler.awaitingActionInput)) + ) { const prompt = this.prompts.shift(); if (prompt?.callback) { prompt.callback(); diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 0b83d112522..eb2c1e4b9cf 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -19,11 +19,13 @@ process.env.TZ = "UTC"; /** Mock the override import to always return default values, ignoring any custom overrides. */ vi.mock("#app/overrides", async (importOriginal) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports const { defaultOverrides } = await importOriginal(); return { default: defaultOverrides, defaultOverrides, + // eslint-disable-next-line @typescript-eslint/consistent-type-imports } satisfies typeof import("#app/overrides"); }); diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 926da91b352..216900e8f58 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -1,9 +1,9 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "#app/ui/text"; -import { nil } from "#app/utils"; +import type { nil } from "#app/utils"; import i18next from "i18next"; import { Species } from "#enums/species"; -import { WeatherPoolEntry } from "#app/data/weather"; +import type { WeatherPoolEntry } from "#app/data/weather"; import { WeatherType } from "#enums/weather-type"; export enum EventType { @@ -129,9 +129,9 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container { private availableWidth: number; private eventTimer: NodeJS.Timeout | null; - constructor(scene: BattleScene, x: number, y: number, event?: TimedEvent) { - super(scene, x, y); - this.availableWidth = scene.scaledCanvas.width; + constructor(x: number, y: number, event?: TimedEvent) { + super(globalScene, x, y); + this.availableWidth = globalScene.scaledCanvas.width; this.event = event; this.setVisible(false); } @@ -167,14 +167,13 @@ export class TimedEventDisplay extends Phaser.GameObjects.Container { console.log(this.event.bannerKey); const padding = 5; const showTimer = this.event.eventType !== EventType.NO_TIMER_DISPLAY; - const yPosition = this.scene.game.canvas.height / 6 - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0); - this.banner = new Phaser.GameObjects.Image(this.scene, this.availableWidth / 2, yPosition - padding, key); + const yPosition = globalScene.game.canvas.height / 6 - padding - (showTimer ? 10 : 0) - (this.event.yOffset ?? 0); + this.banner = new Phaser.GameObjects.Image(globalScene, this.availableWidth / 2, yPosition - padding, key); this.banner.setName("img-event-banner"); this.banner.setOrigin(0.5, 1); this.banner.setScale(this.event.scale ?? 0.18); if (showTimer) { this.eventTimerText = addTextObject( - this.scene, this.banner.x, this.banner.y + 2, this.timeToGo(this.event.endDate), diff --git a/src/touch-controls.ts b/src/touch-controls.ts index 93032ce59fe..db2ae223d3f 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -1,6 +1,6 @@ +import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import EventEmitter = Phaser.Events.EventEmitter; -import BattleScene from "./battle-scene"; const repeatInputDelayMillis = 250; @@ -13,8 +13,8 @@ export default class TouchControl { /** Whether the last touch event has finished before disabling */ private finishedLastTouch: boolean = false; - constructor(scene: BattleScene) { - this.events = scene.game.events; + constructor() { + this.events = globalScene.game.events; this.init(); } diff --git a/src/tutorial.ts b/src/tutorial.ts index 3934ffee57f..b5f688c11fb 100644 --- a/src/tutorial.ts +++ b/src/tutorial.ts @@ -1,6 +1,6 @@ -import BattleScene from "./battle-scene"; +import { globalScene } from "#app/global-scene"; import AwaitableUiHandler from "./ui/awaitable-ui-handler"; -import UiHandler from "./ui/ui-handler"; +import type UiHandler from "./ui/ui-handler"; import { Mode } from "./ui/ui"; import i18next from "i18next"; import Overrides from "#app/overrides"; @@ -17,50 +17,50 @@ export enum Tutorial { } const tutorialHandlers = { - [Tutorial.Intro]: (scene: BattleScene) => { + [Tutorial.Intro]: () => { return new Promise(resolve => { - scene.ui.showText(i18next.t("tutorial:intro"), null, () => resolve(), null, true); + globalScene.ui.showText(i18next.t("tutorial:intro"), null, () => resolve(), null, true); }); }, - [Tutorial.Access_Menu]: (scene: BattleScene) => { + [Tutorial.Access_Menu]: () => { return new Promise(resolve => { - if (scene.enableTouchControls) { + if (globalScene.enableTouchControls) { return resolve(); } - scene.showFieldOverlay(1000).then(() => scene.ui.showText(i18next.t("tutorial:accessMenu"), null, () => scene.hideFieldOverlay(1000).then(() => resolve()), null, true)); + globalScene.showFieldOverlay(1000).then(() => globalScene.ui.showText(i18next.t("tutorial:accessMenu"), null, () => globalScene.hideFieldOverlay(1000).then(() => resolve()), null, true)); }); }, - [Tutorial.Menu]: (scene: BattleScene) => { + [Tutorial.Menu]: () => { return new Promise(resolve => { - scene.gameData.saveTutorialFlag(Tutorial.Access_Menu, true); - scene.ui.showText(i18next.t("tutorial:menu"), null, () => scene.ui.showText("", null, () => resolve()), null, true); + globalScene.gameData.saveTutorialFlag(Tutorial.Access_Menu, true); + globalScene.ui.showText(i18next.t("tutorial:menu"), null, () => globalScene.ui.showText("", null, () => resolve()), null, true); }); }, - [Tutorial.Starter_Select]: (scene: BattleScene) => { + [Tutorial.Starter_Select]: () => { return new Promise(resolve => { - scene.ui.showText(i18next.t("tutorial:starterSelect"), null, () => scene.ui.showText("", null, () => resolve()), null, true); + globalScene.ui.showText(i18next.t("tutorial:starterSelect"), null, () => globalScene.ui.showText("", null, () => resolve()), null, true); }); }, - [Tutorial.Pokerus]: (scene: BattleScene) => { + [Tutorial.Pokerus]: () => { return new Promise(resolve => { - scene.ui.showText(i18next.t("tutorial:pokerus"), null, () => scene.ui.showText("", null, () => resolve()), null, true); + globalScene.ui.showText(i18next.t("tutorial:pokerus"), null, () => globalScene.ui.showText("", null, () => resolve()), null, true); }); }, - [Tutorial.Stat_Change]: (scene: BattleScene) => { + [Tutorial.Stat_Change]: () => { return new Promise(resolve => { - scene.showFieldOverlay(1000).then(() => scene.ui.showText(i18next.t("tutorial:statChange"), null, () => scene.ui.showText("", null, () => scene.hideFieldOverlay(1000).then(() => resolve())), null, true)); + globalScene.showFieldOverlay(1000).then(() => globalScene.ui.showText(i18next.t("tutorial:statChange"), null, () => globalScene.ui.showText("", null, () => globalScene.hideFieldOverlay(1000).then(() => resolve())), null, true)); }); }, - [Tutorial.Select_Item]: (scene: BattleScene) => { + [Tutorial.Select_Item]: () => { return new Promise(resolve => { - scene.ui.setModeWithoutClear(Mode.MESSAGE).then(() => { - scene.ui.showText(i18next.t("tutorial:selectItem"), null, () => scene.ui.showText("", null, () => scene.ui.setModeWithoutClear(Mode.MODIFIER_SELECT).then(() => resolve())), null, true); + globalScene.ui.setModeWithoutClear(Mode.MESSAGE).then(() => { + globalScene.ui.showText(i18next.t("tutorial:selectItem"), null, () => globalScene.ui.showText("", null, () => globalScene.ui.setModeWithoutClear(Mode.MODIFIER_SELECT).then(() => resolve())), null, true); }); }); }, - [Tutorial.Egg_Gacha]: (scene: BattleScene) => { + [Tutorial.Egg_Gacha]: () => { return new Promise(resolve => { - scene.ui.showText(i18next.t("tutorial:eggGacha"), null, () => scene.ui.showText("", null, () => resolve()), null, true); + globalScene.ui.showText(i18next.t("tutorial:eggGacha"), null, () => globalScene.ui.showText("", null, () => resolve()), null, true); }); }, }; @@ -69,35 +69,34 @@ const tutorialHandlers = { * Run through the specified tutorial if it hasn't been seen before and mark it as seen once done * This will show a tutorial overlay if defined in the current {@linkcode AwaitableUiHandler} * The main menu will also get disabled while the tutorial is running - * @param scene the current {@linkcode BattleScene} * @param tutorial the {@linkcode Tutorial} to play * @returns a promise with result `true` if the tutorial was run and finished, `false` otherwise */ -export async function handleTutorial(scene: BattleScene, tutorial: Tutorial): Promise { - if (!scene.enableTutorials && !Overrides.BYPASS_TUTORIAL_SKIP_OVERRIDE) { +export async function handleTutorial(tutorial: Tutorial): Promise { + if (!globalScene.enableTutorials && !Overrides.BYPASS_TUTORIAL_SKIP_OVERRIDE) { return false; } - if (scene.gameData.getTutorialFlags()[tutorial] && !Overrides.BYPASS_TUTORIAL_SKIP_OVERRIDE) { + if (globalScene.gameData.getTutorialFlags()[tutorial] && !Overrides.BYPASS_TUTORIAL_SKIP_OVERRIDE) { return false; } - const handler = scene.ui.getHandler(); - const isMenuDisabled = scene.disableMenu; + const handler = globalScene.ui.getHandler(); + const isMenuDisabled = globalScene.disableMenu; // starting tutorial, disable menu - scene.disableMenu = true; + globalScene.disableMenu = true; if (handler instanceof AwaitableUiHandler) { handler.tutorialActive = true; } - await showTutorialOverlay(scene, handler); - await tutorialHandlers[tutorial](scene); - await hideTutorialOverlay(scene, handler); + await showTutorialOverlay(handler); + await tutorialHandlers[tutorial](); + await hideTutorialOverlay(handler); // tutorial finished and overlay gone, re-enable menu, save tutorial as seen - scene.disableMenu = isMenuDisabled; - scene.gameData.saveTutorialFlag(tutorial, true); + globalScene.disableMenu = isMenuDisabled; + globalScene.gameData.saveTutorialFlag(tutorial, true); if (handler instanceof AwaitableUiHandler) { handler.tutorialActive = false; } @@ -107,13 +106,12 @@ export async function handleTutorial(scene: BattleScene, tutorial: Tutorial): Pr /** * Show the tutorial overlay if there is one - * @param scene the current BattleScene * @param handler the current UiHandler * @returns `true` once the overlay has finished appearing, or if there is no overlay */ -async function showTutorialOverlay(scene: BattleScene, handler: UiHandler) { +async function showTutorialOverlay(handler: UiHandler) { if (handler instanceof AwaitableUiHandler && handler.tutorialOverlay) { - scene.tweens.add({ + globalScene.tweens.add({ targets: handler.tutorialOverlay, alpha: 0.5, duration: 750, @@ -129,13 +127,12 @@ async function showTutorialOverlay(scene: BattleScene, handler: UiHandler) { /** * Hide the tutorial overlay if there is one - * @param scene the current BattleScene * @param handler the current UiHandler * @returns `true` once the overlay has finished disappearing, or if there is no overlay */ -async function hideTutorialOverlay(scene: BattleScene, handler: UiHandler) { +async function hideTutorialOverlay(handler: UiHandler) { if (handler instanceof AwaitableUiHandler && handler.tutorialOverlay) { - scene.tweens.add({ + globalScene.tweens.add({ targets: handler.tutorialOverlay, alpha: 0, duration: 500, diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 92b1653df3d..25ad9d87701 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -1,14 +1,14 @@ -import Phaser from "phaser"; +import type Phaser from "phaser"; import { Mode } from "./ui/ui"; -import { InputsController } from "./inputs-controller"; -import MessageUiHandler from "./ui/message-ui-handler"; +import type { InputsController } from "./inputs-controller"; +import type MessageUiHandler from "./ui/message-ui-handler"; import StarterSelectUiHandler from "./ui/starter-select-ui-handler"; import { Setting, SettingKeys, settingIndex } from "./system/settings/settings"; import SettingsUiHandler from "./ui/settings/settings-ui-handler"; import { Button } from "#enums/buttons"; import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; -import BattleScene from "./battle-scene"; +import { globalScene } from "#app/global-scene"; import SettingsDisplayUiHandler from "./ui/settings/settings-display-ui-handler"; import SettingsAudioUiHandler from "./ui/settings/settings-audio-ui-handler"; import RunInfoUiHandler from "./ui/run-info-ui-handler"; @@ -16,12 +16,10 @@ import RunInfoUiHandler from "./ui/run-info-ui-handler"; type ActionKeys = Record void>; export class UiInputs { - private scene: BattleScene; private events: Phaser.Events.EventEmitter; private inputsController: InputsController; - constructor(scene: BattleScene, inputsController: InputsController) { - this.scene = scene; + constructor(inputsController: InputsController) { this.inputsController = inputsController; this.init(); } @@ -35,12 +33,12 @@ export class UiInputs { if (evt.controller_type === "keyboard") { //if the touch property is present and defined, then this is a simulated keyboard event from the touch screen if (evt.hasOwnProperty("isTouch") && evt.isTouch) { - this.scene.inputMethod = "touch"; + globalScene.inputMethod = "touch"; } else { - this.scene.inputMethod = "keyboard"; + globalScene.inputMethod = "keyboard"; } } else if (evt.controller_type === "gamepad") { - this.scene.inputMethod = "gamepad"; + globalScene.inputMethod = "gamepad"; } } @@ -65,7 +63,7 @@ export class UiInputs { } doVibration(inputSuccess: boolean, vibrationLength: number): void { - if (inputSuccess && this.scene.enableVibration && typeof navigator.vibrate !== "undefined") { + if (inputSuccess && globalScene.enableVibration && typeof navigator.vibrate !== "undefined") { navigator.vibrate(vibrationLength); } } @@ -117,59 +115,59 @@ export class UiInputs { } buttonDirection(direction: Button): void { - const inputSuccess = this.scene.ui.processInput(direction); + const inputSuccess = globalScene.ui.processInput(direction); const vibrationLength = 5; this.doVibration(inputSuccess, vibrationLength); } buttonAb(button: Button): void { - this.scene.ui.processInput(button); + globalScene.ui.processInput(button); } buttonTouch(): void { - this.scene.ui.processInput(Button.SUBMIT) || this.scene.ui.processInput(Button.ACTION); + globalScene.ui.processInput(Button.SUBMIT) || globalScene.ui.processInput(Button.ACTION); } buttonStats(pressed: boolean = true): void { // allow access to Button.STATS as a toggle for other elements - for (const t of this.scene.getInfoToggles(true)) { + for (const t of globalScene.getInfoToggles(true)) { t.toggleInfo(pressed); } // handle normal pokemon battle ui - for (const p of this.scene.getField().filter(p => p?.isActive(true))) { + for (const p of globalScene.getField().filter(p => p?.isActive(true))) { p.toggleStats(pressed); } } buttonGoToFilter(button: Button): void { const whitelist = [ StarterSelectUiHandler ]; - const uiHandler = this.scene.ui?.getHandler(); + const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { - this.scene.ui.processInput(button); + globalScene.ui.processInput(button); } else { this.buttonStats(true); } } buttonInfo(pressed: boolean = true): void { - if (this.scene.showMovesetFlyout ) { - for (const p of this.scene.getField().filter(p => p?.isActive(true))) { + if (globalScene.showMovesetFlyout ) { + for (const p of globalScene.getField().filter(p => p?.isActive(true))) { p.toggleFlyout(pressed); } } - if (this.scene.showArenaFlyout) { - this.scene.ui.processInfoButton(pressed); + if (globalScene.showArenaFlyout) { + globalScene.ui.processInfoButton(pressed); } } buttonMenu(): void { - if (this.scene.disableMenu) { + if (globalScene.disableMenu) { return; } - switch (this.scene.ui?.getMode()) { + switch (globalScene.ui?.getMode()) { case Mode.MESSAGE: - const messageHandler = this.scene.ui.getHandler(); + const messageHandler = globalScene.ui.getHandler(); if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { return; } @@ -177,14 +175,14 @@ export class UiInputs { case Mode.COMMAND: case Mode.MODIFIER_SELECT: case Mode.MYSTERY_ENCOUNTER: - this.scene.ui.setOverlayMode(Mode.MENU); + globalScene.ui.setOverlayMode(Mode.MENU); break; case Mode.STARTER_SELECT: this.buttonTouch(); break; case Mode.MENU: - this.scene.ui.revertMode(); - this.scene.playSound("ui/select"); + globalScene.ui.revertMode(); + globalScene.playSound("ui/select"); break; default: return; @@ -193,9 +191,9 @@ export class UiInputs { buttonCycleOption(button: Button): void { const whitelist = [ StarterSelectUiHandler, SettingsUiHandler, RunInfoUiHandler, SettingsDisplayUiHandler, SettingsAudioUiHandler, SettingsGamepadUiHandler, SettingsKeyboardUiHandler ]; - const uiHandler = this.scene.ui?.getHandler(); + const uiHandler = globalScene.ui?.getHandler(); if (whitelist.some(handler => uiHandler instanceof handler)) { - this.scene.ui.processInput(button); + globalScene.ui.processInput(button); } else if (button === Button.V) { this.buttonInfo(true); } @@ -203,15 +201,15 @@ export class UiInputs { buttonSpeedChange(up = true): void { const settingGameSpeed = settingIndex(SettingKeys.Game_Speed); - if (up && this.scene.gameSpeed < 5) { - this.scene.gameData.saveSetting(SettingKeys.Game_Speed, Setting[settingGameSpeed].options.findIndex((item) => item.label === `${this.scene.gameSpeed}x`) + 1); - if (this.scene.ui?.getMode() === Mode.SETTINGS) { - (this.scene.ui.getHandler() as SettingsUiHandler).show([]); + if (up && globalScene.gameSpeed < 5) { + globalScene.gameData.saveSetting(SettingKeys.Game_Speed, Setting[settingGameSpeed].options.findIndex((item) => item.label === `${globalScene.gameSpeed}x`) + 1); + if (globalScene.ui?.getMode() === Mode.SETTINGS) { + (globalScene.ui.getHandler() as SettingsUiHandler).show([]); } - } else if (!up && this.scene.gameSpeed > 1) { - this.scene.gameData.saveSetting(SettingKeys.Game_Speed, Math.max(Setting[settingGameSpeed].options.findIndex((item) => item.label === `${this.scene.gameSpeed}x`) - 1, 0)); - if (this.scene.ui?.getMode() === Mode.SETTINGS) { - (this.scene.ui.getHandler() as SettingsUiHandler).show([]); + } else if (!up && globalScene.gameSpeed > 1) { + globalScene.gameData.saveSetting(SettingKeys.Game_Speed, Math.max(Setting[settingGameSpeed].options.findIndex((item) => item.label === `${globalScene.gameSpeed}x`) - 1, 0)); + if (globalScene.ui?.getMode() === Mode.SETTINGS) { + (globalScene.ui.getHandler() as SettingsUiHandler).show([]); } } } diff --git a/src/ui/ability-bar.ts b/src/ui/ability-bar.ts index a924d545852..8335f7b517c 100644 --- a/src/ui/ability-bar.ts +++ b/src/ui/ability-bar.ts @@ -1,6 +1,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; -import BattleScene from "../battle-scene"; -import Pokemon from "../field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "../field/pokemon"; import { TextStyle, addTextObject } from "./text"; import i18next from "i18next"; @@ -17,17 +17,17 @@ export default class AbilityBar extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene) { - super(scene, hiddenX, baseY); + constructor() { + super(globalScene, hiddenX, baseY); } setup(): void { - this.bg = this.scene.add.image(0, 0, "ability_bar_left"); + this.bg = globalScene.add.image(0, 0, "ability_bar_left"); this.bg.setOrigin(0, 0); this.add(this.bg); - this.abilityBarText = addTextObject(this.scene, 15, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); + this.abilityBarText = addTextObject(15, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); this.abilityBarText.setOrigin(0, 0); this.abilityBarText.setWordWrapWidth(600, true); this.add(this.abilityBarText); @@ -43,11 +43,11 @@ export default class AbilityBar extends Phaser.GameObjects.Container { return; } - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); - this.y = baseY + ((this.scene as BattleScene).currentBattle.double ? 14 : 0); - this.tween = this.scene.tweens.add({ + this.y = baseY + (globalScene.currentBattle.double ? 14 : 0); + this.tween = globalScene.tweens.add({ targets: this, x: shownX, duration: 500, @@ -75,7 +75,7 @@ export default class AbilityBar extends Phaser.GameObjects.Container { this.tween.stop(); } - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this, x: -91, duration: 500, diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 01fc5b00014..df592fc45b1 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -1,4 +1,4 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; @@ -46,8 +46,8 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { private cursorObj: Phaser.GameObjects.Image | null; - constructor(scene: BattleScene, mode: Mode | null) { - super(scene, mode); + constructor(mode: Mode | null) { + super(mode); } abstract getWindowWidth(): integer; @@ -59,19 +59,19 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { setup() { const ui = this.getUi(); - this.optionSelectContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -48); + this.optionSelectContainer = globalScene.add.container((globalScene.game.canvas.width / 6) - 1, -48); this.optionSelectContainer.setName(`option-select-${this.mode ? Mode[this.mode] : "UNKNOWN"}`); this.optionSelectContainer.setVisible(false); ui.add(this.optionSelectContainer); - this.optionSelectBg = addWindow(this.scene, 0, 0, this.getWindowWidth(), this.getWindowHeight()); + this.optionSelectBg = addWindow(0, 0, this.getWindowWidth(), this.getWindowHeight()); this.optionSelectBg.setName("option-select-bg"); this.optionSelectBg.setOrigin(1, 1); this.optionSelectContainer.add(this.optionSelectBg); this.optionSelectIcons = []; - this.scale = getTextStyleOptions(TextStyle.WINDOW, (this.scene as BattleScene).uiTheme).scale; + this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; this.setCursor(0); } @@ -84,7 +84,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { // for performance reasons, this limits how many options we can see at once. Without this, it would try to make text options for every single options // which makes the performance take a hit. If there's not enough options to do this (set to 10 at the moment) and the ui mode !== Mode.AUTO_COMPLETE, // this is ignored and the original code is untouched, with the options array being all the options from the config - if (configOptions.length >= 10 && this.scene.ui.getMode() === Mode.AUTO_COMPLETE) { + if (configOptions.length >= 10 && globalScene.ui.getMode() === Mode.AUTO_COMPLETE) { const optionsScrollTotal = configOptions.length; const optionStartIndex = this.scrollCursor; const optionEndIndex = Math.min(optionsScrollTotal, optionStartIndex + (!optionStartIndex || this.scrollCursor + (this.config?.maxOptions! - 1) >= optionsScrollTotal ? this.config?.maxOptions! - 1 : this.config?.maxOptions! - 2)); @@ -101,12 +101,12 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.optionSelectIcons.splice(0, this.optionSelectIcons.length); } - this.optionSelectText = addTextObject(this.scene, 0, 0, options.map(o => o.item ? ` ${o.label}` : o.label).join("\n"), TextStyle.WINDOW, { maxLines: options.length }); + this.optionSelectText = addTextObject(0, 0, options.map(o => o.item ? ` ${o.label}` : o.label).join("\n"), TextStyle.WINDOW, { maxLines: options.length }); this.optionSelectText.setLineSpacing(this.scale * 72); this.optionSelectText.setName("text-option-select"); this.optionSelectText.setLineSpacing(12); this.optionSelectContainer.add(this.optionSelectText); - this.optionSelectContainer.setPosition((this.scene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0)); + this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 - (this.config?.xOffset || 0), -48 + (this.config?.yOffset || 0)); this.optionSelectBg.width = Math.max(this.optionSelectText.displayWidth + 24, this.getWindowWidth()); @@ -120,7 +120,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { options.forEach((option: OptionSelectItem, i: integer) => { if (option.item) { - const itemIcon = this.scene.add.sprite(0, 0, "items", option.item); + const itemIcon = globalScene.add.sprite(0, 0, "items", option.item); itemIcon.setScale(3 * this.scale); this.optionSelectIcons.push(itemIcon); @@ -129,7 +129,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { itemIcon.setPositionRelative(this.optionSelectText, 36 * this.scale, 7 + i * (114 * this.scale - 3)); if (option.item === "candy") { - const itemOverlayIcon = this.scene.add.sprite(0, 0, "items", "candy_overlay"); + const itemOverlayIcon = globalScene.add.sprite(0, 0, "items", "candy_overlay"); itemOverlayIcon.setScale(3 * this.scale); this.optionSelectIcons.push(itemOverlayIcon); @@ -156,7 +156,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.config = args[0] as OptionSelectConfig; this.setupOptions(); - this.scene.ui.bringToTop(this.optionSelectContainer); + globalScene.ui.bringToTop(this.optionSelectContainer); this.optionSelectContainer.setVisible(true); this.scrollCursor = 0; @@ -166,7 +166,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.blockInput = true; this.optionSelectText.setAlpha(0.5); this.cursorObj?.setAlpha(0.8); - this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); + globalScene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); } return true; @@ -332,7 +332,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.optionSelectContainer.add(this.cursorObj); } diff --git a/src/ui/achv-bar.ts b/src/ui/achv-bar.ts index 6e7b3185024..9196399f40f 100644 --- a/src/ui/achv-bar.ts +++ b/src/ui/achv-bar.ts @@ -1,8 +1,8 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { Achv, getAchievementDescription } from "../system/achv"; import { Voucher } from "../system/voucher"; import { TextStyle, addTextObject } from "./text"; -import { PlayerGender } from "#enums/player-gender"; +import type { PlayerGender } from "#enums/player-gender"; export default class AchvBar extends Phaser.GameObjects.Container { private defaultWidth: number; @@ -19,33 +19,33 @@ export default class AchvBar extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene) { - super(scene, scene.game.canvas.width / 6, 0); - this.playerGender = scene.gameData.gender; + constructor() { + super(globalScene, globalScene.game.canvas.width / 6, 0); + this.playerGender = globalScene.gameData.gender; } setup(): void { this.defaultWidth = 200; this.defaultHeight = 40; - this.bg = this.scene.add.nineslice(0, 0, "achv_bar", undefined, this.defaultWidth, this.defaultHeight, 41, 6, 16, 4); + this.bg = globalScene.add.nineslice(0, 0, "achv_bar", undefined, this.defaultWidth, this.defaultHeight, 41, 6, 16, 4); this.bg.setOrigin(0, 0); this.add(this.bg); - this.icon = this.scene.add.sprite(4, 4, "items"); + this.icon = globalScene.add.sprite(4, 4, "items"); this.icon.setOrigin(0, 0); this.add(this.icon); - this.titleText = addTextObject(this.scene, 40, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); + this.titleText = addTextObject(40, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); this.titleText.setOrigin(0, 0); this.add(this.titleText); - this.scoreText = addTextObject(this.scene, 150, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); + this.scoreText = addTextObject(150, 3, "", TextStyle.MESSAGE, { fontSize: "72px" }); this.scoreText.setOrigin(1, 0); this.add(this.scoreText); - this.descriptionText = addTextObject(this.scene, 43, 16, "", TextStyle.WINDOW_ALT, { fontSize: "72px" }); + this.descriptionText = addTextObject(43, 16, "", TextStyle.WINDOW_ALT, { fontSize: "72px" }); this.descriptionText.setOrigin(0, 0); this.add(this.descriptionText); @@ -90,16 +90,16 @@ export default class AchvBar extends Phaser.GameObjects.Container { this.bg.height = Math.max(this.defaultHeight, this.titleText.displayHeight + this.descriptionText.displayHeight + 8); this.icon.y = (this.bg.height / 2) - (this.icon.height / 2); - (this.scene as BattleScene).playSound("se/achv"); + globalScene.playSound("se/achv"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6) - (this.bg.width / 2), + x: (globalScene.game.canvas.width / 6) - (this.bg.width / 2), duration: 500, ease: "Sine.easeOut" }); - this.scene.time.delayedCall(10000, () => this.hide(this.playerGender)); + globalScene.time.delayedCall(10000, () => this.hide(this.playerGender)); this.setVisible(true); this.shown = true; @@ -110,9 +110,9 @@ export default class AchvBar extends Phaser.GameObjects.Container { return; } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6), + x: (globalScene.game.canvas.width / 6), duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index a1ced6145eb..ff1e2ee9184 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -1,14 +1,16 @@ -import BattleScene from "#app/battle-scene"; import { Button } from "#enums/buttons"; import i18next from "i18next"; -import { Achv, achvs, getAchievementDescription } from "#app/system/achv"; -import { Voucher, getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#app/system/voucher"; +import type { Achv } from "#app/system/achv"; +import { achvs, getAchievementDescription } from "#app/system/achv"; +import type { Voucher } from "#app/system/voucher"; +import { getVoucherTypeIcon, getVoucherTypeName, vouchers } from "#app/system/voucher"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { addTextObject, TextStyle } from "#app/ui/text"; -import { Mode } from "#app/ui/ui"; +import type { Mode } from "#app/ui/ui"; import { addWindow } from "#app/ui/ui-theme"; import { ScrollBar } from "#app/ui/scroll-bar"; import { PlayerGender } from "#enums/player-gender"; +import { globalScene } from "#app/global-scene"; enum Page { ACHIEVEMENTS, @@ -57,8 +59,8 @@ export default class AchvsUiHandler extends MessageUiHandler { private cursorObj: Phaser.GameObjects.NineSlice | null; private currentPage: Page; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.achvsTotal = Object.keys(achvs).length; this.vouchersTotal = Object.keys(vouchers).length; @@ -68,37 +70,37 @@ export default class AchvsUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.mainContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.mainContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); - this.mainContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.mainContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - this.headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + this.headerBg = addWindow(0, 0, (globalScene.game.canvas.width / 6) - 2, 24); this.headerBg.setOrigin(0, 0); - this.headerText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL); + this.headerText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL); this.headerText.setOrigin(0, 0); this.headerText.setPositionRelative(this.headerBg, 8, 4); - this.headerActionButton = new Phaser.GameObjects.Sprite(this.scene, 0, 0, "keyboard", "ACTION.png"); + this.headerActionButton = new Phaser.GameObjects.Sprite(globalScene, 0, 0, "keyboard", "ACTION.png"); this.headerActionButton.setOrigin(0, 0); this.headerActionButton.setPositionRelative(this.headerBg, 236, 6); - this.headerActionText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW, { fontSize:"60px" }); + this.headerActionText = addTextObject(0, 0, "", TextStyle.WINDOW, { fontSize:"60px" }); this.headerActionText.setOrigin(0, 0); this.headerActionText.setPositionRelative(this.headerBg, 264, 8); // We need to get the player gender from the game data to add the correct prefix to the achievement name - const genderIndex = this.scene.gameData.gender ?? PlayerGender.MALE; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.MALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); this.achvsName = i18next.t("achv:Achievements.name", { context: genderStr }); this.vouchersName = i18next.t("voucher:vouchers"); - this.iconsBg = addWindow(this.scene, 0, this.headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - this.headerBg.height - 68); + this.iconsBg = addWindow(0, this.headerBg.height, (globalScene.game.canvas.width / 6) - 2, (globalScene.game.canvas.height / 6) - this.headerBg.height - 68); this.iconsBg.setOrigin(0, 0); const yOffset = 6; - this.scrollBar = new ScrollBar(this.scene, this.iconsBg.width - 9, this.iconsBg.y + yOffset, 4, this.iconsBg.height - yOffset * 2, this.ROWS); + this.scrollBar = new ScrollBar(this.iconsBg.width - 9, this.iconsBg.y + yOffset, 4, this.iconsBg.height - yOffset * 2, this.ROWS); - this.iconsContainer = this.scene.add.container(5, this.headerBg.height + 8); + this.iconsContainer = globalScene.add.container(5, this.headerBg.height + 8); this.icons = []; @@ -106,7 +108,7 @@ export default class AchvsUiHandler extends MessageUiHandler { const x = (a % this.COLS) * 18; const y = Math.floor(a / this.COLS) * 18; - const icon = this.scene.add.sprite(x, y, "items", "unknown"); + const icon = globalScene.add.sprite(x, y, "items", "unknown"); icon.setOrigin(0, 0); icon.setScale(0.5); @@ -114,11 +116,11 @@ export default class AchvsUiHandler extends MessageUiHandler { this.iconsContainer.add(icon); } - const titleBg = addWindow(this.scene, 0, this.headerBg.height + this.iconsBg.height, 174, 24); + const titleBg = addWindow(0, this.headerBg.height + this.iconsBg.height, 174, 24); titleBg.setOrigin(0, 0); this.titleBg = titleBg; - this.titleText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); + this.titleText = addTextObject(0, 0, "", TextStyle.WINDOW); const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize; this.titleText.setFontSize(textSize); const titleBgCenterX = titleBg.x + titleBg.width / 2; @@ -126,26 +128,26 @@ export default class AchvsUiHandler extends MessageUiHandler { this.titleText.setOrigin(0.5, 0.5); this.titleText.setPosition(titleBgCenterX, titleBgCenterY); - this.scoreContainer = this.scene.add.container(titleBg.x + titleBg.width, titleBg.y); - const scoreBg = addWindow(this.scene, 0, 0, 46, 24); + this.scoreContainer = globalScene.add.container(titleBg.x + titleBg.width, titleBg.y); + const scoreBg = addWindow(0, 0, 46, 24); scoreBg.setOrigin(0, 0); this.scoreContainer.add(scoreBg); - this.scoreText = addTextObject(this.scene, scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); + this.scoreText = addTextObject(scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); this.scoreText.setOrigin(0.5, 0.5); this.scoreContainer.add(this.scoreText); - const unlockBg = addWindow(this.scene, this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); + const unlockBg = addWindow(this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); unlockBg.setOrigin(0, 0); - this.unlockText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); + this.unlockText = addTextObject(0, 0, "", TextStyle.WINDOW); this.unlockText.setOrigin(0.5, 0.5); this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2); - const descriptionBg = addWindow(this.scene, 0, titleBg.y + titleBg.height, (this.scene.game.canvas.width / 6) - 2, 42); + const descriptionBg = addWindow(0, titleBg.y + titleBg.height, (globalScene.game.canvas.width / 6) - 2, 42); descriptionBg.setOrigin(0, 0); - const descriptionText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW, { maxLines: 2 }); + const descriptionText = addTextObject(0, 0, "", TextStyle.WINDOW, { maxLines: 2 }); descriptionText.setWordWrapWidth(1870); descriptionText.setOrigin(0, 0); descriptionText.setPositionRelative(descriptionBg, 8, 4); @@ -195,12 +197,12 @@ export default class AchvsUiHandler extends MessageUiHandler { protected showAchv(achv: Achv) { // We need to get the player gender from the game data to add the correct prefix to the achievement name - const genderIndex = this.scene.gameData.gender ?? PlayerGender.MALE; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.MALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); achv.name = i18next.t(`achv:${achv.localizationKey}.name`, { context: genderStr }); achv.description = getAchievementDescription(achv.localizationKey); - const achvUnlocks = this.scene.gameData.achvUnlocks; + const achvUnlocks = globalScene.gameData.achvUnlocks; const unlocked = achvUnlocks.hasOwnProperty(achv.id); const hidden = !unlocked && achv.secret && (!achv.parentId || !achvUnlocks.hasOwnProperty(achv.parentId)); this.titleText.setText(unlocked ? achv.name : "???"); @@ -210,7 +212,7 @@ export default class AchvsUiHandler extends MessageUiHandler { } protected showVoucher(voucher: Voucher) { - const voucherUnlocks = this.scene.gameData.voucherUnlocks; + const voucherUnlocks = globalScene.gameData.voucherUnlocks; const unlocked = voucherUnlocks.hasOwnProperty(voucher.id); this.titleText.setText(getVoucherTypeName(voucher.voucherType)); @@ -240,7 +242,7 @@ export default class AchvsUiHandler extends MessageUiHandler { } if (button === Button.CANCEL) { success = true; - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); } else { const rowIndex = Math.floor(this.cursor / this.COLS); const itemOffset = (this.scrollCursor * this.COLS); @@ -306,7 +308,7 @@ export default class AchvsUiHandler extends MessageUiHandler { let update = ret; if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1); + this.cursorObj = globalScene.add.nineslice(0, 0, "select_cursor_highlight", undefined, 16, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); this.iconsContainer.add(this.cursorObj); update = true; @@ -382,7 +384,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.headerActionText.setX(textPosition); this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - const achvUnlocks = this.scene.gameData.achvUnlocks; + const achvUnlocks = globalScene.gameData.achvUnlocks; const itemOffset = this.scrollCursor * this.COLS; const itemLimit = this.ROWS * this.COLS; @@ -422,7 +424,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.headerActionText.setX(textPosition); this.headerActionButton.setX(textPosition - this.headerActionButton.displayWidth - 4); - const voucherUnlocks = this.scene.gameData.voucherUnlocks; + const voucherUnlocks = globalScene.gameData.voucherUnlocks; const itemOffset = this.scrollCursor * this.COLS; const itemLimit = this.ROWS * this.COLS; diff --git a/src/ui/admin-ui-handler.ts b/src/ui/admin-ui-handler.ts index 269b5ac5096..e31acda6fa2 100644 --- a/src/ui/admin-ui-handler.ts +++ b/src/ui/admin-ui-handler.ts @@ -1,11 +1,12 @@ -import BattleScene from "#app/battle-scene"; import { Button } from "#app/enums/buttons"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; import { formatText } from "#app/utils"; -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; -import { ModalConfig } from "./modal-ui-handler"; +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; import { TextStyle } from "./text"; import { Mode } from "./ui"; +import { globalScene } from "#app/global-scene"; type AdminUiHandlerService = "discord" | "google"; type AdminUiHandlerServiceMode = "Link" | "Unlink"; @@ -31,8 +32,8 @@ export default class AdminUiHandler extends FormModalUiHandler { return `Username and ${service} successfully ${mode.toLowerCase()}ed`; }; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } override getModalTitle(): string { @@ -126,10 +127,10 @@ export default class AdminUiHandler extends FormModalUiHandler { const adminSearchResult: AdminSearchInfo = this.convertInputsToAdmin(); // this converts the input texts into a single object for use later const validFields = this.areFieldsValid(this.adminMode); if (validFields.error) { - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error return this.showMessage(validFields.errorMessage ?? "", adminSearchResult, true); } - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); if (this.adminMode === AdminMode.LINK) { this.adminLinkUnlink(adminSearchResult, "discord", "Link") // calls server to link discord .then(response => { @@ -157,11 +158,11 @@ export default class AdminUiHandler extends FormModalUiHandler { } showMessage(message: string, adminResult: AdminSearchInfo, isError: boolean) { - this.scene.ui.setMode(Mode.ADMIN, Object.assign(this.config, { errorMessage: message?.trim() }), this.adminMode, adminResult, isError); + globalScene.ui.setMode(Mode.ADMIN, Object.assign(this.config, { errorMessage: message?.trim() }), this.adminMode, adminResult, isError); if (isError) { - this.scene.ui.playError(); + globalScene.ui.playError(); } else { - this.scene.ui.playSelect(); + globalScene.ui.playSelect(); } } @@ -185,7 +186,7 @@ export default class AdminUiHandler extends FormModalUiHandler { this.inputs[i].setText(adminResult[aR]); if (aR === "discordId" || aR === "googleId") { // this is here to add the icons for linking/unlinking of google/discord IDs const nineSlice = this.inputContainers[i].list.find(iC => iC.type === "NineSlice"); - const img = this.scene.add.image(this.inputContainers[i].x + nineSlice!.width + this.buttonGap, this.inputContainers[i].y + (Math.floor(nineSlice!.height / 2)), adminResult[aR] === "" ? "link_icon" : "unlink_icon"); + const img = globalScene.add.image(this.inputContainers[i].x + nineSlice!.width + this.buttonGap, this.inputContainers[i].y + (Math.floor(nineSlice!.height / 2)), adminResult[aR] === "" ? "link_icon" : "unlink_icon"); img.setName(`adminBtn_${aR}`); img.setOrigin(0.5, 0.5); img.setInteractive(); @@ -194,15 +195,15 @@ export default class AdminUiHandler extends FormModalUiHandler { const mode = adminResult[aR] === "" ? "Link" : "Unlink"; // this figures out if we're linking or unlinking a service const validFields = this.areFieldsValid(this.adminMode, service); if (validFields.error) { - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); // this is here to force a loading screen to allow the admin tool to reopen again if there's an error return this.showMessage(validFields.errorMessage ?? "", adminResult, true); } this.adminLinkUnlink(this.convertInputsToAdmin(), service as AdminUiHandlerService, mode).then(response => { // attempts to link/unlink depending on the service if (response.error) { - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); return this.showMessage(response.errorType, adminResult, true); // fail } else { // success, reload panel with new results - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); this.adminSearch(adminResult) .then(response => { if (response.error) { @@ -340,16 +341,16 @@ export default class AdminUiHandler extends FormModalUiHandler { private updateAdminPanelInfo(adminSearchResult: AdminSearchInfo, mode?: AdminMode) { mode = mode ?? AdminMode.ADMIN; - this.scene.ui.setMode(Mode.ADMIN, { + globalScene.ui.setMode(Mode.ADMIN, { buttonActions: [ // we double revert here and below to go back 2 layers of menus () => { - this.scene.ui.revertMode(); - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); + globalScene.ui.revertMode(); }, () => { - this.scene.ui.revertMode(); - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); + globalScene.ui.revertMode(); } ] }, mode, adminSearchResult); diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 573cb85db70..7e9b24d1d97 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -1,15 +1,18 @@ import { addTextObject, TextStyle } from "./text"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; import { addWindow, WindowVariant } from "./ui-theme"; -import { ArenaEvent, ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; -import { BattleSceneEventType, TurnEndEvent } from "../events/battle-scene"; +import type { ArenaEvent } from "#app/events/arena"; +import { ArenaEventType, TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; +import type { TurnEndEvent } from "../events/battle-scene"; +import { BattleSceneEventType } from "../events/battle-scene"; import { ArenaTagType } from "#enums/arena-tag-type"; import TimeOfDayWidget from "./time-of-day-widget"; import * as Utils from "../utils"; -import i18next, { ParseKeys } from "i18next"; +import type { ParseKeys } from "i18next"; +import i18next from "i18next"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -45,9 +48,6 @@ export function getFieldEffectText(arenaTagType: string): string { } export class ArenaFlyout extends Phaser.GameObjects.Container { - /** An alias for the scene typecast to a {@linkcode BattleScene} */ - private battleScene: BattleScene; - /** The restricted width of the flyout which should be drawn to */ private flyoutWidth = 170; /** The restricted height of the flyout which should be drawn to */ @@ -98,62 +98,61 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { private readonly onFieldEffectChangedEvent = (event: Event) => this.onFieldEffectChanged(event); - constructor(scene: Phaser.Scene) { - super(scene, 0, 0); + constructor() { + super(globalScene, 0, 0); this.setName("arena-flyout"); - this.battleScene = this.scene as BattleScene; this.translationX = this.flyoutWidth; this.anchorX = 0; this.anchorY = -98; - this.flyoutParent = this.scene.add.container(this.anchorX - this.translationX, this.anchorY); + this.flyoutParent = globalScene.add.container(this.anchorX - this.translationX, this.anchorY); this.flyoutParent.setAlpha(0); this.add(this.flyoutParent); - this.flyoutContainer = this.scene.add.container(0, 0); + this.flyoutContainer = globalScene.add.container(0, 0); this.flyoutParent.add(this.flyoutContainer); - this.flyoutWindow = addWindow(this.scene as BattleScene, 0, 0, this.flyoutWidth, this.flyoutHeight, false, false, 0, 0, WindowVariant.THIN); + this.flyoutWindow = addWindow(0, 0, this.flyoutWidth, this.flyoutHeight, false, false, 0, 0, WindowVariant.THIN); this.flyoutContainer.add(this.flyoutWindow); - this.flyoutWindowHeader = addWindow(this.scene as BattleScene, this.flyoutWidth / 2, 0, this.flyoutWidth / 2, 14, false, false, 0, 0, WindowVariant.XTHIN); + this.flyoutWindowHeader = addWindow(this.flyoutWidth / 2, 0, this.flyoutWidth / 2, 14, false, false, 0, 0, WindowVariant.XTHIN); this.flyoutWindowHeader.setOrigin(); this.flyoutContainer.add(this.flyoutWindowHeader); - this.flyoutTextHeader = addTextObject(this.scene, this.flyoutWidth / 2, 0, i18next.t("arenaFlyout:activeBattleEffects"), TextStyle.BATTLE_INFO); + this.flyoutTextHeader = addTextObject(this.flyoutWidth / 2, 0, i18next.t("arenaFlyout:activeBattleEffects"), TextStyle.BATTLE_INFO); this.flyoutTextHeader.setFontSize(54); this.flyoutTextHeader.setAlign("center"); this.flyoutTextHeader.setOrigin(); this.flyoutContainer.add(this.flyoutTextHeader); - this.timeOfDayWidget = new TimeOfDayWidget(this.scene, (this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2)); + this.timeOfDayWidget = new TimeOfDayWidget((this.flyoutWidth / 2) + (this.flyoutWindowHeader.displayWidth / 2)); this.flyoutContainer.add(this.timeOfDayWidget); - this.flyoutTextHeaderPlayer = addTextObject(this.scene, 6, 5, i18next.t("arenaFlyout:player"), TextStyle.SUMMARY_BLUE); + this.flyoutTextHeaderPlayer = addTextObject(6, 5, i18next.t("arenaFlyout:player"), TextStyle.SUMMARY_BLUE); this.flyoutTextHeaderPlayer.setFontSize(54); this.flyoutTextHeaderPlayer.setAlign("left"); this.flyoutTextHeaderPlayer.setOrigin(0, 0); this.flyoutContainer.add(this.flyoutTextHeaderPlayer); - this.flyoutTextHeaderField = addTextObject(this.scene, this.flyoutWidth / 2, 5, i18next.t("arenaFlyout:neutral"), TextStyle.SUMMARY_GREEN); + this.flyoutTextHeaderField = addTextObject(this.flyoutWidth / 2, 5, i18next.t("arenaFlyout:neutral"), TextStyle.SUMMARY_GREEN); this.flyoutTextHeaderField.setFontSize(54); this.flyoutTextHeaderField.setAlign("center"); this.flyoutTextHeaderField.setOrigin(0.5, 0); this.flyoutContainer.add(this.flyoutTextHeaderField); - this.flyoutTextHeaderEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 5, i18next.t("arenaFlyout:enemy"), TextStyle.SUMMARY_RED); + this.flyoutTextHeaderEnemy = addTextObject(this.flyoutWidth - 6, 5, i18next.t("arenaFlyout:enemy"), TextStyle.SUMMARY_RED); this.flyoutTextHeaderEnemy.setFontSize(54); this.flyoutTextHeaderEnemy.setAlign("right"); this.flyoutTextHeaderEnemy.setOrigin(1, 0); this.flyoutContainer.add(this.flyoutTextHeaderEnemy); - this.flyoutTextPlayer = addTextObject(this.scene, 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextPlayer = addTextObject(6, 13, "", TextStyle.BATTLE_INFO); this.flyoutTextPlayer.setLineSpacing(-1); this.flyoutTextPlayer.setFontSize(48); this.flyoutTextPlayer.setAlign("left"); @@ -161,7 +160,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutContainer.add(this.flyoutTextPlayer); - this.flyoutTextField = addTextObject(this.scene, this.flyoutWidth / 2, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextField = addTextObject(this.flyoutWidth / 2, 13, "", TextStyle.BATTLE_INFO); this.flyoutTextField.setLineSpacing(-1); this.flyoutTextField.setFontSize(48); this.flyoutTextField.setAlign("center"); @@ -169,7 +168,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutContainer.add(this.flyoutTextField); - this.flyoutTextEnemy = addTextObject(this.scene, this.flyoutWidth - 6, 13, "", TextStyle.BATTLE_INFO); + this.flyoutTextEnemy = addTextObject(this.flyoutWidth - 6, 13, "", TextStyle.BATTLE_INFO); this.flyoutTextEnemy.setLineSpacing(-1); this.flyoutTextEnemy.setFontSize(48); this.flyoutTextEnemy.setAlign("right"); @@ -181,18 +180,18 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutParent.name = "Fight Flyout Parent"; // Subscribes to required events available on game start - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); } private onNewArena(event: Event) { this.fieldEffectInfo.length = 0; // Subscribes to required events available on battle start - this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.addEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.addEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.addEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); } @@ -255,7 +254,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { switch (arenaEffectChangedEvent.constructor) { case TagAddedEvent: const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; - const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; + const isArenaTrapTag = globalScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; let arenaEffectType: ArenaEffectType; if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { @@ -367,7 +366,7 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { * @param visible Should the flyout be shown? */ public toggleFlyout(visible: boolean): void { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, duration: Utils.fixedInt(125), @@ -378,13 +377,13 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { } public destroy(fromScene?: boolean): void { - this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); - this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); + globalScene.eventTarget.removeEventListener(BattleSceneEventType.NEW_ARENA, this.onNewArenaEvent); + globalScene.eventTarget.removeEventListener(BattleSceneEventType.TURN_END, this.onTurnEndEvent); - this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); - this.battleScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.removeEventListener(ArenaEventType.WEATHER_CHANGED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TERRAIN_CHANGED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_ADDED, this.onFieldEffectChangedEvent); + globalScene.arena.eventTarget.removeEventListener(ArenaEventType.TAG_REMOVED, this.onFieldEffectChangedEvent); super.destroy(fromScene); } diff --git a/src/ui/autocomplete-ui-handler.ts b/src/ui/autocomplete-ui-handler.ts index 480a3cf72d0..8754e65db77 100644 --- a/src/ui/autocomplete-ui-handler.ts +++ b/src/ui/autocomplete-ui-handler.ts @@ -1,12 +1,11 @@ import { Button } from "#enums/buttons"; -import BattleScene from "../battle-scene"; import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; import { Mode } from "./ui"; export default class AutoCompleteUiHandler extends AbstractOptionSelectUiHandler { modalContainer: Phaser.GameObjects.Container; - constructor(scene: BattleScene, mode: Mode = Mode.OPTION_SELECT) { - super(scene, mode); + constructor(mode: Mode = Mode.OPTION_SELECT) { + super(mode); } getWindowWidth(): integer { diff --git a/src/ui/awaitable-ui-handler.ts b/src/ui/awaitable-ui-handler.ts index 8256f938106..cc970358cb2 100644 --- a/src/ui/awaitable-ui-handler.ts +++ b/src/ui/awaitable-ui-handler.ts @@ -1,7 +1,7 @@ -import BattleScene from "../battle-scene"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; export default abstract class AwaitableUiHandler extends UiHandler { protected awaitingActionInput: boolean; @@ -9,8 +9,8 @@ export default abstract class AwaitableUiHandler extends UiHandler { public tutorialActive: boolean = false; public tutorialOverlay: Phaser.GameObjects.Rectangle; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } processTutorialInput(button: Button): boolean { @@ -32,7 +32,7 @@ export default abstract class AwaitableUiHandler extends UiHandler { */ initTutorialOverlay(container: Phaser.GameObjects.Container) { if (!this.tutorialOverlay) { - this.tutorialOverlay = new Phaser.GameObjects.Rectangle(this.scene, -1, -1, this.scene.scaledCanvas.width, this.scene.scaledCanvas.height, 0x070707); + this.tutorialOverlay = new Phaser.GameObjects.Rectangle(globalScene, -1, -1, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0x070707); this.tutorialOverlay.setName("tutorial-overlay"); this.tutorialOverlay.setOrigin(0, 0); this.tutorialOverlay.setAlpha(0); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 9df6da36055..2c1a53a9a52 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "../battle-scene"; import { getPokeballName } from "../data/pokeball"; import { addTextObject, getTextStyleOptions, TextStyle } from "./text"; import { Command } from "./command-ui-handler"; @@ -6,7 +5,8 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import { Button } from "#enums/buttons"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; +import { globalScene } from "#app/global-scene"; export default class BallUiHandler extends UiHandler { private pokeballSelectContainer: Phaser.GameObjects.Container; @@ -17,28 +17,28 @@ export default class BallUiHandler extends UiHandler { private scale: number = 0.1666666667; - constructor(scene: BattleScene) { - super(scene, Mode.BALL); + constructor() { + super(Mode.BALL); } setup() { const ui = this.getUi(); - this.scale = getTextStyleOptions(TextStyle.WINDOW, this.scene.uiTheme).scale; + this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; let optionsTextContent = ""; - for (let pb = 0; pb < Object.keys(this.scene.pokeballCounts).length; pb++) { + for (let pb = 0; pb < Object.keys(globalScene.pokeballCounts).length; pb++) { optionsTextContent += `${getPokeballName(pb)}\n`; } optionsTextContent += "Cancel"; - const optionsText = addTextObject(this.scene, 0, 0, optionsTextContent, TextStyle.WINDOW, { align: "right", maxLines: 6 }); + const optionsText = addTextObject(0, 0, optionsTextContent, TextStyle.WINDOW, { align: "right", maxLines: 6 }); const optionsTextWidth = optionsText.displayWidth; - this.pokeballSelectContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 51 - Math.max(64, optionsTextWidth), -49); + this.pokeballSelectContainer = globalScene.add.container((globalScene.game.canvas.width / 6) - 51 - Math.max(64, optionsTextWidth), -49); this.pokeballSelectContainer.setVisible(false); ui.add(this.pokeballSelectContainer); - this.pokeballSelectBg = addWindow(this.scene, 0, 0, 50 + Math.max(64, optionsTextWidth), 32 + 480 * this.scale); + this.pokeballSelectBg = addWindow(0, 0, 50 + Math.max(64, optionsTextWidth), 32 + 480 * this.scale); this.pokeballSelectBg.setOrigin(0, 1); this.pokeballSelectContainer.add(this.pokeballSelectBg); this.pokeballSelectContainer.add(optionsText); @@ -46,7 +46,7 @@ export default class BallUiHandler extends UiHandler { optionsText.setPositionRelative(this.pokeballSelectBg, 42, 9); optionsText.setLineSpacing(this.scale * 72); - this.countsText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW, { maxLines: 5 }); + this.countsText = addTextObject(0, 0, "", TextStyle.WINDOW, { maxLines: 5 }); this.countsText.setPositionRelative(this.pokeballSelectBg, 18, 9); this.countsText.setLineSpacing(this.scale * 72); this.pokeballSelectContainer.add(this.countsText); @@ -69,16 +69,16 @@ export default class BallUiHandler extends UiHandler { let success = false; - const pokeballTypeCount = Object.keys(this.scene.pokeballCounts).length; + const pokeballTypeCount = Object.keys(globalScene.pokeballCounts).length; if (button === Button.ACTION || button === Button.CANCEL) { - const commandPhase = this.scene.getCurrentPhase() as CommandPhase; + const commandPhase = globalScene.getCurrentPhase() as CommandPhase; success = true; if (button === Button.ACTION && this.cursor < pokeballTypeCount) { - if (this.scene.pokeballCounts[this.cursor]) { + if (globalScene.pokeballCounts[this.cursor]) { if (commandPhase.handleCommand(Command.BALL, this.cursor)) { - this.scene.ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); - this.scene.ui.setMode(Mode.MESSAGE); + globalScene.ui.setMode(Mode.COMMAND, commandPhase.getFieldIndex()); + globalScene.ui.setMode(Mode.MESSAGE); success = true; } } else { @@ -107,14 +107,14 @@ export default class BallUiHandler extends UiHandler { } updateCounts() { - this.countsText.setText(Object.values(this.scene.pokeballCounts).map(c => `x${c}`).join("\n")); + this.countsText.setText(Object.values(globalScene.pokeballCounts).map(c => `x${c}`).join("\n")); } setCursor(cursor: integer): boolean { const ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.pokeballSelectContainer.add(this.cursorObj); } diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 4541a2bfefa..c716705452c 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,9 +1,10 @@ -import { default as Pokemon } from "../field/pokemon"; +import type { default as Pokemon } from "../field/pokemon"; import { addTextObject, TextStyle } from "./text"; import * as Utils from "../utils"; -import BattleScene from "#app/battle-scene"; -import Move from "#app/data/move"; -import { BattleSceneEventType, BerryUsedEvent, MoveUsedEvent } from "../events/battle-scene"; +import { globalScene } from "#app/global-scene"; +import type Move from "#app/data/move"; +import type { BerryUsedEvent, MoveUsedEvent } from "../events/battle-scene"; +import { BattleSceneEventType } from "../events/battle-scene"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { UiTheme } from "#enums/ui-theme"; @@ -22,9 +23,6 @@ interface MoveInfo { /** A Flyout Menu attached to each {@linkcode BattleInfo} object on the field UI */ export default class BattleFlyout extends Phaser.GameObjects.Container { - /** An alias for the scene typecast to a {@linkcode BattleScene} */ - private battleScene: BattleScene; - /** Is this object linked to a player's Pokemon? */ private player: boolean; @@ -63,9 +61,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { private readonly onMoveUsedEvent = (event: Event) => this.onMoveUsed(event); private readonly onBerryUsedEvent = (event: Event) => this.onBerryUsed(event); - constructor(scene: Phaser.Scene, player: boolean) { - super(scene, 0, 0); - this.battleScene = scene as BattleScene; + constructor(player: boolean) { + super(globalScene, 0, 0); // Note that all player based flyouts are disabled. This is included in case of future development this.player = player; @@ -74,23 +71,22 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { this.anchorX = (this.player ? -130 : -40); this.anchorY = -2.5 + (this.player ? -18.5 : -13); - this.flyoutParent = this.scene.add.container(this.anchorX - this.translationX, this.anchorY); + this.flyoutParent = globalScene.add.container(this.anchorX - this.translationX, this.anchorY); this.flyoutParent.setAlpha(0); this.add(this.flyoutParent); // Load the background image - this.flyoutBackground = this.scene.add.sprite(0, 0, "pbinfo_enemy_boss_stats"); + this.flyoutBackground = globalScene.add.sprite(0, 0, "pbinfo_enemy_boss_stats"); this.flyoutBackground.setOrigin(0, 0); this.flyoutParent.add(this.flyoutBackground); - this.flyoutContainer = this.scene.add.container(44 + (this.player ? -this.flyoutWidth : 0), 2); + this.flyoutContainer = globalScene.add.container(44 + (this.player ? -this.flyoutWidth : 0), 2); this.flyoutParent.add(this.flyoutContainer); // Loops through and sets the position of each text object according to the width and height of the flyout for (let i = 0; i < 4; i++) { this.flyoutText[i] = addTextObject( - this.scene, (this.flyoutWidth / 4) + (this.flyoutWidth / 2) * (i % 2), (this.flyoutHeight / 4) + (this.flyoutHeight / 2) * (i < 2 ? 0 : 1), "???", TextStyle.BATTLE_INFO); this.flyoutText[i].setFontSize(45); @@ -102,9 +98,9 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { this.flyoutContainer.add(this.flyoutText); this.flyoutContainer.add( - new Phaser.GameObjects.Rectangle(this.scene, this.flyoutWidth / 2, 0, 1, this.flyoutHeight + (this.battleScene.uiTheme === UiTheme.LEGACY ? 1 : 0), 0x212121).setOrigin(0.5, 0)); + new Phaser.GameObjects.Rectangle(globalScene, this.flyoutWidth / 2, 0, 1, this.flyoutHeight + (globalScene.uiTheme === UiTheme.LEGACY ? 1 : 0), 0x212121).setOrigin(0.5, 0)); this.flyoutContainer.add( - new Phaser.GameObjects.Rectangle(this.scene, 0, this.flyoutHeight / 2, this.flyoutWidth + 6, 1, 0x212121).setOrigin(0, 0.5)); + new Phaser.GameObjects.Rectangle(globalScene, 0, this.flyoutHeight / 2, this.flyoutWidth + 6, 1, 0x212121).setOrigin(0, 0.5)); } /** @@ -117,8 +113,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`; this.flyoutParent.name = `Flyout Parent ${getPokemonNameWithAffix(this.pokemon)}`; - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); } /** Sets and formats the text property for all {@linkcode Phaser.GameObjects.Text} in the flyoutText array */ @@ -176,7 +172,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { toggleFlyout(visible: boolean): void { this.flyoutVisible = visible; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.flyoutParent, x: visible ? this.anchorX : this.anchorX - this.translationX, duration: Utils.fixedInt(125), @@ -186,8 +182,8 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { } destroy(fromScene?: boolean): void { - this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); - this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); + globalScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); + globalScene.eventTarget.removeEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); super.destroy(fromScene); } diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 72447988bdd..ca98d4c9d10 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -1,10 +1,10 @@ -import { EnemyPokemon, default as Pokemon } from "../field/pokemon"; +import type { EnemyPokemon, default as Pokemon } from "../field/pokemon"; import { getLevelTotalExp, getLevelRelExp } from "../data/exp"; import * as Utils from "../utils"; import { addTextObject, TextStyle } from "./text"; import { getGenderSymbol, getGenderColor, Gender } from "../data/gender"; import { StatusEffect } from "#enums/status-effect"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; import { getVariantTint } from "#app/data/variant"; @@ -76,8 +76,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { private readonly statOrderPlayer = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; private readonly statOrderEnemy = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; - constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) { - super(scene, x, y); + constructor(x: number, y: number, player: boolean) { + super(globalScene, x, y); this.baseY = y; this.player = player; this.mini = !player; @@ -96,31 +96,31 @@ export default class BattleInfo extends Phaser.GameObjects.Container { // Initially invisible and shown via Pokemon.showInfo this.setVisible(false); - this.box = this.scene.add.sprite(0, 0, this.getTextureName()); + this.box = globalScene.add.sprite(0, 0, this.getTextureName()); this.box.setName("box"); this.box.setOrigin(1, 0.5); this.add(this.box); - this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO); + this.nameText = addTextObject(player ? -115 : -124, player ? -15.2 : -11.2, "", TextStyle.BATTLE_INFO); this.nameText.setName("text_name"); this.nameText.setOrigin(0, 0); this.add(this.nameText); - this.genderText = addTextObject(this.scene, 0, 0, "", TextStyle.BATTLE_INFO); + this.genderText = addTextObject(0, 0, "", TextStyle.BATTLE_INFO); this.genderText.setName("text_gender"); this.genderText.setOrigin(0, 0); this.genderText.setPositionRelative(this.nameText, 0, 2); this.add(this.genderText); if (!this.player) { - this.ownedIcon = this.scene.add.sprite(0, 0, "icon_owned"); + this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned"); this.ownedIcon.setName("icon_owned"); this.ownedIcon.setVisible(false); this.ownedIcon.setOrigin(0, 0); this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75); this.add(this.ownedIcon); - this.championRibbon = this.scene.add.sprite(0, 0, "champion_ribbon"); + this.championRibbon = globalScene.add.sprite(0, 0, "champion_ribbon"); this.championRibbon.setName("icon_champion_ribbon"); this.championRibbon.setVisible(false); this.championRibbon.setOrigin(0, 0); @@ -128,7 +128,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.add(this.championRibbon); } - this.teraIcon = this.scene.add.sprite(0, 0, "icon_tera"); + this.teraIcon = globalScene.add.sprite(0, 0, "icon_tera"); this.teraIcon.setName("icon_tera"); this.teraIcon.setVisible(false); this.teraIcon.setOrigin(0, 0); @@ -137,7 +137,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.teraIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.add(this.teraIcon); - this.shinyIcon = this.scene.add.sprite(0, 0, "shiny_star"); + this.shinyIcon = globalScene.add.sprite(0, 0, "shiny_star"); this.shinyIcon.setName("icon_shiny"); this.shinyIcon.setVisible(false); this.shinyIcon.setOrigin(0, 0); @@ -146,7 +146,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.add(this.shinyIcon); - this.fusionShinyIcon = this.scene.add.sprite(0, 0, "shiny_star_2"); + this.fusionShinyIcon = globalScene.add.sprite(0, 0, "shiny_star_2"); this.fusionShinyIcon.setName("icon_fusion_shiny"); this.fusionShinyIcon.setVisible(false); this.fusionShinyIcon.setOrigin(0, 0); @@ -154,7 +154,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); this.add(this.fusionShinyIcon); - this.splicedIcon = this.scene.add.sprite(0, 0, "icon_spliced"); + this.splicedIcon = globalScene.add.sprite(0, 0, "icon_spliced"); this.splicedIcon.setName("icon_spliced"); this.splicedIcon.setVisible(false); this.splicedIcon.setOrigin(0, 0); @@ -163,42 +163,42 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.add(this.splicedIcon); - this.statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses")); + this.statusIndicator = globalScene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses")); this.statusIndicator.setName("icon_status"); this.statusIndicator.setVisible(false); this.statusIndicator.setOrigin(0, 0); this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5); this.add(this.statusIndicator); - this.levelContainer = this.scene.add.container(player ? -41 : -50, player ? -10 : -5); + this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5); this.levelContainer.setName("container_level"); this.add(this.levelContainer); - const levelOverlay = this.scene.add.image(0, 0, "overlay_lv"); + const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); this.levelContainer.add(levelOverlay); - this.hpBar = this.scene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp"); + this.hpBar = globalScene.add.image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp"); this.hpBar.setName("hp_bar"); this.hpBar.setOrigin(0); this.add(this.hpBar); this.hpBarSegmentDividers = []; - this.levelNumbersContainer = this.scene.add.container(9.5, (this.scene as BattleScene).uiTheme ? 0 : -0.5); + this.levelNumbersContainer = globalScene.add.container(9.5, globalScene.uiTheme ? 0 : -0.5); this.levelNumbersContainer.setName("container_level"); this.levelContainer.add(this.levelNumbersContainer); if (this.player) { - this.hpNumbersContainer = this.scene.add.container(-15, 10); + this.hpNumbersContainer = globalScene.add.container(-15, 10); this.hpNumbersContainer.setName("container_hp"); this.add(this.hpNumbersContainer); - const expBar = this.scene.add.image(-98, 18, "overlay_exp"); + const expBar = globalScene.add.image(-98, 18, "overlay_exp"); expBar.setName("overlay_exp"); expBar.setOrigin(0); this.add(expBar); - const expMaskRect = this.scene.make.graphics({}); + const expMaskRect = globalScene.make.graphics({}); expMaskRect.setScale(6); expMaskRect.fillStyle(0xFFFFFF); expMaskRect.beginPath(); @@ -212,12 +212,12 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.expMaskRect = expMaskRect; } - this.statsContainer = this.scene.add.container(0, 0); + this.statsContainer = globalScene.add.container(0, 0); this.statsContainer.setName("container_stats"); this.statsContainer.setAlpha(0); this.add(this.statsContainer); - this.statsBox = this.scene.add.sprite(0, 0, `${this.getTextureName()}_stats`); + this.statsBox = globalScene.add.sprite(0, 0, `${this.getTextureName()}_stats`); this.statsBox.setName("box_stats"); this.statsBox.setOrigin(1, 0.5); this.statsContainer.add(this.statsBox); @@ -225,7 +225,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const statLabels: Phaser.GameObjects.Sprite[] = []; this.statNumbers = []; - this.statValuesContainer = this.scene.add.container(0, 0); + this.statValuesContainer = globalScene.add.container(0, 0); this.statsContainer.add(this.statValuesContainer); // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy @@ -249,13 +249,13 @@ export default class BattleInfo extends Phaser.GameObjects.Container { statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us } - const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]); + const statLabel = globalScene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]); statLabel.setName("icon_stat_label_" + i.toString()); statLabel.setOrigin(0, 0); statLabels.push(statLabel); this.statValuesContainer.add(statLabel); - const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.statOrder[i] !== Stat.HP ? "3" : "empty"); + const statNumber = globalScene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.statOrder[i] !== Stat.HP ? "3" : "empty"); statNumber.setName("icon_stat_number_" + i.toString()); statNumber.setOrigin(0, 0); this.statNumbers.push(statNumber); @@ -269,35 +269,35 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }); if (!this.player) { - this.flyoutMenu = new BattleFlyout(this.scene, this.player); + this.flyoutMenu = new BattleFlyout(this.player); this.add(this.flyoutMenu); this.moveBelow(this.flyoutMenu, this.box); } - this.type1Icon = this.scene.add.sprite(player ? -139 : -15, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type1`); + this.type1Icon = globalScene.add.sprite(player ? -139 : -15, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type1`); this.type1Icon.setName("icon_type_1"); this.type1Icon.setOrigin(0, 0); this.add(this.type1Icon); - this.type2Icon = this.scene.add.sprite(player ? -139 : -15, player ? -1 : -2.5, `pbinfo_${player ? "player" : "enemy"}_type2`); + this.type2Icon = globalScene.add.sprite(player ? -139 : -15, player ? -1 : -2.5, `pbinfo_${player ? "player" : "enemy"}_type2`); this.type2Icon.setName("icon_type_2"); this.type2Icon.setOrigin(0, 0); this.add(this.type2Icon); - this.type3Icon = this.scene.add.sprite(player ? -154 : 0, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type`); + this.type3Icon = globalScene.add.sprite(player ? -154 : 0, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type`); this.type3Icon.setName("icon_type_3"); this.type3Icon.setOrigin(0, 0); this.add(this.type3Icon); if (!this.player) { - this.effectivenessContainer = this.scene.add.container(0, 0); + this.effectivenessContainer = globalScene.add.container(0, 0); this.effectivenessContainer.setPositionRelative(this.type1Icon, 22, 4); this.effectivenessContainer.setVisible(false); this.add(this.effectivenessContainer); - this.effectivenessText = addTextObject(this.scene, 5, 4.5, "", TextStyle.BATTLE_INFO); - this.effectivenessWindow = addWindow((this.scene as BattleScene), 0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); + this.effectivenessText = addTextObject(5, 4.5, "", TextStyle.BATTLE_INFO); + this.effectivenessWindow = addWindow(0, 0, 0, 20, undefined, false, undefined, undefined, WindowVariant.XTHIN); this.effectivenessContainer.add(this.effectivenessWindow); this.effectivenessContainer.add(this.effectivenessText); @@ -327,18 +327,18 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.teraIcon.setVisible(this.lastTeraType !== Type.UNKNOWN); this.teraIcon.on("pointerover", () => { if (this.lastTeraType !== Type.UNKNOWN) { - (this.scene as BattleScene).ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) })); + globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) })); } }); - this.teraIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip()); const isFusion = pokemon.isFusion(); this.splicedIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 2.5); this.splicedIcon.setVisible(isFusion); if (this.splicedIcon.visible) { - this.splicedIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`)); - this.splicedIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.splicedIcon.on("pointerover", () => globalScene.ui.showTooltip("", `${pokemon.species.getName(pokemon.formIndex)}/${pokemon.fusionSpecies?.getName(pokemon.fusionFormIndex)}`)); + this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip()); } const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; @@ -352,8 +352,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const shinyDescriptor = doubleShiny || baseVariant ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` : ""; - this.shinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`)); - this.shinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.shinyIcon.on("pointerover", () => globalScene.ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`)); + this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); } this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); @@ -364,15 +364,15 @@ export default class BattleInfo extends Phaser.GameObjects.Container { if (!this.player) { if (this.nameText.visible) { - this.nameText.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", i18next.t("battleInfo:generation", { generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`) }))); - this.nameText.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.nameText.on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("battleInfo:generation", { generation: i18next.t(`starterSelectUiHandler:gen${pokemon.species.generation}`) }))); + this.nameText.on("pointerout", () => globalScene.ui.hideTooltip()); } - const dexEntry = pokemon.scene.gameData.dexData[pokemon.species.speciesId]; + const dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; this.ownedIcon.setVisible(!!dexEntry.caughtAttr); const opponentPokemonDexAttr = pokemon.getDexAttr(); - if (pokemon.scene.gameMode.isClassic) { - if (pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0) { + if (globalScene.gameMode.isClassic) { + if (globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].classicWinCount > 0 && globalScene.gameData.starterData[pokemon.species.getRootSpeciesId(true)].classicWinCount > 0) { this.championRibbon.setVisible(true); } } @@ -380,7 +380,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { // Check if Player owns all genders and forms of the Pokemon const missingDexAttrs = ((dexEntry.caughtAttr & opponentPokemonDexAttr) < opponentPokemonDexAttr); - const ownedAbilityAttrs = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; + const ownedAbilityAttrs = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr; // Check if the player owns ability for the root form const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(ownedAbilityAttrs); @@ -469,7 +469,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } toggleStats(visible: boolean): void { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.statsContainer, duration: Utils.fixedInt(125), ease: "Sine.easeInOut", @@ -501,11 +501,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } if (this.boss && this.bossSegments > 1) { - const uiTheme = (this.scene as BattleScene).uiTheme; + const uiTheme = globalScene.uiTheme; const maxHp = pokemon.getMaxHp(); for (let s = 1; s < this.bossSegments; s++) { const dividerX = (Math.round((maxHp / this.bossSegments) * s) / maxHp) * this.hpBar.width; - const divider = this.scene.add.rectangle(0, 0, 1, this.hpBar.height - (uiTheme ? 0 : 1), pokemon.bossSegmentIndex >= s ? 0xFFFFFF : 0x404040); + const divider = globalScene.add.rectangle(0, 0, 1, this.hpBar.height - (uiTheme ? 0 : 1), pokemon.bossSegmentIndex >= s ? 0xFFFFFF : 0x404040); divider.setOrigin(0.5, 0); divider.setName("hpBar_divider_" + s.toString()); this.add(divider); @@ -531,7 +531,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { updateInfo(pokemon: Pokemon, instant?: boolean): Promise { return new Promise(resolve => { - if (!this.scene) { + if (!globalScene) { return resolve(); } @@ -595,11 +595,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const updatePokemonHp = () => { let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; - const speed = (this.scene as BattleScene).hpBarSpeed; + const speed = globalScene.hpBarSpeed; if (speed) { duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.hpBar, ease: "Sine.easeOut", scaleX: pokemon.getHpRatio(true), @@ -625,7 +625,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }; if (this.player) { - const isLevelCapped = pokemon.level >= (this.scene as BattleScene).getMaxExpLevel(); + const isLevelCapped = pokemon.level >= globalScene.getMaxExpLevel(); if ((this.lastExp !== pokemon.exp || this.lastLevel !== pokemon.level)) { const originalResolve = resolve; @@ -663,7 +663,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { let displayName = pokemon.getNameToRender().replace(/[♂♀]/g, ""); let nameTextWidth: number; - const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.BATTLE_INFO); + const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO); nameTextWidth = nameSizeTest.displayWidth; while (nameTextWidth > (this.player || !this.boss ? 60 : 98) - ((pokemon.gender !== Gender.GENDERLESS ? 6 : 0) + (pokemon.fusionSpecies ? 8 : 0) + (pokemon.isShiny() ? 8 : 0) + (Math.min(pokemon.level.toString().length, 3) - 3) * 8)) { @@ -688,7 +688,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const relLevelExp = getLevelRelExp(this.lastLevel + 1, pokemon.species.growthRate); const levelExp = levelUp ? relLevelExp : pokemon.levelExp; let ratio = relLevelExp ? levelExp / relLevelExp : 0; - if (this.lastLevel >= (this.scene as BattleScene).getMaxExpLevel(true)) { + if (this.lastLevel >= globalScene.getMaxExpLevel(true)) { if (levelUp) { ratio = 1; } else { @@ -698,7 +698,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } const durationMultiplier = Phaser.Tweens.Builders.GetEaseFunction("Sine.easeIn")(1 - (Math.max(this.lastLevel - 100, 0) / 150)); let duration = this.visible && !instant ? (((levelExp - this.lastLevelExp) / relLevelExp) * BattleInfo.EXP_GAINS_DURATION_BASE) * durationMultiplier * levelDurationMultiplier : 0; - const speed = (this.scene as BattleScene).expGainsSpeed; + const speed = globalScene.expGainsSpeed; if (speed && speed >= ExpGainsSpeed.DEFAULT) { duration = speed >= ExpGainsSpeed.SKIP ? ExpGainsSpeed.DEFAULT : duration / Math.pow(2, speed); } @@ -710,24 +710,24 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastLevelExp = pokemon.levelExp; } if (duration) { - (this.scene as BattleScene).playSound("se/exp"); + globalScene.playSound("se/exp"); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.expMaskRect, ease: "Sine.easeIn", x: ratio * 510, duration: duration, onComplete: () => { - if (!this.scene) { + if (!globalScene) { return resolve(); } if (duration) { - this.scene.sound.stopByKey("se/exp"); + globalScene.sound.stopByKey("se/exp"); } if (ratio === 1) { - (this.scene as BattleScene).playSound("se/level_up"); + globalScene.playSound("se/level_up"); this.setLevel(this.lastLevel); - this.scene.time.delayedCall(500 * levelDurationMultiplier, () => { + globalScene.time.delayedCall(500 * levelDurationMultiplier, () => { this.expMaskRect.x = 0; this.updateInfo(pokemon, instant).then(() => resolve()); }); @@ -740,17 +740,17 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } setLevel(level: integer): void { - const isCapped = level >= (this.scene as BattleScene).getMaxExpLevel(); + const isCapped = level >= globalScene.getMaxExpLevel(); this.levelNumbersContainer.removeAll(true); const levelStr = level.toString(); for (let i = 0; i < levelStr.length; i++) { - this.levelNumbersContainer.add(this.scene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i])); + this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i])); } this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); } setHpNumbers(hp: integer, maxHp: integer): void { - if (!this.player || !this.scene) { + if (!this.player || !globalScene) { return; } this.hpNumbersContainer.removeAll(true); @@ -758,11 +758,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { const maxHpStr = maxHp.toString(); let offset = 0; for (let i = maxHpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", maxHpStr[i])); } - this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, "numbers", "/")); + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", "/")); for (let i = hpStr.length - 1; i >= 0; i--) { - this.hpNumbersContainer.add(this.scene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); + this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); } } @@ -797,7 +797,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } this.currentEffectiveness = effectiveness; - if (!(this.scene as BattleScene).typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) { + if (!globalScene.typeHints || effectiveness === undefined || this.flyoutMenu?.flyoutVisible) { this.effectivenessContainer.setVisible(false); return; } @@ -817,14 +817,14 @@ export default class BattleInfo extends Phaser.GameObjects.Container { } export class PlayerBattleInfo extends BattleInfo { - constructor(scene: Phaser.Scene) { - super(scene, Math.floor(scene.game.canvas.width / 6) - 10, -72, true); + constructor() { + super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true); } } export class EnemyBattleInfo extends BattleInfo { - constructor(scene: Phaser.Scene) { - super(scene, 140, -141, false); + constructor() { + super(140, -141, false); } setMini(mini: boolean): void { } // Always mini diff --git a/src/ui/battle-message-ui-handler.ts b/src/ui/battle-message-ui-handler.ts index 180fc66ed9b..2c0998b79ab 100644 --- a/src/ui/battle-message-ui-handler.ts +++ b/src/ui/battle-message-ui-handler.ts @@ -1,12 +1,13 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./text"; import { Mode } from "./ui"; import MessageUiHandler from "./message-ui-handler"; import { addWindow } from "./ui-theme"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Button } from "#enums/buttons"; import i18next from "i18next"; -import { Stat, PERMANENT_STATS, getStatKey } from "#app/enums/stat"; +import type { Stat } from "#app/enums/stat"; +import { PERMANENT_STATS, getStatKey } from "#app/enums/stat"; export default class BattleMessageUiHandler extends MessageUiHandler { private levelUpStatsContainer: Phaser.GameObjects.Container; @@ -22,8 +23,8 @@ export default class BattleMessageUiHandler extends MessageUiHandler { public readonly wordWrapWidth: number = 1780; - constructor(scene: BattleScene) { - super(scene, Mode.MESSAGE); + constructor() { + super(Mode.MESSAGE); } setup(): void { @@ -32,36 +33,36 @@ export default class BattleMessageUiHandler extends MessageUiHandler { this.textTimer = null; this.textCallbackTimer = null; - this.bg = this.scene.add.sprite(0, 0, "bg", this.scene.windowType); + this.bg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType); this.bg.setName("sprite-battle-msg-bg"); this.bg.setOrigin(0, 1); ui.add(this.bg); - this.commandWindow = addWindow(this.scene, 202, 0, 118, 48); + this.commandWindow = addWindow(202, 0, 118, 48); this.commandWindow.setName("window-command"); this.commandWindow.setOrigin(0, 1); this.commandWindow.setVisible(false); ui.add(this.commandWindow); - this.movesWindowContainer = this.scene.add.container(0, 0); + this.movesWindowContainer = globalScene.add.container(0, 0); this.movesWindowContainer.setName("moves-bg"); this.movesWindowContainer.setVisible(false); - const movesWindow = addWindow(this.scene, 0, 0, 243, 48); + const movesWindow = addWindow(0, 0, 243, 48); movesWindow.setName("moves-window"); movesWindow.setOrigin(0, 1); - const moveDetailsWindow = addWindow(this.scene, 240, 0, 80, 48, false, false, -1, 132); + const moveDetailsWindow = addWindow(240, 0, 80, 48, false, false, -1, 132); moveDetailsWindow.setName("move-details-window"); moveDetailsWindow.setOrigin(0, 1); this.movesWindowContainer.add([ movesWindow, moveDetailsWindow ]); ui.add(this.movesWindowContainer); - const messageContainer = this.scene.add.container(12, -39); + const messageContainer = globalScene.add.container(12, -39); ui.add(messageContainer); - const message = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE, { + const message = addTextObject(0, 0, "", TextStyle.MESSAGE, { maxLines: 2, wordWrap: { width: this.wordWrapWidth @@ -71,13 +72,13 @@ export default class BattleMessageUiHandler extends MessageUiHandler { this.message = message; - this.nameBoxContainer = this.scene.add.container(0, -16); + this.nameBoxContainer = globalScene.add.container(0, -16); this.nameBoxContainer.setVisible(false); - this.nameBox = this.scene.add.nineslice(0, 0, "namebox", this.scene.windowType, 72, 16, 8, 8, 5, 5); + this.nameBox = globalScene.add.nineslice(0, 0, "namebox", globalScene.windowType, 72, 16, 8, 8, 5, 5); this.nameBox.setOrigin(0, 0); - this.nameText = addTextObject(this.scene, 8, 0, "Rival", TextStyle.MESSAGE, { maxLines: 1 }); + this.nameText = addTextObject(8, 0, "Rival", TextStyle.MESSAGE, { maxLines: 1 }); this.nameBoxContainer.add(this.nameBox); this.nameBoxContainer.add(this.nameText); @@ -85,13 +86,13 @@ export default class BattleMessageUiHandler extends MessageUiHandler { this.initPromptSprite(messageContainer); - const levelUpStatsContainer = this.scene.add.container(0, 0); + const levelUpStatsContainer = globalScene.add.container(0, 0); levelUpStatsContainer.setVisible(false); ui.add(levelUpStatsContainer); this.levelUpStatsContainer = levelUpStatsContainer; - const levelUpStatsLabelsContent = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 73, -94, "", TextStyle.WINDOW, { maxLines: 6 }); + const levelUpStatsLabelsContent = addTextObject((globalScene.game.canvas.width / 6) - 73, -94, "", TextStyle.WINDOW, { maxLines: 6 }); levelUpStatsLabelsContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5); let levelUpStatsLabelText = ""; @@ -101,19 +102,19 @@ export default class BattleMessageUiHandler extends MessageUiHandler { levelUpStatsLabelsContent.text = levelUpStatsLabelText; levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth; - const levelUpStatsBg = addWindow(this.scene, (this.scene.game.canvas.width / 6), -100, 80 + levelUpStatsLabelsContent.displayWidth, 100); + const levelUpStatsBg = addWindow((globalScene.game.canvas.width / 6), -100, 80 + levelUpStatsLabelsContent.displayWidth, 100); levelUpStatsBg.setOrigin(1, 0); levelUpStatsContainer.add(levelUpStatsBg); levelUpStatsContainer.add(levelUpStatsLabelsContent); - const levelUpStatsIncrContent = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 50, -94, "+\n+\n+\n+\n+\n+", TextStyle.WINDOW, { maxLines: 6 }); + const levelUpStatsIncrContent = addTextObject((globalScene.game.canvas.width / 6) - 50, -94, "+\n+\n+\n+\n+\n+", TextStyle.WINDOW, { maxLines: 6 }); levelUpStatsIncrContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5); levelUpStatsContainer.add(levelUpStatsIncrContent); this.levelUpStatsIncrContent = levelUpStatsIncrContent; - const levelUpStatsValuesContent = addBBCodeTextObject(this.scene, (this.scene.game.canvas.width / 6) - 7, -94, "", TextStyle.WINDOW, { maxLines: 6, lineSpacing: 5 }); + const levelUpStatsValuesContent = addBBCodeTextObject((globalScene.game.canvas.width / 6) - 7, -94, "", TextStyle.WINDOW, { maxLines: 6, lineSpacing: 5 }); levelUpStatsValuesContent.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5); levelUpStatsValuesContent.setOrigin(1, 0); levelUpStatsValuesContent.setAlign("right"); @@ -167,10 +168,10 @@ export default class BattleMessageUiHandler extends MessageUiHandler { promptLevelUpStats(partyMemberIndex: integer, prevStats: integer[], showTotals: boolean): Promise { return new Promise(resolve => { - if (!this.scene.showLevelUpStats) { + if (!globalScene.showLevelUpStats) { return resolve(); } - const newStats = (this.scene as BattleScene).getPlayerParty()[partyMemberIndex].stats; + const newStats = globalScene.getPlayerParty()[partyMemberIndex].stats; let levelUpStatsValuesText = ""; for (const s of PERMANENT_STATS) { levelUpStatsValuesText += `${showTotals ? newStats[s] : newStats[s] - prevStats[s]}\n`; @@ -192,7 +193,7 @@ export default class BattleMessageUiHandler extends MessageUiHandler { promptIvs(pokemonId: integer, ivs: integer[], shownIvsCount: integer): Promise { return new Promise(resolve => { - this.scene.executeWithSeedOffset(() => { + globalScene.executeWithSeedOffset(() => { let levelUpStatsValuesText = ""; const shownStats = this.getTopIvs(ivs, shownIvsCount); for (const s of PERMANENT_STATS) { @@ -226,9 +227,9 @@ export default class BattleMessageUiHandler extends MessageUiHandler { } getIvDescriptor(value: integer, typeIv: integer, pokemonId: integer): string { - const starterSpecies = this.scene.getPokemonById(pokemonId)!.species.getRootSpeciesId(); // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists - const starterIvs: number[] = this.scene.gameData.dexData[starterSpecies].ivs; - const uiTheme = (this.scene as BattleScene).uiTheme; // Assuming uiTheme is accessible + const starterSpecies = globalScene.getPokemonById(pokemonId)!.species.getRootSpeciesId(); // we are using getRootSpeciesId() here because we want to check against the baby form, not the mid form if it exists + const starterIvs: number[] = globalScene.gameData.dexData[starterSpecies].ivs; + const uiTheme = globalScene.uiTheme; // Assuming uiTheme is accessible // Function to wrap text in color based on comparison const coloredText = (text: string, isBetter: boolean, ivValue) => { diff --git a/src/ui/bgm-bar.ts b/src/ui/bgm-bar.ts index 616b3ff87cf..0cd3da71248 100644 --- a/src/ui/bgm-bar.ts +++ b/src/ui/bgm-bar.ts @@ -1,7 +1,7 @@ -import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; import i18next from "i18next"; import * as Utils from "#app/utils"; +import { globalScene } from "#app/global-scene"; const hiddenX = -150; const shownX = 0; @@ -16,20 +16,20 @@ export default class BgmBar extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene) { - super(scene, hiddenX, baseY); + constructor() { + super(globalScene, hiddenX, baseY); } setup(): void { this.defaultWidth = 230; this.defaultHeight = 100; - this.bg = this.scene.add.nineslice(-5, -5, "bgm_bar", undefined, this.defaultWidth, this.defaultHeight, 0, 0, 10, 10); + this.bg = globalScene.add.nineslice(-5, -5, "bgm_bar", undefined, this.defaultWidth, this.defaultHeight, 0, 0, 10, 10); this.bg.setOrigin(0, 0); this.add(this.bg); - this.musicText = addTextObject(this.scene, 5, 5, "", TextStyle.BGM_BAR); + this.musicText = addTextObject(5, 5, "", TextStyle.BGM_BAR); this.musicText.setOrigin(0, 0); this.musicText.setWordWrapWidth(650, true); @@ -52,7 +52,7 @@ export default class BgmBar extends Phaser.GameObjects.Container { this.bg.width = Math.min(this.defaultWidth, this.musicText.displayWidth + 23); this.bg.height = Math.min(this.defaultHeight, this.musicText.displayHeight + 20); - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); this.y = baseY; } @@ -72,11 +72,11 @@ export default class BgmBar extends Phaser.GameObjects.Container { return; } - if (!(this.scene as BattleScene).showBgmBar) { + if (!globalScene.showBgmBar) { this.setVisible(false); return; } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, x: visible ? shownX : hiddenX, duration: 500, diff --git a/src/ui/candy-bar.ts b/src/ui/candy-bar.ts index 14015f02259..81478f6fa7c 100644 --- a/src/ui/candy-bar.ts +++ b/src/ui/candy-bar.ts @@ -1,8 +1,9 @@ -import BattleScene, { starterColors } from "../battle-scene"; +import { starterColors } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "./text"; import { argbFromRgba } from "@material/material-color-utilities"; import * as Utils from "../utils"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; export default class CandyBar extends Phaser.GameObjects.Container { private bg: Phaser.GameObjects.NineSlice; @@ -16,29 +17,29 @@ export default class CandyBar extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene) { - super(scene, (scene.game.canvas.width / 6), -((scene.game.canvas.height) / 6) + 15); + constructor() { + super(globalScene, (globalScene.game.canvas.width / 6), -((globalScene.game.canvas.height) / 6) + 15); } setup(): void { - this.bg = this.scene.add.nineslice(0, 0, "party_exp_bar", undefined, 8, 18, 21, 5, 6, 4); + this.bg = globalScene.add.nineslice(0, 0, "party_exp_bar", undefined, 8, 18, 21, 5, 6, 4); this.bg.setOrigin(0, 0); this.add(this.bg); - this.candyIcon = this.scene.add.sprite(14, 0, "items", "candy"); + this.candyIcon = globalScene.add.sprite(14, 0, "items", "candy"); this.candyIcon.setOrigin(0.5, 0); this.candyIcon.setScale(0.5); this.add(this.candyIcon); - this.candyOverlayIcon = this.scene.add.sprite(14, 0, "items", "candy_overlay"); + this.candyOverlayIcon = globalScene.add.sprite(14, 0, "items", "candy_overlay"); this.candyOverlayIcon.setOrigin(0.5, 0); this.candyOverlayIcon.setScale(0.5); this.add(this.candyOverlayIcon); - this.countText = addTextObject(this.scene, 22, 4, "", TextStyle.BATTLE_INFO); + this.countText = addTextObject(22, 4, "", TextStyle.BATTLE_INFO); this.countText.setOrigin(0, 0); this.add(this.countText); @@ -61,21 +62,21 @@ export default class CandyBar extends Phaser.GameObjects.Container { this.candyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0]))); this.candyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); - this.countText.setText(`${(this.scene as BattleScene).gameData.starterData[starterSpeciesId].candyCount + count} (+${count.toString()})`); + this.countText.setText(`${globalScene.gameData.starterData[starterSpeciesId].candyCount + count} (+${count.toString()})`); this.bg.width = this.countText.displayWidth + 28; - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); if (this.tween) { this.tween.stop(); } - (this.scene as BattleScene).playSound("se/shing"); + globalScene.playSound("se/shing"); - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6) - (this.bg.width - 5), + x: (globalScene.game.canvas.width / 6) - (this.bg.width - 5), duration: 500, ease: "Sine.easeOut", onComplete: () => { @@ -104,9 +105,9 @@ export default class CandyBar extends Phaser.GameObjects.Container { this.tween.stop(); } - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6), + x: (globalScene.game.canvas.width / 6), duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index e2547a626de..9c13d54bf55 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -1,17 +1,17 @@ -import BattleScene from "../battle-scene"; import { TextStyle, addTextObject } from "./text"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { addWindow } from "./ui-theme"; import { Button } from "#enums/buttons"; import i18next from "i18next"; -import { Challenge } from "#app/data/challenge"; +import type { Challenge } from "#app/data/challenge"; import * as Utils from "../utils"; import { Challenges } from "#app/enums/challenges"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Color, ShadowColor } from "#app/enums/color"; import { SelectStarterPhase } from "#app/phases/select-starter-phase"; import { TitlePhase } from "#app/phases/title-phase"; +import { globalScene } from "#app/global-scene"; /** * Handles all the UI for choosing optional challenges. @@ -45,8 +45,8 @@ export default class GameChallengesUiHandler extends UiHandler { private readonly leftArrowGap: number = 90; // distance from the label to the left arrow private readonly arrowSpacing: number = 3; // distance between the arrows and the value area - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } setup() { @@ -54,49 +54,37 @@ export default class GameChallengesUiHandler extends UiHandler { this.widestTextBox = 0; - this.challengesContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.challengesContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); this.challengesContainer.setName("challenges"); - this.challengesContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.challengesContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - const bgOverlay = this.scene.add.rectangle(-1, -1, this.scene.scaledCanvas.width, this.scene.scaledCanvas.height, 0x424242, 0.8); + const bgOverlay = globalScene.add.rectangle(-1, -1, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0x424242, 0.8); bgOverlay.setName("rect-challenge-overlay"); bgOverlay.setOrigin(0, 0); this.challengesContainer.add(bgOverlay); // TODO: Change this back to /9 when adding in difficulty - const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6), 24); + const headerBg = addWindow(0, 0, (globalScene.game.canvas.width / 6), 24); headerBg.setName("window-header-bg"); headerBg.setOrigin(0, 0); - const headerText = addTextObject(this.scene, 0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL); + const headerText = addTextObject(0, 0, i18next.t("challenges:title"), TextStyle.SETTINGS_LABEL); headerText.setName("text-header"); headerText.setOrigin(0, 0); headerText.setPositionRelative(headerBg, 8, 4); - // const difficultyBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 18) - 2, 24); - // difficultyBg.setOrigin(0, 0); - // difficultyBg.setPositionRelative(headerBg, headerBg.width, 0); - - // this.difficultyText = addTextObject(this.scene, 0, 0, "0", TextStyle.SETTINGS_LABEL); - // this.difficultyText.setOrigin(0, 0); - // this.difficultyText.setPositionRelative(difficultyBg, 8, 4); - - // const difficultyName = addTextObject(this.scene, 0, 0, i18next.t("challenges:points"), TextStyle.SETTINGS_LABEL); - // difficultyName.setOrigin(0, 0); - // difficultyName.setPositionRelative(difficultyBg, difficultyBg.width - difficultyName.displayWidth - 8, 4); - - this.optionsWidth = this.scene.scaledCanvas.width * 0.6; - this.optionsBg = addWindow(this.scene, 0, headerBg.height, this.optionsWidth, this.scene.scaledCanvas.height - headerBg.height - 2); + this.optionsWidth = globalScene.scaledCanvas.width * 0.6; + this.optionsBg = addWindow(0, headerBg.height, this.optionsWidth, globalScene.scaledCanvas.height - headerBg.height - 2); this.optionsBg.setName("window-options-bg"); this.optionsBg.setOrigin(0, 0); - const descriptionBg = addWindow(this.scene, 0, headerBg.height, this.scene.scaledCanvas.width - this.optionsWidth, this.scene.scaledCanvas.height - headerBg.height - 26); + const descriptionBg = addWindow(0, headerBg.height, globalScene.scaledCanvas.width - this.optionsWidth, globalScene.scaledCanvas.height - headerBg.height - 26); descriptionBg.setName("window-desc-bg"); descriptionBg.setOrigin(0, 0); descriptionBg.setPositionRelative(this.optionsBg, this.optionsBg.width, 0); - this.descriptionText = new BBCodeText(this.scene, descriptionBg.x + 6, descriptionBg.y + 4, "", { + this.descriptionText = new BBCodeText(globalScene, descriptionBg.x + 6, descriptionBg.y + 4, "", { fontFamily: "emerald", fontSize: 84, color: Color.ORANGE, @@ -109,54 +97,54 @@ export default class GameChallengesUiHandler extends UiHandler { } }); this.descriptionText.setName("text-desc"); - this.scene.add.existing(this.descriptionText); + globalScene.add.existing(this.descriptionText); this.descriptionText.setScale(1 / 6); this.descriptionText.setShadow(4, 5, ShadowColor.ORANGE); this.descriptionText.setOrigin(0, 0); - this.startBg = addWindow(this.scene, 0, 0, descriptionBg.width, 24); + this.startBg = addWindow(0, 0, descriptionBg.width, 24); this.startBg.setName("window-start-bg"); this.startBg.setOrigin(0, 0); this.startBg.setPositionRelative(descriptionBg, 0, descriptionBg.height); - this.startText = addTextObject(this.scene, 0, 0, i18next.t("challenges:noneSelected"), TextStyle.SETTINGS_LABEL); + this.startText = addTextObject(0, 0, i18next.t("challenges:noneSelected"), TextStyle.SETTINGS_LABEL); this.startText.setName("text-start"); this.startText.setOrigin(0, 0); this.startText.setPositionRelative(this.startBg, (this.startBg.width - this.startText.displayWidth) / 2, 4); - this.startCursor = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, descriptionBg.width - 8, 16, 1, 1, 1, 1); + this.startCursor = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, descriptionBg.width - 8, 16, 1, 1, 1, 1); this.startCursor.setName("9s-start-cursor"); this.startCursor.setOrigin(0, 0); this.startCursor.setPositionRelative(this.startBg, 4, 3); this.startCursor.setVisible(false); - this.valuesContainer = this.scene.add.container(0, 0); + this.valuesContainer = globalScene.add.container(0, 0); this.valuesContainer.setName("values"); this.challengeLabels = []; for (let i = 0; i < 9; i++) { - const label = addTextObject(this.scene, 8, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); + const label = addTextObject(8, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); label.setName(`text-challenge-label-${i}`); label.setOrigin(0, 0); this.valuesContainer.add(label); - const leftArrow = this.scene.add.image(0, 0, "cursor_reverse"); + const leftArrow = globalScene.add.image(0, 0, "cursor_reverse"); leftArrow.setName(`challenge-left-arrow-${i}`); leftArrow.setOrigin(0, 0); leftArrow.setVisible(false); leftArrow.setScale(0.75); this.valuesContainer.add(leftArrow); - const rightArrow = this.scene.add.image(0, 0, "cursor"); + const rightArrow = globalScene.add.image(0, 0, "cursor"); rightArrow.setName(`challenge-right-arrow-${i}`); rightArrow.setOrigin(0, 0); rightArrow.setScale(0.75); rightArrow.setVisible(false); this.valuesContainer.add(rightArrow); - const value = addTextObject(this.scene, 0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); + const value = addTextObject(0, 28 + i * 16, "", TextStyle.SETTINGS_LABEL); value.setName(`challenge-value-text-${i}`); value.setPositionRelative(label, 100, 0); this.valuesContainer.add(value); @@ -169,7 +157,7 @@ export default class GameChallengesUiHandler extends UiHandler { }; } - this.monoTypeValue = this.scene.add.sprite(8, 98, Utils.getLocalizedSpriteKey("types")); + this.monoTypeValue = globalScene.add.sprite(8, 98, Utils.getLocalizedSpriteKey("types")); this.monoTypeValue.setName("challenge-value-monotype-sprite"); this.monoTypeValue.setScale(0.86); this.monoTypeValue.setVisible(false); @@ -209,20 +197,20 @@ export default class GameChallengesUiHandler extends UiHandler { * init all challenge labels */ initLabels(): void { - this.setDescription(this.scene.gameMode.challenges[0].getDescription()); + this.setDescription(globalScene.gameMode.challenges[0].getDescription()); this.widestTextBox = 0; for (let i = 0; i < 9; i++) { - if (i < this.scene.gameMode.challenges.length) { + if (i < globalScene.gameMode.challenges.length) { this.challengeLabels[i].label.setVisible(true); this.challengeLabels[i].value.setVisible(true); this.challengeLabels[i].leftArrow.setVisible(true); this.challengeLabels[i].rightArrow.setVisible(true); - const tempText = addTextObject(this.scene, 0, 0, "", TextStyle.SETTINGS_LABEL); // this is added here to get the widest text object for this language, which will be used for the arrow placement + const tempText = addTextObject(0, 0, "", TextStyle.SETTINGS_LABEL); // this is added here to get the widest text object for this language, which will be used for the arrow placement - for (let j = 0; j <= this.scene.gameMode.challenges[i].maxValue; j++) { // this goes through each challenge's value to find out what the max width will be - if (this.scene.gameMode.challenges[i].id !== Challenges.SINGLE_TYPE) { - tempText.setText(this.scene.gameMode.challenges[i].getValue(j)); + for (let j = 0; j <= globalScene.gameMode.challenges[i].maxValue; j++) { // this goes through each challenge's value to find out what the max width will be + if (globalScene.gameMode.challenges[i].id !== Challenges.SINGLE_TYPE) { + tempText.setText(globalScene.gameMode.challenges[i].getValue(j)); if (tempText.displayWidth > this.widestTextBox) { this.widestTextBox = tempText.displayWidth; } @@ -240,8 +228,8 @@ export default class GameChallengesUiHandler extends UiHandler { updateText(): void { this.setDescription(this.getActiveChallenge().getDescription()); let monoTypeVisible = false; - for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) { - const challenge = this.scene.gameMode.challenges[this.scrollCursor + i]; + for (let i = 0; i < Math.min(9, globalScene.gameMode.challenges.length); i++) { + const challenge = globalScene.gameMode.challenges[this.scrollCursor + i]; const challengeLabel = this.challengeLabels[i]; challengeLabel.label.setText(challenge.getName()); challengeLabel.leftArrow.setPositionRelative(challengeLabel.label, this.leftArrowGap, 4.5); @@ -276,7 +264,7 @@ export default class GameChallengesUiHandler extends UiHandler { } // This checks if a challenge has been selected by the user and updates the text/its opacity accordingly. - this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0); + this.hasSelectedChallenge = globalScene.gameMode.challenges.some(c => c.value !== 0); if (this.hasSelectedChallenge) { this.startText.setText(i18next.t("common:start")); @@ -289,11 +277,6 @@ export default class GameChallengesUiHandler extends UiHandler { this.startText.setPositionRelative(this.startBg, (this.startBg.width - this.startText.displayWidth) / 2, 4); } this.challengesContainer.update(); - - // const totalDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getDifficulty(), 0); - // const totalMinDifficulty = this.scene.gameMode.challenges.reduce((v, c) => v + c.getMinDifficulty(), 0); - // this.difficultyText.text = `${totalDifficulty}` + (totalMinDifficulty ? `/${totalMinDifficulty}` : ""); - // this.difficultyText.updateText(); } show(args: any[]): boolean { @@ -303,7 +286,7 @@ export default class GameChallengesUiHandler extends UiHandler { this.updateChallengeArrows(false); this.challengesContainer.setVisible(true); // Should always be false at the start - this.hasSelectedChallenge = this.scene.gameMode.challenges.some(c => c.value !== 0); + this.hasSelectedChallenge = globalScene.gameMode.challenges.some(c => c.value !== 0); this.setCursor(0); this.initLabels(); @@ -319,7 +302,7 @@ export default class GameChallengesUiHandler extends UiHandler { /* This code updates the challenge starter arrows to be tinted/not tinted when the start button is selected to show they can't be changed */ updateChallengeArrows(tinted: boolean) { - for (let i = 0; i < Math.min(9, this.scene.gameMode.challenges.length); i++) { + for (let i = 0; i < Math.min(9, globalScene.gameMode.challenges.length); i++) { const challengeLabel = this.challengeLabels[i]; if (tinted) { challengeLabel.leftArrow.setTint(0x808080); @@ -354,16 +337,16 @@ export default class GameChallengesUiHandler extends UiHandler { this.cursorObj?.setVisible(true); this.updateChallengeArrows(this.startCursor.visible); } else { - this.scene.clearPhaseQueue(); - this.scene.pushPhase(new TitlePhase(this.scene)); - this.scene.getCurrentPhase()?.end(); + globalScene.clearPhaseQueue(); + globalScene.pushPhase(new TitlePhase()); + globalScene.getCurrentPhase()?.end(); } success = true; } else if (button === Button.SUBMIT || button === Button.ACTION) { if (this.hasSelectedChallenge) { if (this.startCursor.visible) { - this.scene.unshiftPhase(new SelectStarterPhase(this.scene)); - this.scene.getCurrentPhase()?.end(); + globalScene.unshiftPhase(new SelectStarterPhase()); + globalScene.getCurrentPhase()?.end(); } else { this.startCursor.setVisible(true); this.cursorObj?.setVisible(false); @@ -380,14 +363,14 @@ export default class GameChallengesUiHandler extends UiHandler { if (this.cursor === 0) { if (this.scrollCursor === 0) { // When at the top of the menu and pressing UP, move to the bottommost item. - if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom + if (globalScene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom // First, set the cursor to the last visible element, preparing for the scroll to the end. const successA = this.setCursor(rowsToDisplay - 1); // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); + const successB = this.setScrollCursor(globalScene.gameMode.challenges.length - rowsToDisplay); success = successA && successB; // success is just there to play the little validation sound effect } else { // If there are 9 or less challenges, just move to the bottom one - success = this.setCursor(this.scene.gameMode.challenges.length - 1); + success = this.setCursor(globalScene.gameMode.challenges.length - 1); } } else { success = this.setScrollCursor(this.scrollCursor - 1); @@ -401,7 +384,7 @@ export default class GameChallengesUiHandler extends UiHandler { break; case Button.DOWN: if (this.cursor === rowsToDisplay - 1) { - if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { + if (this.scrollCursor < globalScene.gameMode.challenges.length - rowsToDisplay) { // When at the bottom and pressing DOWN, scroll if possible. success = this.setScrollCursor(this.scrollCursor + 1); } else { @@ -412,7 +395,7 @@ export default class GameChallengesUiHandler extends UiHandler { const successB = this.setScrollCursor(0); success = successA && successB; // success is just there to play the little validation sound effect } - } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { + } else if (globalScene.gameMode.challenges.length < rowsToDisplay && this.cursor === globalScene.gameMode.challenges.length - 1) { // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. success = this.setCursor(0); } else { @@ -451,7 +434,7 @@ export default class GameChallengesUiHandler extends UiHandler { let ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, this.optionsWidth - 8, 16, 1, 1, 1, 1); + this.cursorObj = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, this.optionsWidth - 8, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); this.valuesContainer.add(this.cursorObj); } @@ -477,7 +460,7 @@ export default class GameChallengesUiHandler extends UiHandler { } getActiveChallenge(): Challenge { - return this.scene.gameMode.challenges[this.cursor + this.scrollCursor]; + return globalScene.gameMode.challenges[this.cursor + this.scrollCursor]; } clear() { diff --git a/src/ui/char-sprite.ts b/src/ui/char-sprite.ts index d76c7ec59d0..ccd97e2c8e4 100644 --- a/src/ui/char-sprite.ts +++ b/src/ui/char-sprite.ts @@ -1,4 +1,4 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; export default class CharSprite extends Phaser.GameObjects.Container { @@ -9,13 +9,13 @@ export default class CharSprite extends Phaser.GameObjects.Container { public variant: string; public shown: boolean; - constructor(scene: BattleScene) { - super(scene, (scene.game.canvas.width / 6) + 32, -42); + constructor() { + super(globalScene, (globalScene.game.canvas.width / 6) + 32, -42); } setup(): void { [ this.sprite, this.transitionSprite ] = new Array(2).fill(null).map(() => { - const ret = this.scene.add.sprite(0, 0, "", ""); + const ret = globalScene.add.sprite(0, 0, "", ""); ret.setOrigin(0.5, 1); this.add(ret); return ret; @@ -45,11 +45,11 @@ export default class CharSprite extends Phaser.GameObjects.Container { this.sprite.setTexture(key, variant); - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6) - 102, + x: (globalScene.game.canvas.width / 6) - 102, duration: 750, ease: "Cubic.easeOut", onComplete: () => { @@ -57,7 +57,7 @@ export default class CharSprite extends Phaser.GameObjects.Container { } }); - this.setVisible(this.scene.textures.get(key).key !== Utils.MissingTextureKey); + this.setVisible(globalScene.textures.get(key).key !== Utils.MissingTextureKey); this.shown = true; this.key = key; @@ -67,12 +67,12 @@ export default class CharSprite extends Phaser.GameObjects.Container { setVariant(variant: string): Promise { return new Promise(resolve => { - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); this.transitionSprite.setTexture(this.key, variant); this.transitionSprite.setAlpha(0); this.transitionSprite.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.transitionSprite, alpha: 1, duration: 250, @@ -93,9 +93,9 @@ export default class CharSprite extends Phaser.GameObjects.Container { return resolve(); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6) + 32, + x: (globalScene.game.canvas.width / 6) + 32, duration: 750, ease: "Cubic.easeIn", onComplete: () => { diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 0dacacc7b70..32a3bb764a9 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "../battle-scene"; import { addTextObject, TextStyle } from "./text"; import PartyUiHandler, { PartyUiMode } from "./party-ui-handler"; import { Mode } from "./ui"; @@ -7,6 +6,7 @@ import i18next from "i18next"; import { Button } from "#enums/buttons"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommandPhase } from "#app/phases/command-phase"; +import { globalScene } from "#app/global-scene"; export enum Command { FIGHT = 0, @@ -22,8 +22,8 @@ export default class CommandUiHandler extends UiHandler { protected fieldIndex: integer = 0; protected cursor2: integer = 0; - constructor(scene: BattleScene) { - super(scene, Mode.COMMAND); + constructor() { + super(Mode.COMMAND); } setup() { @@ -35,13 +35,13 @@ export default class CommandUiHandler extends UiHandler { i18next.t("commandUiHandler:run") ]; - this.commandsContainer = this.scene.add.container(217, -38.7); + this.commandsContainer = globalScene.add.container(217, -38.7); this.commandsContainer.setName("commands"); this.commandsContainer.setVisible(false); ui.add(this.commandsContainer); for (let c = 0; c < commands.length; c++) { - const commandText = addTextObject(this.scene, c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW); + const commandText = addTextObject(c % 2 === 0 ? 0 : 55.8, c < 2 ? 0 : 16, commands[c], TextStyle.WINDOW); commandText.setName(commands[c]); this.commandsContainer.add(commandText); } @@ -55,11 +55,11 @@ export default class CommandUiHandler extends UiHandler { this.commandsContainer.setVisible(true); let commandPhase: CommandPhase; - const currentPhase = this.scene.getCurrentPhase(); + const currentPhase = globalScene.getCurrentPhase(); if (currentPhase instanceof CommandPhase) { commandPhase = currentPhase; } else { - commandPhase = this.scene.getStandbyPhase() as CommandPhase; + commandPhase = globalScene.getStandbyPhase() as CommandPhase; } const messageHandler = this.getUi().getMessageHandler(); @@ -90,7 +90,7 @@ export default class CommandUiHandler extends UiHandler { switch (cursor) { // Fight case Command.FIGHT: - ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + ui.setMode(Mode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex()); success = true; break; // Ball @@ -100,17 +100,17 @@ export default class CommandUiHandler extends UiHandler { break; // Pokemon case Command.POKEMON: - ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); + ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); success = true; break; // Run case Command.RUN: - (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); + (globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); success = true; break; } } else { - (this.scene.getCurrentPhase() as CommandPhase).cancel(); + (globalScene.getCurrentPhase() as CommandPhase).cancel(); } } else { switch (button) { @@ -159,7 +159,7 @@ export default class CommandUiHandler extends UiHandler { } if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.commandsContainer.add(this.cursorObj); } diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index 2022508fc0d..b53a350cce0 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -1,8 +1,9 @@ -import BattleScene from "../battle-scene"; -import AbstractOptionSelectUiHandler, { OptionSelectConfig } from "./abstact-option-select-ui-handler"; +import type { OptionSelectConfig } from "./abstact-option-select-ui-handler"; +import AbstractOptionSelectUiHandler from "./abstact-option-select-ui-handler"; import { Mode } from "./ui"; import i18next from "i18next"; import { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { @@ -12,8 +13,8 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { private switchCheck: boolean; private switchCheckCursor: integer; - constructor(scene: BattleScene) { - super(scene, Mode.CONFIRM); + constructor() { + super(Mode.CONFIRM); } getWindowWidth(): integer { @@ -54,7 +55,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { const xOffset = (args.length >= 6 && args[5] !== null ? args[5] as number : 0); const yOffset = (args.length >= 7 && args[6] !== null ? args[6] as number : 0); - this.optionSelectContainer.setPosition((this.scene.game.canvas.width / 6) - 1 + xOffset, -48 + yOffset); + this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 + xOffset, -48 + yOffset); this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); return true; @@ -87,7 +88,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { const xOffset = (args.length >= 4 && args[3] !== null ? args[3] as number : 0); const yOffset = (args.length >= 5 && args[4] !== null ? args[4] as number : 0); - this.optionSelectContainer.setPosition((this.scene.game.canvas.width / 6) - 1 + xOffset, -48 + yOffset); + this.optionSelectContainer.setPosition((globalScene.game.canvas.width / 6) - 1 + xOffset, -48 + yOffset); this.setCursor(this.switchCheck ? this.switchCheckCursor : 0); diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index bb93b1fb1f5..0c7ac0d60b3 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; import { TextStyle, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; @@ -34,8 +34,8 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { private _isUpdating: boolean; - constructor(scene: BattleScene, x: number, y: number) { - super(scene, x, y); + constructor(x: number, y: number) { + super(globalScene, x, y); this._isUpdating = false; this.setup(); @@ -60,24 +60,24 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { } setup() { - const titleWindow = addWindow(this.scene, 0, 0, 114, 18, false, false, undefined, undefined, WindowVariant.THIN); + const titleWindow = addWindow(0, 0, 114, 18, false, false, undefined, undefined, WindowVariant.THIN); this.add(titleWindow); - this.titleLabel = addTextObject(this.scene, titleWindow.displayWidth / 2, titleWindow.displayHeight / 2, i18next.t("menu:loading"), TextStyle.WINDOW, { fontSize: "64px" }); + this.titleLabel = addTextObject(titleWindow.displayWidth / 2, titleWindow.displayHeight / 2, i18next.t("menu:loading"), TextStyle.WINDOW, { fontSize: "64px" }); this.titleLabel.setOrigin(0.5, 0.5); this.add(this.titleLabel); - const window = addWindow(this.scene, 0, 17, 114, 118, false, false, undefined, undefined, WindowVariant.THIN); + const window = addWindow(0, 17, 114, 118, false, false, undefined, undefined, WindowVariant.THIN); this.add(window); - this.rankingsContainer = this.scene.add.container(6, 21); + this.rankingsContainer = globalScene.add.container(6, 21); this.add(this.rankingsContainer); - this.loadingLabel = addTextObject(this.scene, window.displayWidth / 2, window.displayHeight / 2 + 16, "", TextStyle.WINDOW); + this.loadingLabel = addTextObject(window.displayWidth / 2, window.displayHeight / 2 + 16, "", TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); this.loadingLabel.setVisible(false); - this.prevCategoryButton = this.scene.add.sprite(4, 4, "cursor_reverse"); + this.prevCategoryButton = globalScene.add.sprite(4, 4, "cursor_reverse"); this.prevCategoryButton.setOrigin(0, 0); this.add(this.prevCategoryButton); @@ -86,7 +86,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.update(this.category ? this.category - 1 : Utils.getEnumKeys(ScoreboardCategory).length - 1); }); - this.nextCategoryButton = this.scene.add.sprite(window.displayWidth - 4, 4, "cursor"); + this.nextCategoryButton = globalScene.add.sprite(window.displayWidth - 4, 4, "cursor"); this.nextCategoryButton.setOrigin(1, 0); this.add(this.nextCategoryButton); @@ -95,7 +95,7 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { this.update(this.category < Utils.getEnumKeys(ScoreboardCategory).length - 1 ? this.category + 1 : 0); }); - this.prevPageButton = this.scene.add.sprite(window.displayWidth / 2 - 16, titleWindow.displayHeight + window.displayHeight - 15, "cursor_reverse"); + this.prevPageButton = globalScene.add.sprite(window.displayWidth / 2 - 16, titleWindow.displayHeight + window.displayHeight - 15, "cursor_reverse"); this.prevPageButton.setOrigin(0, 0); this.prevPageButton.setAlpha(0.5); this.add(this.prevPageButton); @@ -107,11 +107,11 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { } }); - this.pageNumberLabel = addTextObject(this.scene, window.displayWidth / 2, titleWindow.displayHeight + window.displayHeight - 16, "1", TextStyle.WINDOW, { fontSize: "64px" }); + this.pageNumberLabel = addTextObject(window.displayWidth / 2, titleWindow.displayHeight + window.displayHeight - 16, "1", TextStyle.WINDOW, { fontSize: "64px" }); this.pageNumberLabel.setOrigin(0.5, 0); this.add(this.pageNumberLabel); - this.nextPageButton = this.scene.add.sprite(window.displayWidth / 2 + 16, titleWindow.displayHeight + window.displayHeight - 15, "cursor"); + this.nextPageButton = globalScene.add.sprite(window.displayWidth / 2 + 16, titleWindow.displayHeight + window.displayHeight - 15, "cursor"); this.nextPageButton.setOrigin(1, 0); this.nextPageButton.setAlpha(0.5); this.add(this.nextPageButton); @@ -131,20 +131,20 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { updateRankings(rankings: RankingEntry[]) { const getEntry = (rank: string, username: string, score: string, wave: string) => { - const entryContainer = this.scene.add.container(0, 0); + const entryContainer = globalScene.add.container(0, 0); - const rankLabel = addTextObject(this.scene, 0, 0, rank, TextStyle.WINDOW, { fontSize: "54px" }); + const rankLabel = addTextObject(0, 0, rank, TextStyle.WINDOW, { fontSize: "54px" }); entryContainer.add(rankLabel); - const usernameLabel = addTextObject(this.scene, 12, 0, username, TextStyle.WINDOW, { fontSize: "54px" }); + const usernameLabel = addTextObject(12, 0, username, TextStyle.WINDOW, { fontSize: "54px" }); entryContainer.add(usernameLabel); - const scoreLabel = addTextObject(this.scene, 84, 0, score, TextStyle.WINDOW, { fontSize: "54px" }); + const scoreLabel = addTextObject(84, 0, score, TextStyle.WINDOW, { fontSize: "54px" }); entryContainer.add(scoreLabel); switch (this.category) { case ScoreboardCategory.DAILY: - const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); + const waveLabel = addTextObject(68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); entryContainer.add(waveLabel); break; case ScoreboardCategory.WEEKLY: @@ -236,7 +236,3 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { }); } } - -export interface DailyRunScoreboard { - scene: BattleScene -} diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index e16efe17036..ec124312e14 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -1,5 +1,4 @@ -import BattleScene from "#app/battle-scene"; -import { SceneBase } from "#app/scene-base"; +import { globalScene } from "#app/global-scene"; import { addTextObject, TextStyle } from "./text"; import { addWindow, WindowVariant } from "./ui-theme"; import i18next from "i18next"; @@ -57,8 +56,8 @@ export class DropDownOption extends Phaser.GameObjects.Container { private excludeColor = 0xff5555; private unlockableColor = 0xffff00; - constructor(scene: SceneBase, val: any, labels: DropDownLabel | DropDownLabel[]) { - super(scene); + constructor(val: any, labels: DropDownLabel | DropDownLabel[]) { + super(globalScene); this.val = val; if (Array.isArray(labels)) { @@ -70,7 +69,7 @@ export class DropDownOption extends Phaser.GameObjects.Container { const currentLabel = this.labels[this.currentLabelIndex]; this.state = currentLabel.state; - this.text = addTextObject(scene, 0, 0, currentLabel.text || "", TextStyle.TOOLTIP_CONTENT); + this.text = addTextObject(0, 0, currentLabel.text || "", TextStyle.TOOLTIP_CONTENT); this.text.setOrigin(0, 0.5); this.add(this.text); @@ -96,12 +95,12 @@ export class DropDownOption extends Phaser.GameObjects.Container { */ setupToggleIcon(type: DropDownType, visible: boolean): void { if (type === DropDownType.SINGLE) { - this.toggle = this.scene.add.sprite(0, 0, "cursor"); + this.toggle = globalScene.add.sprite(0, 0, "cursor"); this.toggle.setScale(0.5); this.toggle.setOrigin(0, 0.5); this.toggle.setRotation(Math.PI / 180 * -90); } else { - this.toggle = this.scene.add.sprite(0, 0, "candy"); + this.toggle = globalScene.add.sprite(0, 0, "candy"); this.toggle.setScale(0.3); this.toggle.setOrigin(0, 0.5); } @@ -283,7 +282,7 @@ export class DropDown extends Phaser.GameObjects.Container { private lastDir: SortDirection = SortDirection.ASC; private defaultSettings: any[]; - constructor(scene: BattleScene, x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) { + constructor(x: number, y: number, options: DropDownOption[], onChange: () => void, type: DropDownType = DropDownType.MULTI, optionSpacing: number = 2) { const windowPadding = 5; const optionHeight = 7; const optionPaddingX = 4; @@ -291,19 +290,19 @@ export class DropDown extends Phaser.GameObjects.Container { const cursorOffset = 7; const optionWidth = 100; - super(scene, x - cursorOffset - windowPadding, y); + super(globalScene, x - cursorOffset - windowPadding, y); this.options = options; this.dropDownType = type; this.onChange = onChange; - this.cursorObj = scene.add.image(optionPaddingX + 3, 0, "cursor"); + this.cursorObj = globalScene.add.image(optionPaddingX + 3, 0, "cursor"); this.cursorObj.setScale(0.5); this.cursorObj.setOrigin(0, 0.5); this.cursorObj.setVisible(false); // For MULTI and HYBRID filter, add an ALL option at the top if (this.dropDownType === DropDownType.MULTI || this.dropDownType === DropDownType.HYBRID) { - this.options.unshift(new DropDownOption(scene, "ALL", new DropDownLabel(i18next.t("filterBar:all"), undefined, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF))); + this.options.unshift(new DropDownOption("ALL", new DropDownLabel(i18next.t("filterBar:all"), undefined, this.checkForAllOn() ? DropDownState.ON : DropDownState.OFF))); } this.defaultSettings = this.getSettings(); @@ -326,7 +325,7 @@ export class DropDown extends Phaser.GameObjects.Container { } }); - this.window = addWindow(scene, 0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN); + this.window = addWindow(0, 0, optionWidth, options[options.length - 1].y + optionHeight + optionPaddingY, false, false, undefined, undefined, WindowVariant.XTHIN); this.add(this.window); this.add(options); this.add(this.cursorObj); diff --git a/src/ui/egg-counter-container.ts b/src/ui/egg-counter-container.ts index 21cebf5d97e..7bec7c97480 100644 --- a/src/ui/egg-counter-container.ts +++ b/src/ui/egg-counter-container.ts @@ -1,12 +1,13 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { addWindow } from "./ui-theme"; import { addTextObject, TextStyle } from "./text"; -import { EggCountChangedEvent, EggEventType } from "#app/events/egg"; -import EggHatchSceneHandler from "./egg-hatch-scene-handler"; +import type { EggCountChangedEvent } from "#app/events/egg"; +import { EggEventType } from "#app/events/egg"; +import type EggHatchSceneHandler from "./egg-hatch-scene-handler"; /** * A container that displays the count of hatching eggs. - * Extends Phaser.GameObjects.Container. + * @extends Phaser.GameObjects.Container */ export default class EggCounterContainer extends Phaser.GameObjects.Container { private readonly WINDOW_DEFAULT_WIDTH = 37; @@ -14,21 +15,18 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container { private readonly WINDOW_HEIGHT = 26; private readonly onEggCountChangedEvent = (event: Event) => this.onEggCountChanged(event); - private battleScene: BattleScene; - private eggCount: integer; + private eggCount: number; private eggCountWindow: Phaser.GameObjects.NineSlice; public eggCountText: Phaser.GameObjects.Text; /** - * @param {BattleScene} scene - The scene to which this container belongs. - * @param {number} eggCount - The number of eggs to hatch. + * @param eggCount - The number of eggs to hatch. */ - constructor(scene: BattleScene, eggCount: integer) { - super(scene, 0, 0); + constructor(eggCount: number) { + super(globalScene, 0, 0); this.eggCount = eggCount; - this.battleScene = scene; - const uiHandler = this.battleScene.ui.getHandler() as EggHatchSceneHandler; + const uiHandler = globalScene.ui.getHandler() as EggHatchSceneHandler; uiHandler.eventTarget.addEventListener(EggEventType.EGG_COUNT_CHANGED, this.onEggCountChangedEvent); this.setup(); @@ -40,15 +38,15 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container { private setup(): void { const windowWidth = this.eggCount > 9 ? this.WINDOW_MEDIUM_WIDTH : this.WINDOW_DEFAULT_WIDTH; - this.eggCountWindow = addWindow(this.battleScene, 5, 5, windowWidth, this.WINDOW_HEIGHT); + this.eggCountWindow = addWindow(5, 5, windowWidth, this.WINDOW_HEIGHT); this.setVisible(this.eggCount > 1); this.add(this.eggCountWindow); - const eggSprite = this.battleScene.add.sprite(19, 18, "egg", "egg_0"); + const eggSprite = globalScene.add.sprite(19, 18, "egg", "egg_0"); eggSprite.setScale(0.32); - this.eggCountText = addTextObject(this.battleScene, 28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }); + this.eggCountText = addTextObject(28, 13, `${this.eggCount}`, TextStyle.MESSAGE, { fontSize: "66px" }); this.eggCountText.setName("text-egg-count"); this.add(eggSprite); @@ -66,7 +64,6 @@ export default class EggCounterContainer extends Phaser.GameObjects.Container { * Handles window size, the egg count to show, and whether it should be displayed. * * @param event {@linkcode Event} being sent - * @returns void */ private onEggCountChanged(event: Event): void { const eggCountChangedEvent = event as EggCountChangedEvent; diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index b14f5381a84..82e361fac39 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -1,9 +1,9 @@ -import BattleScene from "../battle-scene"; import { Mode } from "./ui"; import { TextStyle, addTextObject, getEggTierTextTint, getTextStyleOptions } from "./text"; import MessageUiHandler from "./message-ui-handler"; import * as Utils from "../utils"; -import { Egg, getLegendaryGachaSpeciesForTimestamp, IEggOptions } from "../data/egg"; +import type { IEggOptions } from "../data/egg"; +import { Egg, getLegendaryGachaSpeciesForTimestamp } from "../data/egg"; import { VoucherType, getVoucherTypeIcon } from "../system/voucher"; import { getPokemonSpecies } from "../data/pokemon-species"; import { addWindow } from "./ui-theme"; @@ -13,6 +13,7 @@ import Overrides from "#app/overrides"; import { GachaType } from "#app/enums/gacha-types"; import i18next from "i18next"; import { EggTier } from "#enums/egg-type"; +import { globalScene } from "#app/global-scene"; export default class EggGachaUiHandler extends MessageUiHandler { private eggGachaContainer: Phaser.GameObjects.Container; @@ -39,8 +40,8 @@ export default class EggGachaUiHandler extends MessageUiHandler { private scale: number = 0.1666666667; - constructor(scene: BattleScene) { - super(scene, Mode.EGG_GACHA); + constructor() { + super(Mode.EGG_GACHA); this.gachaContainers = []; this.gachaKnobs = []; @@ -53,29 +54,29 @@ export default class EggGachaUiHandler extends MessageUiHandler { setup() { this.gachaCursor = 0; - this.scale = getTextStyleOptions(TextStyle.WINDOW, this.scene.uiTheme).scale; + this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; const ui = this.getUi(); - this.eggGachaContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.eggGachaContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); this.eggGachaContainer.setVisible(false); ui.add(this.eggGachaContainer); - const bg = this.scene.add.nineslice(0, 0, "default_bg", undefined, 320, 180, 0, 0, 16, 0); + const bg = globalScene.add.nineslice(0, 0, "default_bg", undefined, 320, 180, 0, 0, 16, 0); bg.setOrigin(0, 0); this.eggGachaContainer.add(bg); - const hatchFrameNames = this.scene.anims.generateFrameNames("gacha_hatch", { suffix: ".png", start: 1, end: 4 }); - if (!(this.scene.anims.exists("open"))) { - this.scene.anims.create({ + const hatchFrameNames = globalScene.anims.generateFrameNames("gacha_hatch", { suffix: ".png", start: 1, end: 4 }); + if (!(globalScene.anims.exists("open"))) { + globalScene.anims.create({ key: "open", frames: hatchFrameNames, frameRate: 12 }); } - if (!(this.scene.anims.exists("close"))) { - this.scene.anims.create({ + if (!(globalScene.anims.exists("close"))) { + globalScene.anims.create({ key: "close", frames: hatchFrameNames.reverse(), frameRate: 12 @@ -84,21 +85,21 @@ export default class EggGachaUiHandler extends MessageUiHandler { Utils.getEnumValues(GachaType).forEach((gachaType, g) => { const gachaTypeKey = GachaType[gachaType].toString().toLowerCase(); - const gachaContainer = this.scene.add.container(180 * g, 18); + const gachaContainer = globalScene.add.container(180 * g, 18); - const gacha = this.scene.add.sprite(0, 0, `gacha_${gachaTypeKey}`); + const gacha = globalScene.add.sprite(0, 0, `gacha_${gachaTypeKey}`); gacha.setOrigin(0, 0); - const gachaUnderlay = this.scene.add.sprite(115, 80, `gacha_underlay_${gachaTypeKey}`); + const gachaUnderlay = globalScene.add.sprite(115, 80, `gacha_underlay_${gachaTypeKey}`); gachaUnderlay.setOrigin(0, 0); - const gachaEggs = this.scene.add.sprite(0, 0, "gacha_eggs"); + const gachaEggs = globalScene.add.sprite(0, 0, "gacha_eggs"); gachaEggs.setOrigin(0, 0); - const gachaGlass = this.scene.add.sprite(0, 0, "gacha_glass"); + const gachaGlass = globalScene.add.sprite(0, 0, "gacha_glass"); gachaGlass.setOrigin(0, 0); - const gachaInfoContainer = this.scene.add.container(160, 46); + const gachaInfoContainer = globalScene.add.container(160, 46); const currentLanguage = i18next.resolvedLanguage ?? "en"; let gachaTextStyle = TextStyle.WINDOW_ALT; @@ -122,7 +123,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { legendaryLabelY = 0; } - const gachaUpLabel = addTextObject(this.scene, gachaX, gachaY, i18next.t("egg:legendaryUPGacha"), gachaTextStyle); + const gachaUpLabel = addTextObject(gachaX, gachaY, i18next.t("egg:legendaryUPGacha"), gachaTextStyle); gachaUpLabel.setOrigin(0, 0); gachaInfoContainer.add(gachaUpLabel); @@ -139,7 +140,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { } gachaUpLabel.setY(legendaryLabelY); - const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); + const pokemonIcon = globalScene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); if ([ "pt-BR" ].includes(currentLanguage)) { pokemonIcon.setX(pokemonIconX - 2); } @@ -170,9 +171,9 @@ export default class EggGachaUiHandler extends MessageUiHandler { break; } - const gachaKnob = this.scene.add.sprite(191, 89, "gacha_knob"); + const gachaKnob = globalScene.add.sprite(191, 89, "gacha_knob"); - const gachaHatch = this.scene.add.sprite(115, 73, "gacha_hatch"); + const gachaHatch = globalScene.add.sprite(115, 73, "gacha_hatch"); gachaHatch.setOrigin(0, 0); gachaContainer.add(gachaEggs); @@ -198,13 +199,13 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.updateGachaInfo(g); }); - this.eggGachaOptionsContainer = this.scene.add.container(); + this.eggGachaOptionsContainer = globalScene.add.container(); - this.eggGachaOptionsContainer = this.scene.add.container((this.scene.game.canvas.width / 6), 148); + this.eggGachaOptionsContainer = globalScene.add.container((globalScene.game.canvas.width / 6), 148); this.eggGachaContainer.add(this.eggGachaOptionsContainer); - this.eggGachaOptionSelectBg = addWindow(this.scene, 0, 0, 96, 16 + 576 * this.scale); + this.eggGachaOptionSelectBg = addWindow(0, 0, 96, 16 + 576 * this.scale); this.eggGachaOptionSelectBg.setOrigin(1, 1); this.eggGachaOptionsContainer.add(this.eggGachaOptionSelectBg); @@ -231,7 +232,6 @@ export default class EggGachaUiHandler extends MessageUiHandler { }).join("\n"); const optionText = addTextObject( - this.scene, 0, 0, `${pullOptionsText}\n${i18next.t("menu:cancel")}`, @@ -246,7 +246,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { optionText.setPositionRelative(this.eggGachaOptionSelectBg, 16, 9); pullOptions.forEach((option, i) => { - const icon = this.scene.add.sprite(0, 0, "items", option.icon); + const icon = globalScene.add.sprite(0, 0, "items", option.icon); icon.setScale(3 * this.scale); icon.setPositionRelative(this.eggGachaOptionSelectBg, 20, 9 + (48 + i * 96) * this.scale); this.eggGachaOptionsContainer.add(icon); @@ -255,13 +255,13 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(this.eggGachaOptionsContainer); new Array(Utils.getEnumKeys(VoucherType).length).fill(null).map((_, i) => { - const container = this.scene.add.container((this.scene.game.canvas.width / 6) - 56 * i, 0); + const container = globalScene.add.container((globalScene.game.canvas.width / 6) - 56 * i, 0); - const bg = addWindow(this.scene, 0, 0, 56, 22); + const bg = addWindow(0, 0, 56, 22); bg.setOrigin(1, 0); container.add(bg); - const countLabel = addTextObject(this.scene, -48, 3, "0", TextStyle.WINDOW); + const countLabel = addTextObject(-48, 3, "0", TextStyle.WINDOW); countLabel.setOrigin(0, 0); container.add(countLabel); @@ -269,7 +269,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const iconImage = getVoucherTypeIcon(i as VoucherType); - const icon = this.scene.add.sprite(-19, 2, "items", iconImage); + const icon = globalScene.add.sprite(-19, 2, "items", iconImage); icon.setOrigin(0, 0); icon.setScale(0.5); container.add(icon); @@ -277,25 +277,25 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.add(container); }); - this.eggGachaOverlay = this.scene.add.rectangle(0, 0, bg.displayWidth, bg.displayHeight, 0x000000); + this.eggGachaOverlay = globalScene.add.rectangle(0, 0, bg.displayWidth, bg.displayHeight, 0x000000); this.eggGachaOverlay.setOrigin(0, 0); this.eggGachaOverlay.setAlpha(0); this.eggGachaContainer.add(this.eggGachaOverlay); - this.eggGachaSummaryContainer = this.scene.add.container(0, 0); + this.eggGachaSummaryContainer = globalScene.add.container(0, 0); this.eggGachaSummaryContainer.setVisible(false); this.eggGachaContainer.add(this.eggGachaSummaryContainer); - const gachaMessageBoxContainer = this.scene.add.container(0, 148); + const gachaMessageBoxContainer = globalScene.add.container(0, 148); - const gachaMessageBox = addWindow(this.scene, 0, 0, 320, 32); + const gachaMessageBox = addWindow(0, 0, 320, 32); gachaMessageBox.setOrigin(0, 0); gachaMessageBoxContainer.add(gachaMessageBox); this.eggGachaMessageBox = gachaMessageBox; - const gachaMessageText = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + const gachaMessageText = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); gachaMessageText.setOrigin(0, 0); gachaMessageBoxContainer.add(gachaMessageText); @@ -326,7 +326,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaContainer.setVisible(true); - handleTutorial(this.scene, Tutorial.Egg_Gacha); + handleTutorial(Tutorial.Egg_Gacha); return true; } @@ -351,41 +351,41 @@ export default class EggGachaUiHandler extends MessageUiHandler { return this.showSummary(eggs!); } - const egg = this.scene.add.sprite(127, 75, "egg", `egg_${eggs![count].getKey()}`); + const egg = globalScene.add.sprite(127, 75, "egg", `egg_${eggs![count].getKey()}`); egg.setScale(0.5); this.gachaContainers[this.gachaCursor].add(egg); this.gachaContainers[this.gachaCursor].moveTo(egg, 2); const doPullAnim = () => { - this.scene.playSound("se/gacha_running", { loop: true }); - this.scene.time.delayedCall(this.getDelayValue(count ? 500 : 1250), () => { - this.scene.playSound("se/gacha_dispense"); - this.scene.time.delayedCall(this.getDelayValue(750), () => { - this.scene.sound.stopByKey("se/gacha_running"); - this.scene.tweens.add({ + globalScene.playSound("se/gacha_running", { loop: true }); + globalScene.time.delayedCall(this.getDelayValue(count ? 500 : 1250), () => { + globalScene.playSound("se/gacha_dispense"); + globalScene.time.delayedCall(this.getDelayValue(750), () => { + globalScene.sound.stopByKey("se/gacha_running"); + globalScene.tweens.add({ targets: egg, duration: this.getDelayValue(350), y: 95, ease: "Bounce.easeOut", onComplete: () => { - this.scene.time.delayedCall(this.getDelayValue(125), () => { - this.scene.playSound("se/pb_catch"); + globalScene.time.delayedCall(this.getDelayValue(125), () => { + globalScene.playSound("se/pb_catch"); this.gachaHatches[this.gachaCursor].play("open"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: egg, duration: this.getDelayValue(350), scale: 0.75, ease: "Sine.easeIn" }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: egg, y: 110, duration: this.getDelayValue(350), ease: "Back.easeOut", onComplete: () => { this.gachaHatches[this.gachaCursor].play("close"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: egg, y: 200, duration: this.getDelayValue(350), @@ -408,20 +408,20 @@ export default class EggGachaUiHandler extends MessageUiHandler { }; if (!count) { - this.scene.playSound("se/gacha_dial"); - this.scene.tweens.add({ + globalScene.playSound("se/gacha_dial"); + globalScene.tweens.add({ targets: this.gachaKnobs[this.gachaCursor], duration: this.getDelayValue(350), angle: 90, ease: "Cubic.easeInOut", onComplete: () => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.gachaKnobs[this.gachaCursor], duration: this.getDelayValue(350), angle: 0, ease: "Sine.easeInOut" }); - this.scene.time.delayedCall(this.getDelayValue(350), doPullAnim); + globalScene.time.delayedCall(this.getDelayValue(350), doPullAnim); } }); } else { @@ -438,7 +438,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { if (!eggs) { eggs = []; for (let i = 1; i <= pullCount; i++) { - const eggOptions: IEggOptions = { scene: this.scene, pulled: true, sourceType: this.gachaCursor }; + const eggOptions: IEggOptions = { pulled: true, sourceType: this.gachaCursor }; // Before creating the last egg, check if the guaranteed egg tier was already generated // if not, override the egg tier @@ -456,9 +456,9 @@ export default class EggGachaUiHandler extends MessageUiHandler { eggs = Utils.randSeedShuffle(eggs); - (this.scene.currentBattle ? this.scene.gameData.saveAll(this.scene, true, true, true) : this.scene.gameData.saveSystem()).then(success => { + (globalScene.currentBattle ? globalScene.gameData.saveAll(true, true, true) : globalScene.gameData.saveSystem()).then(success => { if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } doPull(); }); @@ -490,7 +490,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const eggScale = eggs.length < 20 ? 1 : 0.5; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.eggGachaOverlay, alpha: 0.5, ease: "Sine.easeOut", @@ -508,13 +508,13 @@ export default class EggGachaUiHandler extends MessageUiHandler { const sliceWidth = this.eggGachaOverlay.displayWidth / (cols + 2); const sliceHeight = height / (rows + 2); const yOffset = (sliceHeight / 2 * (row / Math.max(rows - 1, 1))) + sliceHeight / 4; - const ret = this.scene.add.container(sliceWidth * (col + 1) + (sliceWidth * 0.5), sliceHeight * (row + 1) + yOffset); + const ret = globalScene.add.container(sliceWidth * (col + 1) + (sliceWidth * 0.5), sliceHeight * (row + 1) + yOffset); ret.setScale(0.0001); - const eggSprite = this.scene.add.sprite(0, 0, "egg", `egg_${egg.getKey()}`); + const eggSprite = globalScene.add.sprite(0, 0, "egg", `egg_${egg.getKey()}`); ret.add(eggSprite); - const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); + const eggText = addTextObject(0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); eggText.setOrigin(0.5, 0); eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC)); ret.add(eggText); @@ -527,8 +527,8 @@ export default class EggGachaUiHandler extends MessageUiHandler { // Otherwise show the eggs one by one with a small delay between each eggContainers.forEach((eggContainer, index) => { const delay = !this.transitionCancelled ? this.getDelayValue(index * 100) : 0; - this.scene.time.delayedCall(delay, () => - this.scene.tweens.add({ + globalScene.time.delayedCall(delay, () => + globalScene.tweens.add({ targets: eggContainer, duration: this.getDelayValue(350), scale: eggScale, @@ -548,7 +548,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { hideSummary() { this.setTransitioning(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.eggGachaOverlay, this.eggGachaSummaryContainer ], alpha: 0, duration: this.getDelayValue(250), @@ -568,7 +568,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const infoContainer = this.gachaInfoContainers[gachaType]; switch (gachaType as GachaType) { case GachaType.LEGENDARY: - const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); + const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime())); const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); break; @@ -576,13 +576,13 @@ export default class EggGachaUiHandler extends MessageUiHandler { } consumeVouchers(voucherType: VoucherType, count: integer): void { - this.scene.gameData.voucherCounts[voucherType] = Math.max(this.scene.gameData.voucherCounts[voucherType] - count, 0); + globalScene.gameData.voucherCounts[voucherType] = Math.max(globalScene.gameData.voucherCounts[voucherType] - count, 0); this.updateVoucherCounts(); } updateVoucherCounts(): void { this.voucherCountLabels.forEach((label, type) => { - label.setText(this.scene.gameData.voucherCounts[type].toString()); + label.setText(globalScene.gameData.voucherCounts[type].toString()); }); } @@ -641,10 +641,10 @@ export default class EggGachaUiHandler extends MessageUiHandler { case Button.ACTION: switch (this.cursor) { case 0: - if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + if (!globalScene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + } else if (globalScene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.REGULAR, 1); } @@ -656,10 +656,10 @@ export default class EggGachaUiHandler extends MessageUiHandler { } break; case 2: - if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + if (!globalScene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + } else if (globalScene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.PLUS, 1); } @@ -672,11 +672,11 @@ export default class EggGachaUiHandler extends MessageUiHandler { break; case 1: case 3: - if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) - || (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { + if ((this.cursor === 1 && globalScene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) + || (this.cursor === 3 && !globalScene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + } else if (globalScene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (this.cursor === 3) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.PREMIUM, 1); @@ -694,10 +694,10 @@ export default class EggGachaUiHandler extends MessageUiHandler { } break; case 4: - if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + if (!globalScene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + } else if (globalScene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.GOLDEN, 1); } @@ -755,7 +755,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.eggGachaOptionsContainer.add(this.cursorObj); } @@ -775,7 +775,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.setTransitioning(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.gachaContainers, duration: this.eggGachaContainer.visible ? 500 : 0, x: (_target, _key, _value, index) => 180 * (index - cursor), diff --git a/src/ui/egg-hatch-scene-handler.ts b/src/ui/egg-hatch-scene-handler.ts index 1bf58a786e1..189d2f295d1 100644 --- a/src/ui/egg-hatch-scene-handler.ts +++ b/src/ui/egg-hatch-scene-handler.ts @@ -1,8 +1,8 @@ -import BattleScene from "../battle-scene"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { Button } from "#enums/buttons"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; +import { globalScene } from "#app/global-scene"; export default class EggHatchSceneHandler extends UiHandler { public eggHatchContainer: Phaser.GameObjects.Container; @@ -15,17 +15,17 @@ export default class EggHatchSceneHandler extends UiHandler { */ public readonly eventTarget: EventTarget = new EventTarget(); - constructor(scene: BattleScene) { - super(scene, Mode.EGG_HATCH_SCENE); + constructor() { + super(Mode.EGG_HATCH_SCENE); } setup() { - this.eggHatchContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); - this.scene.fieldUI.add(this.eggHatchContainer); + this.eggHatchContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); + globalScene.fieldUI.add(this.eggHatchContainer); - const eggLightraysAnimFrames = this.scene.anims.generateFrameNames("egg_lightrays", { start: 0, end: 3 }); - if (!(this.scene.anims.exists("egg_lightrays"))) { - this.scene.anims.create({ + const eggLightraysAnimFrames = globalScene.anims.generateFrameNames("egg_lightrays", { start: 0, end: 3 }); + if (!(globalScene.anims.exists("egg_lightrays"))) { + globalScene.anims.create({ key: "egg_lightrays", frames: eggLightraysAnimFrames, frameRate: 32 @@ -38,20 +38,20 @@ export default class EggHatchSceneHandler extends UiHandler { this.getUi().showText("", 0); - this.scene.setModifiersVisible(false); + globalScene.setModifiersVisible(false); return true; } processInput(button: Button): boolean { if (button === Button.ACTION || button === Button.CANCEL) { - const phase = this.scene.getCurrentPhase(); + const phase = globalScene.getCurrentPhase(); if (phase instanceof EggHatchPhase && phase.trySkip()) { return true; } } - return this.scene.ui.getMessageHandler().processInput(button); + return globalScene.ui.getMessageHandler().processInput(button); } setCursor(_cursor: integer): boolean { diff --git a/src/ui/egg-list-ui-handler.ts b/src/ui/egg-list-ui-handler.ts index 939f95fabe6..1b25d55d96d 100644 --- a/src/ui/egg-list-ui-handler.ts +++ b/src/ui/egg-list-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { TextStyle, addTextObject } from "#app/ui/text"; @@ -8,6 +7,7 @@ import { Button } from "#enums/buttons"; import i18next from "i18next"; import ScrollableGridUiHandler from "#app/ui/scrollable-grid-handler"; import { ScrollBar } from "#app/ui/scroll-bar"; +import { globalScene } from "#app/global-scene"; export default class EggListUiHandler extends MessageUiHandler { private readonly ROWS = 9; @@ -28,59 +28,59 @@ export default class EggListUiHandler extends MessageUiHandler { private iconAnimHandler: PokemonIconAnimHandler; - constructor(scene: BattleScene) { - super(scene, Mode.EGG_LIST); + constructor() { + super(Mode.EGG_LIST); } setup() { const ui = this.getUi(); - this.eggListContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.eggListContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); this.eggListContainer.setVisible(false); ui.add(this.eggListContainer); - const bgColor = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0x006860); + const bgColor = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860); bgColor.setOrigin(0, 0); this.eggListContainer.add(bgColor); - const eggListBg = this.scene.add.image(0, 0, "egg_list_bg"); + const eggListBg = globalScene.add.image(0, 0, "egg_list_bg"); eggListBg.setOrigin(0, 0); this.eggListContainer.add(eggListBg); - this.eggListContainer.add(addWindow(this.scene, 1, 85, 106, 22)); - this.eggListContainer.add(addWindow(this.scene, 1, 102, 106, 50, true)); - this.eggListContainer.add(addWindow(this.scene, 1, 147, 106, 32, true)); - this.eggListContainer.add(addWindow(this.scene, 107, 1, 212, 178)); + this.eggListContainer.add(addWindow(1, 85, 106, 22)); + this.eggListContainer.add(addWindow(1, 102, 106, 50, true)); + this.eggListContainer.add(addWindow(1, 147, 106, 32, true)); + this.eggListContainer.add(addWindow(107, 1, 212, 178)); this.iconAnimHandler = new PokemonIconAnimHandler(); - this.iconAnimHandler.setup(this.scene); + this.iconAnimHandler.setup(); - this.eggNameText = addTextObject(this.scene, 8, 68, "", TextStyle.SUMMARY); + this.eggNameText = addTextObject(8, 68, "", TextStyle.SUMMARY); this.eggNameText.setOrigin(0, 0); this.eggListContainer.add(this.eggNameText); - this.eggDateText = addTextObject(this.scene, 8, 91, "", TextStyle.TOOLTIP_CONTENT); + this.eggDateText = addTextObject(8, 91, "", TextStyle.TOOLTIP_CONTENT); this.eggListContainer.add(this.eggDateText); - this.eggHatchWavesText = addTextObject(this.scene, 8, 108, "", TextStyle.TOOLTIP_CONTENT); + this.eggHatchWavesText = addTextObject(8, 108, "", TextStyle.TOOLTIP_CONTENT); this.eggHatchWavesText.setWordWrapWidth(540); this.eggListContainer.add(this.eggHatchWavesText); - this.eggGachaInfoText = addTextObject(this.scene, 8, 152, "", TextStyle.TOOLTIP_CONTENT); + this.eggGachaInfoText = addTextObject(8, 152, "", TextStyle.TOOLTIP_CONTENT); this.eggGachaInfoText.setWordWrapWidth(540); this.eggListContainer.add(this.eggGachaInfoText); - this.eggListIconContainer = this.scene.add.container(113, 5); + this.eggListIconContainer = globalScene.add.container(113, 5); this.eggListContainer.add(this.eggListIconContainer); - this.cursorObj = this.scene.add.image(0, 0, "select_cursor"); + this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); this.cursorObj.setOrigin(0, 0); this.eggListContainer.add(this.cursorObj); - this.eggSprite = this.scene.add.sprite(54, 37, "egg"); + this.eggSprite = globalScene.add.sprite(54, 37, "egg"); this.eggListContainer.add(this.eggSprite); - const scrollBar = new ScrollBar(this.scene, 310, 5, 4, 170, this.ROWS); + const scrollBar = new ScrollBar(310, 5, 4, 170, this.ROWS); this.eggListContainer.add(scrollBar); this.scrollGridHandler = new ScrollableGridUiHandler(this, this.ROWS, this.COLUMNS) @@ -88,15 +88,15 @@ export default class EggListUiHandler extends MessageUiHandler { .withUpdateGridCallBack(() => this.updateEggIcons()) .withUpdateSingleElementCallback((i:number) => this.setEggDetails(i)); - this.eggListMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6); + this.eggListMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); this.eggListMessageBoxContainer.setVisible(false); this.eggListContainer.add(this.eggListMessageBoxContainer); - const eggListMessageBox = addWindow(this.scene, 1, -1, 318, 28); + const eggListMessageBox = addWindow(1, -1, 318, 28); eggListMessageBox.setOrigin(0, 1); this.eggListMessageBoxContainer.add(eggListMessageBox); - this.message = addTextObject(this.scene, 8, -8, "", TextStyle.WINDOW, { maxLines: 1 }); + this.message = addTextObject(8, -8, "", TextStyle.WINDOW, { maxLines: 1 }); this.message.setOrigin(0, 1); this.eggListMessageBoxContainer.add(this.message); @@ -112,7 +112,7 @@ export default class EggListUiHandler extends MessageUiHandler { this.eggListContainer.setVisible(true); - this.scrollGridHandler.setTotalElements(this.scene.gameData.eggs.length); + this.scrollGridHandler.setTotalElements(globalScene.gameData.eggs.length); this.updateEggIcons(); this.setCursor(0); @@ -125,10 +125,10 @@ export default class EggListUiHandler extends MessageUiHandler { */ private initEggIcons() { this.eggIcons = []; - for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, this.scene.gameData.eggs.length); i++) { + for (let i = 0; i < Math.min(this.ROWS * this.COLUMNS, globalScene.gameData.eggs.length); i++) { const x = (i % this.COLUMNS) * 18; const y = Math.floor(i / this.COLUMNS) * 18; - const icon = this.scene.add.sprite(x - 2, y + 2, "egg_icons"); + const icon = globalScene.add.sprite(x - 2, y + 2, "egg_icons"); icon.setScale(0.5); icon.setOrigin(0, 0); this.eggListIconContainer.add(icon); @@ -141,14 +141,14 @@ export default class EggListUiHandler extends MessageUiHandler { */ private updateEggIcons() { const indexOffset = this.scrollGridHandler.getItemOffset(); - const eggsToShow = Math.min(this.eggIcons.length, this.scene.gameData.eggs.length - indexOffset); + const eggsToShow = Math.min(this.eggIcons.length, globalScene.gameData.eggs.length - indexOffset); this.eggIcons.forEach((icon, i) => { if (i !== this.cursor) { this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.NONE); } if (i < eggsToShow) { - const egg = this.scene.gameData.eggs[i + indexOffset]; + const egg = globalScene.gameData.eggs[i + indexOffset]; icon.setFrame(egg.getKey()); icon.setVisible(true); } else { @@ -162,7 +162,7 @@ export default class EggListUiHandler extends MessageUiHandler { * @param index which egg in the list to display the info for */ private setEggDetails(index: number): void { - const egg = this.scene.gameData.eggs[index]; + const egg = globalScene.gameData.eggs[index]; this.eggSprite.setFrame(`egg_${egg.getKey()}`); this.eggNameText.setText(`${i18next.t("egg:egg")} (${egg.getEggDescriptor()})`); this.eggDateText.setText( @@ -174,7 +174,7 @@ export default class EggListUiHandler extends MessageUiHandler { }) ); this.eggHatchWavesText.setText(egg.getEggHatchWavesMessage()); - this.eggGachaInfoText.setText(egg.getEggTypeDescriptor(this.scene)); + this.eggGachaInfoText.setText(egg.getEggTypeDescriptor()); } processInput(button: Button): boolean { diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index da93168926e..04b31ab9ca8 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "../battle-scene"; import { Mode } from "./ui"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; import MessageUiHandler from "./message-ui-handler"; @@ -6,10 +5,11 @@ import { getEggTierForSpecies } from "../data/egg"; import { Button } from "#enums/buttons"; import PokemonHatchInfoContainer from "./pokemon-hatch-info-container"; import { EggSummaryPhase } from "#app/phases/egg-summary-phase"; -import { EggHatchData } from "#app/data/egg-hatch-data"; +import type { EggHatchData } from "#app/data/egg-hatch-data"; import ScrollableGridUiHandler from "./scrollable-grid-handler"; import { HatchedPokemonContainer } from "./hatched-pokemon-container"; import { ScrollBar } from "#app/ui/scroll-bar"; +import { globalScene } from "#app/global-scene"; const iconContainerX = 112; const iconContainerY = 9; @@ -53,43 +53,43 @@ export default class EggSummaryUiHandler extends MessageUiHandler { */ public readonly eventTarget: EventTarget = new EventTarget(); - constructor(scene: BattleScene) { - super(scene, Mode.EGG_HATCH_SUMMARY); + constructor() { + super(Mode.EGG_HATCH_SUMMARY); } setup() { const ui = this.getUi(); - this.summaryContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.summaryContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); this.summaryContainer.setVisible(false); ui.add(this.summaryContainer); - this.eggHatchContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.eggHatchContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); this.eggHatchContainer.setVisible(false); ui.add(this.eggHatchContainer); this.iconAnimHandler = new PokemonIconAnimHandler(); - this.iconAnimHandler.setup(this.scene); + this.iconAnimHandler.setup(); - this.eggHatchBg = this.scene.add.image(0, 0, "egg_summary_bg"); + this.eggHatchBg = globalScene.add.image(0, 0, "egg_summary_bg"); this.eggHatchBg.setOrigin(0, 0); this.eggHatchContainer.add(this.eggHatchBg); - this.cursorObj = this.scene.add.image(0, 0, "select_cursor"); + this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); this.cursorObj.setOrigin(0, 0); this.summaryContainer.add(this.cursorObj); this.pokemonContainers = []; - this.pokemonIconsContainer = this.scene.add.container(iconContainerX, iconContainerY); + this.pokemonIconsContainer = globalScene.add.container(iconContainerX, iconContainerY); this.summaryContainer.add(this.pokemonIconsContainer); - this.infoContainer = new PokemonHatchInfoContainer(this.scene, this.summaryContainer); + this.infoContainer = new PokemonHatchInfoContainer(this.summaryContainer); this.infoContainer.setup(); this.infoContainer.changeToEggSummaryLayout(); this.infoContainer.setVisible(true); this.summaryContainer.add(this.infoContainer); - const scrollBar = new ScrollBar(this.scene, iconContainerX + numCols * iconSize, iconContainerY + 3, 4, this.scene.game.canvas.height / 6 - 20, numRows); + const scrollBar = new ScrollBar(iconContainerX + numCols * iconSize, iconContainerY + 3, 4, globalScene.game.canvas.height / 6 - 20, numRows); this.summaryContainer.add(scrollBar); this.scrollGridHandler = new ScrollableGridUiHandler(this, numRows, numCols) @@ -112,19 +112,19 @@ export default class EggSummaryUiHandler extends MessageUiHandler { this.getUi().hideTooltip(); // Note: Questions on garbage collection go to @frutescens - const activeKeys = this.scene.getActiveKeys(); + const activeKeys = globalScene.getActiveKeys(); // Removing unnecessary sprites from animation manager - const animKeys = Object.keys(this.scene.anims["anims"]["entries"]); + const animKeys = Object.keys(globalScene.anims["anims"]["entries"]); animKeys.forEach(key => { if (key.startsWith("pkmn__") && !activeKeys.includes(key)) { - this.scene.anims.remove(key); + globalScene.anims.remove(key); } }); // Removing unnecessary cries from audio cache - const audioKeys = Object.keys(this.scene.cache.audio.entries.entries); + const audioKeys = Object.keys(globalScene.cache.audio.entries.entries); audioKeys.forEach(key => { if (key.startsWith("cry/") && !activeKeys.includes(key)) { - delete this.scene.cache.audio.entries.entries[key]; + delete globalScene.cache.audio.entries.entries[key]; } }); // Clears eggHatchData in EggSummaryUiHandler @@ -170,13 +170,13 @@ export default class EggSummaryUiHandler extends MessageUiHandler { this.updatePokemonIcons(); this.setCursor(0); - this.scene.playSoundWithoutBgm("evolution_fanfare"); + globalScene.playSoundWithoutBgm("evolution_fanfare"); // Prevent exiting the egg summary for 2 seconds if the egg hatching // was skipped automatically and for 1 second otherwise - const exitBlockingDuration = (this.scene.eggSkipPreference === 2) ? 2000 : 1000; + const exitBlockingDuration = (globalScene.eggSkipPreference === 2) ? 2000 : 1000; this.blockExit = true; - this.scene.time.delayedCall(exitBlockingDuration, () => this.blockExit = false); + globalScene.time.delayedCall(exitBlockingDuration, () => this.blockExit = false); return true; } @@ -196,7 +196,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler { if (!hatchContainer) { const x = (i % numCols) * iconSize; const y = Math.floor(i / numCols) * iconSize; - hatchContainer = new HatchedPokemonContainer(this.scene, x, y, hatchData).setVisible(false); + hatchContainer = new HatchedPokemonContainer(x, y, hatchData).setVisible(false); this.pokemonContainers.push(hatchContainer); this.pokemonIconsContainer.add(hatchContainer); } @@ -216,7 +216,7 @@ export default class EggSummaryUiHandler extends MessageUiHandler { let error = false; if (button === Button.CANCEL) { if (!this.blockExit) { - const phase = this.scene.getCurrentPhase(); + const phase = globalScene.getCurrentPhase(); if (phase instanceof EggSummaryPhase) { phase.end(); } diff --git a/src/ui/evolution-scene-handler.ts b/src/ui/evolution-scene-handler.ts index a116a33f373..e7866dfea53 100644 --- a/src/ui/evolution-scene-handler.ts +++ b/src/ui/evolution-scene-handler.ts @@ -1,8 +1,8 @@ -import BattleScene from "../battle-scene"; import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; export default class EvolutionSceneHandler extends MessageUiHandler { public evolutionContainer: Phaser.GameObjects.Container; @@ -11,8 +11,8 @@ export default class EvolutionSceneHandler extends MessageUiHandler { public canCancel: boolean; public cancelled: boolean; - constructor(scene: BattleScene) { - super(scene, Mode.EVOLUTION_SCENE); + constructor() { + super(Mode.EVOLUTION_SCENE); } setup() { @@ -21,21 +21,21 @@ export default class EvolutionSceneHandler extends MessageUiHandler { const ui = this.getUi(); - this.evolutionContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.evolutionContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); ui.add(this.evolutionContainer); - const messageBg = this.scene.add.sprite(0, 0, "bg", this.scene.windowType); + const messageBg = globalScene.add.sprite(0, 0, "bg", globalScene.windowType); messageBg.setOrigin(0, 1); messageBg.setVisible(false); ui.add(messageBg); this.messageBg = messageBg; - this.messageContainer = this.scene.add.container(12, -39); + this.messageContainer = globalScene.add.container(12, -39); this.messageContainer.setVisible(false); ui.add(this.messageContainer); - const message = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE, { + const message = addTextObject(0, 0, "", TextStyle.MESSAGE, { maxLines: 2, wordWrap: { width: 1780 @@ -51,9 +51,9 @@ export default class EvolutionSceneHandler extends MessageUiHandler { show(_args: any[]): boolean { super.show(_args); - this.scene.ui.bringToTop(this.evolutionContainer); - this.scene.ui.bringToTop(this.messageBg); - this.scene.ui.bringToTop(this.messageContainer); + globalScene.ui.bringToTop(this.evolutionContainer); + globalScene.ui.bringToTop(this.messageBg); + globalScene.ui.bringToTop(this.messageContainer); this.messageBg.setVisible(true); this.messageContainer.setVisible(true); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index eaf504495d5..220e5d817ef 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -1,4 +1,5 @@ -import BattleScene, { InfoToggle } from "../battle-scene"; +import type { InfoToggle } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { addTextObject, TextStyle } from "./text"; import { getTypeDamageMultiplierColor } from "#app/data/type"; import { Type } from "#enums/type"; @@ -9,8 +10,9 @@ import * as Utils from "../utils"; import { MoveCategory } from "#app/data/move"; import i18next from "i18next"; import { Button } from "#enums/buttons"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import type { CommandPhase } from "#app/phases/command-phase"; import MoveInfoOverlay from "./move-info-overlay"; import { BattleType } from "#app/battle"; @@ -33,79 +35,79 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { protected fieldIndex: integer = 0; protected cursor2: integer = 0; - constructor(scene: BattleScene) { - super(scene, Mode.FIGHT); + constructor() { + super(Mode.FIGHT); } setup() { const ui = this.getUi(); - this.movesContainer = this.scene.add.container(18, -38.7); + this.movesContainer = globalScene.add.container(18, -38.7); this.movesContainer.setName(FightUiHandler.MOVES_CONTAINER_NAME); ui.add(this.movesContainer); - this.moveInfoContainer = this.scene.add.container(1, 0); + this.moveInfoContainer = globalScene.add.container(1, 0); this.moveInfoContainer.setName("move-info"); ui.add(this.moveInfoContainer); - this.typeIcon = this.scene.add.sprite(this.scene.scaledCanvas.width - 57, -36, Utils.getLocalizedSpriteKey("types"), "unknown"); + this.typeIcon = globalScene.add.sprite(globalScene.scaledCanvas.width - 57, -36, Utils.getLocalizedSpriteKey("types"), "unknown"); this.typeIcon.setVisible(false); this.moveInfoContainer.add(this.typeIcon); - this.moveCategoryIcon = this.scene.add.sprite(this.scene.scaledCanvas.width - 25, -36, "categories", "physical"); + this.moveCategoryIcon = globalScene.add.sprite(globalScene.scaledCanvas.width - 25, -36, "categories", "physical"); this.moveCategoryIcon.setVisible(false); this.moveInfoContainer.add(this.moveCategoryIcon); - this.ppLabel = addTextObject(this.scene, this.scene.scaledCanvas.width - 70, -26, "PP", TextStyle.MOVE_INFO_CONTENT); + this.ppLabel = addTextObject(globalScene.scaledCanvas.width - 70, -26, "PP", TextStyle.MOVE_INFO_CONTENT); this.ppLabel.setOrigin(0.0, 0.5); this.ppLabel.setVisible(false); this.ppLabel.setText(i18next.t("fightUiHandler:pp")); this.moveInfoContainer.add(this.ppLabel); - this.ppText = addTextObject(this.scene, this.scene.scaledCanvas.width - 12, -26, "--/--", TextStyle.MOVE_INFO_CONTENT); + this.ppText = addTextObject(globalScene.scaledCanvas.width - 12, -26, "--/--", TextStyle.MOVE_INFO_CONTENT); this.ppText.setOrigin(1, 0.5); this.ppText.setVisible(false); this.moveInfoContainer.add(this.ppText); - this.powerLabel = addTextObject(this.scene, this.scene.scaledCanvas.width - 70, -18, "POWER", TextStyle.MOVE_INFO_CONTENT); + this.powerLabel = addTextObject(globalScene.scaledCanvas.width - 70, -18, "POWER", TextStyle.MOVE_INFO_CONTENT); this.powerLabel.setOrigin(0.0, 0.5); this.powerLabel.setVisible(false); this.powerLabel.setText(i18next.t("fightUiHandler:power")); this.moveInfoContainer.add(this.powerLabel); - this.powerText = addTextObject(this.scene, this.scene.scaledCanvas.width - 12, -18, "---", TextStyle.MOVE_INFO_CONTENT); + this.powerText = addTextObject(globalScene.scaledCanvas.width - 12, -18, "---", TextStyle.MOVE_INFO_CONTENT); this.powerText.setOrigin(1, 0.5); this.powerText.setVisible(false); this.moveInfoContainer.add(this.powerText); - this.accuracyLabel = addTextObject(this.scene, this.scene.scaledCanvas.width - 70, -10, "ACC", TextStyle.MOVE_INFO_CONTENT); + this.accuracyLabel = addTextObject(globalScene.scaledCanvas.width - 70, -10, "ACC", TextStyle.MOVE_INFO_CONTENT); this.accuracyLabel.setOrigin(0.0, 0.5); this.accuracyLabel.setVisible(false); this.accuracyLabel.setText(i18next.t("fightUiHandler:accuracy")); this.moveInfoContainer.add(this.accuracyLabel); - this.accuracyText = addTextObject(this.scene, this.scene.scaledCanvas.width - 12, -10, "---", TextStyle.MOVE_INFO_CONTENT); + this.accuracyText = addTextObject(globalScene.scaledCanvas.width - 12, -10, "---", TextStyle.MOVE_INFO_CONTENT); this.accuracyText.setOrigin(1, 0.5); this.accuracyText.setVisible(false); this.moveInfoContainer.add(this.accuracyText); // prepare move overlay const overlayScale = 1; - this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + this.moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: true, scale: overlayScale, onSide: true, right: true, x: 0, y: -MoveInfoOverlay.getHeight(overlayScale, true), - width: (this.scene.game.canvas.width / 6) + 4, + width: (globalScene.game.canvas.width / 6) + 4, hideEffectBox: true, hideBg: true }); ui.add(this.moveInfoOverlay); // register the overlay to receive toggle events - this.scene.addInfoToggle(this.moveInfoOverlay); - this.scene.addInfoToggle(this); + globalScene.addInfoToggle(this.moveInfoOverlay); + globalScene.addInfoToggle(this); } show(args: any[]): boolean { @@ -117,7 +119,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { messageHandler.bg.setVisible(false); messageHandler.commandWindow.setVisible(false); messageHandler.movesWindowContainer.setVisible(true); - const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); if (pokemon.battleSummonData.turnCount <= 1) { this.setCursor(0); } else { @@ -138,14 +140,14 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { if (button === Button.CANCEL || button === Button.ACTION) { if (button === Button.ACTION) { - if ((this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, cursor, false)) { + if ((globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, cursor, false)) { success = true; } else { ui.playError(); } } else { // Cannot back out of fight menu if skipToFightInput is enabled - const { battleType, mysteryEncounter } = this.scene.currentBattle; + const { battleType, mysteryEncounter } = globalScene.currentBattle; if (battleType !== BattleType.MYSTERY_ENCOUNTER || !mysteryEncounter?.skipToFightInput) { ui.setMode(Mode.COMMAND, this.fieldIndex); success = true; @@ -188,7 +190,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { this.movesContainer.setVisible(false); this.cursorObj?.setVisible(false); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.movesContainer, this.cursorObj ], duration: Utils.fixedInt(125), ease: "Sine.easeInOut", @@ -222,11 +224,11 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); ui.add(this.cursorObj); } - const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); const moveset = pokemon.getMoveset(); const hasMove = cursor < moveset.length; @@ -300,11 +302,11 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } displayMoves() { - const pokemon = (this.scene.getCurrentPhase() as CommandPhase).getPokemon(); + const pokemon = (globalScene.getCurrentPhase() as CommandPhase).getPokemon(); const moveset = pokemon.getMoveset(); for (let moveIndex = 0; moveIndex < 4; moveIndex++) { - const moveText = addTextObject(this.scene, moveIndex % 2 === 0 ? 0 : 100, moveIndex < 2 ? 0 : 16, "-", TextStyle.WINDOW); + const moveText = addTextObject(moveIndex % 2 === 0 ? 0 : 100, moveIndex < 2 ? 0 : 16, "-", TextStyle.WINDOW); moveText.setName("text-empty-move"); if (moveIndex < moveset.length) { @@ -324,7 +326,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { * @returns A color or undefined if the default color should be used */ private getMoveColor(pokemon: Pokemon, pokemonMove: PokemonMove): string | undefined { - if (!this.scene.typeHints) { + if (!globalScene.typeHints) { return undefined; } @@ -362,7 +364,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { clearMoves() { this.movesContainer.removeAll(true); - const opponents = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); + const opponents = (globalScene.getCurrentPhase() as CommandPhase).getPokemon().getOpponents(); opponents.forEach((opponent) => { opponent.updateEffectiveness(undefined); }); diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts index bcf7409fce0..a6f9f66efe2 100644 --- a/src/ui/filter-bar.ts +++ b/src/ui/filter-bar.ts @@ -1,9 +1,10 @@ -import BattleScene from "#app/battle-scene"; -import { DropDown, DropDownType } from "./dropdown"; -import { StarterContainer } from "./starter-container"; +import type { DropDown } from "./dropdown"; +import { DropDownType } from "./dropdown"; +import type { StarterContainer } from "./starter-container"; import { addTextObject, getTextColor, TextStyle } from "./text"; -import { UiTheme } from "#enums/ui-theme"; +import type { UiTheme } from "#enums/ui-theme"; import { addWindow, WindowVariant } from "./ui-theme"; +import { globalScene } from "#app/global-scene"; export enum DropDownColumn { GEN, @@ -25,22 +26,22 @@ export class FilterBar extends Phaser.GameObjects.Container { private lastCursor: number = -1; private uiTheme: UiTheme; - constructor(scene: BattleScene, x: number, y: number, width: number, height: number) { - super(scene, x, y); + constructor(x: number, y: number, width: number, height: number) { + super(globalScene, x, y); this.width = width; this.height = height; - this.window = addWindow(scene, 0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN); + this.window = addWindow(0, 0, width, height, false, false, undefined, undefined, WindowVariant.THIN); this.add(this.window); - this.cursorObj = this.scene.add.image(1, 1, "cursor"); + this.cursorObj = globalScene.add.image(1, 1, "cursor"); this.cursorObj.setScale(0.5); this.cursorObj.setVisible(false); this.cursorObj.setOrigin(0, 0); this.add(this.cursorObj); - this.uiTheme = scene.uiTheme; + this.uiTheme = globalScene.uiTheme; } /** @@ -58,7 +59,7 @@ export class FilterBar extends Phaser.GameObjects.Container { this.columns.push(column); - const filterTypesLabel = addTextObject(this.scene, 0, 3, title, TextStyle.TOOLTIP_CONTENT); + const filterTypesLabel = addTextObject(0, 3, title, TextStyle.TOOLTIP_CONTENT); this.labels.push(filterTypesLabel); this.add(filterTypesLabel); this.dropDowns.push(dropDown); diff --git a/src/ui/form-modal-ui-handler.ts b/src/ui/form-modal-ui-handler.ts index 65ee9f2db10..86a1cc2238b 100644 --- a/src/ui/form-modal-ui-handler.ts +++ b/src/ui/form-modal-ui-handler.ts @@ -1,11 +1,12 @@ -import BattleScene from "../battle-scene"; -import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; -import { Mode } from "./ui"; +import type { ModalConfig } from "./modal-ui-handler"; +import { ModalUiHandler } from "./modal-ui-handler"; +import type { Mode } from "./ui"; import { TextStyle, addTextInputObject, addTextObject } from "./text"; import { WindowVariant, addWindow } from "./ui-theme"; -import InputText from "phaser3-rex-plugins/plugins/inputtext"; +import type InputText from "phaser3-rex-plugins/plugins/inputtext"; import * as Utils from "../utils"; import { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; export interface FormModalConfig extends ModalConfig { errorMessage?: string; @@ -20,8 +21,8 @@ export abstract class FormModalUiHandler extends ModalUiHandler { protected tween: Phaser.Tweens.Tween; protected formLabels: Phaser.GameObjects.Text[]; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.editing = false; this.inputContainers = []; @@ -59,7 +60,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.updateFields(config, hasTitle); } - this.errorMessage = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(), "", TextStyle.TOOLTIP_CONTENT); + this.errorMessage = addTextObject(10, (hasTitle ? 31 : 5) + 20 * (config.length - 1) + 16 + this.getButtonTopMargin(), "", TextStyle.TOOLTIP_CONTENT); this.errorMessage.setColor(this.getTextColor(TextStyle.SUMMARY_PINK)); this.errorMessage.setShadowColor(this.getTextColor(TextStyle.SUMMARY_PINK, true)); this.errorMessage.setVisible(false); @@ -71,20 +72,20 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.inputs = []; this.formLabels = []; fieldsConfig.forEach((config, f) => { - const label = addTextObject(this.scene, 10, (hasTitle ? 31 : 5) + 20 * f, config.label, TextStyle.TOOLTIP_CONTENT); + const label = addTextObject(10, (hasTitle ? 31 : 5) + 20 * f, config.label, TextStyle.TOOLTIP_CONTENT); label.name = "formLabel" + f; this.formLabels.push(label); this.modalContainer.add(this.formLabels[this.formLabels.length - 1]); - const inputContainer = this.scene.add.container(70, (hasTitle ? 28 : 2) + 20 * f); + const inputContainer = globalScene.add.container(70, (hasTitle ? 28 : 2) + 20 * f); inputContainer.setVisible(false); - const inputBg = addWindow(this.scene, 0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN); + const inputBg = addWindow(0, 0, 80, 16, false, false, 0, 0, WindowVariant.XTHIN); const isPassword = config?.isPassword; const isReadOnly = config?.isReadOnly; - const input = addTextInputObject(this.scene, 4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20, readOnly: isReadOnly }); + const input = addTextInputObject(4, -2, 440, 116, TextStyle.TOOLTIP_CONTENT, { type: isPassword ? "password" : "text", maxLength: isPassword ? 64 : 20, readOnly: isReadOnly }); input.setOrigin(0, 0); inputContainer.add(inputBg); @@ -119,7 +120,7 @@ export abstract class FormModalUiHandler extends ModalUiHandler { this.modalContainer.y += 24; this.modalContainer.setAlpha(0); - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this.modalContainer, duration: Utils.fixedInt(1000), ease: "Sine.easeInOut", diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 671bed29036..2fa5b54ac76 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -1,15 +1,16 @@ import Phaser from "phaser"; -import BattleScene from "#app/battle-scene"; import { TextStyle, addTextObject } from "#app/ui/text"; -import { Mode } from "#app/ui/ui"; +import type { Mode } from "#app/ui/ui"; import UiHandler from "#app/ui/ui-handler"; import { addWindow } from "#app/ui/ui-theme"; import * as Utils from "#app/utils"; -import { DexAttr, GameData } from "#app/system/game-data"; +import type { GameData } from "#app/system/game-data"; +import { DexAttr } from "#app/system/game-data"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { Button } from "#enums/buttons"; import i18next from "i18next"; import { UiTheme } from "#enums/ui-theme"; +import { globalScene } from "#app/global-scene"; interface DisplayStat { label_key?: string; @@ -222,8 +223,8 @@ export default class GameStatsUiHandler extends UiHandler { private arrowUp: Phaser.GameObjects.Sprite; private arrowDown: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.statLabels = []; this.statValues = []; @@ -232,37 +233,37 @@ export default class GameStatsUiHandler extends UiHandler { setup() { const ui = this.getUi(); - this.gameStatsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.gameStatsContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); - this.gameStatsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.gameStatsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + const headerBg = addWindow(0, 0, (globalScene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); - const headerText = addTextObject(this.scene, 0, 0, i18next.t("gameStatsUiHandler:stats"), TextStyle.SETTINGS_LABEL); + const headerText = addTextObject(0, 0, i18next.t("gameStatsUiHandler:stats"), TextStyle.SETTINGS_LABEL); headerText.setOrigin(0, 0); headerText.setPositionRelative(headerBg, 8, 4); - const statsBgWidth = ((this.scene.game.canvas.width / 6) - 2) / 2; + const statsBgWidth = ((globalScene.game.canvas.width / 6) - 2) / 2; const [ statsBgLeft, statsBgRight ] = new Array(2).fill(null).map((_, i) => { const width = statsBgWidth + 2; - const height = Math.floor((this.scene.game.canvas.height / 6) - headerBg.height - 2); - const statsBg = addWindow(this.scene, (statsBgWidth - 2) * i, headerBg.height, width, height, false, false, i > 0 ? -3 : 0, 1); + const height = Math.floor((globalScene.game.canvas.height / 6) - headerBg.height - 2); + const statsBg = addWindow((statsBgWidth - 2) * i, headerBg.height, width, height, false, false, i > 0 ? -3 : 0, 1); statsBg.setOrigin(0, 0); return statsBg; }); - this.statsContainer = this.scene.add.container(0, 0); + this.statsContainer = globalScene.add.container(0, 0); new Array(18).fill(null).map((_, s) => { - const statLabel = addTextObject(this.scene, 8 + (s % 2 === 1 ? statsBgWidth : 0), 28 + Math.floor(s / 2) * 16, "", TextStyle.STATS_LABEL); + const statLabel = addTextObject(8 + (s % 2 === 1 ? statsBgWidth : 0), 28 + Math.floor(s / 2) * 16, "", TextStyle.STATS_LABEL); statLabel.setOrigin(0, 0); this.statsContainer.add(statLabel); this.statLabels.push(statLabel); - const statValue = addTextObject(this.scene, (statsBgWidth * ((s % 2) + 1)) - 8, statLabel.y, "", TextStyle.STATS_VALUE); + const statValue = addTextObject((statsBgWidth * ((s % 2) + 1)) - 8, statLabel.y, "", TextStyle.STATS_VALUE); statValue.setOrigin(1, 0); this.statsContainer.add(statValue); this.statValues.push(statValue); @@ -275,10 +276,10 @@ export default class GameStatsUiHandler extends UiHandler { this.gameStatsContainer.add(this.statsContainer); // arrows to show that we can scroll through the stats - const isLegacyTheme = this.scene.uiTheme === UiTheme.LEGACY; - this.arrowDown = this.scene.add.sprite(statsBgWidth, this.scene.game.canvas.height / 6 - (isLegacyTheme ? 9 : 5), "prompt"); + const isLegacyTheme = globalScene.uiTheme === UiTheme.LEGACY; + this.arrowDown = globalScene.add.sprite(statsBgWidth, globalScene.game.canvas.height / 6 - (isLegacyTheme ? 9 : 5), "prompt"); this.gameStatsContainer.add(this.arrowDown); - this.arrowUp = this.scene.add.sprite(statsBgWidth, headerBg.height + (isLegacyTheme ? 7 : 3), "prompt"); + this.arrowUp = globalScene.add.sprite(statsBgWidth, headerBg.height + (isLegacyTheme ? 7 : 3), "prompt"); this.arrowUp.flipY = true; this.gameStatsContainer.add(this.arrowUp); @@ -298,7 +299,7 @@ export default class GameStatsUiHandler extends UiHandler { this.arrowUp.play("prompt"); this.arrowDown.play("prompt"); - if (this.scene.uiTheme === UiTheme.LEGACY) { + if (globalScene.uiTheme === UiTheme.LEGACY) { this.arrowUp.setTint(0x484848); this.arrowDown.setTint(0x484848); } @@ -318,7 +319,7 @@ export default class GameStatsUiHandler extends UiHandler { const statKeys = Object.keys(displayStats).slice(this.cursor * 2, this.cursor * 2 + 18); statKeys.forEach((key, s) => { const stat = displayStats[key] as DisplayStat; - const value = stat.sourceFunc!(this.scene.gameData); // TODO: is this bang correct? + const value = stat.sourceFunc!(globalScene.gameData); // TODO: is this bang correct? this.statLabels[s].setText(!stat.hidden || isNaN(parseInt(value)) || parseInt(value) ? i18next.t(`gameStatsUiHandler:${stat.label_key}`) : "???"); this.statValues[s].setText(value); }); @@ -348,7 +349,7 @@ export default class GameStatsUiHandler extends UiHandler { if (button === Button.CANCEL) { success = true; - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); } else { switch (button) { case Button.UP: diff --git a/src/ui/hatched-pokemon-container.ts b/src/ui/hatched-pokemon-container.ts index 9fb1fd26b30..f006002617a 100644 --- a/src/ui/hatched-pokemon-container.ts +++ b/src/ui/hatched-pokemon-container.ts @@ -1,10 +1,11 @@ -import { EggHatchData } from "#app/data/egg-hatch-data"; +import type { EggHatchData } from "#app/data/egg-hatch-data"; import { Gender } from "#app/data/gender"; import { getVariantTint } from "#app/data/variant"; import { DexAttr } from "#app/system/game-data"; -import BattleScene from "#app/battle-scene"; -import PokemonSpecies from "#app/data/pokemon-species"; -import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; +import { globalScene } from "#app/global-scene"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import type PokemonIconAnimHandler from "./pokemon-icon-anim-handler"; +import { PokemonIconAnimMode } from "./pokemon-icon-anim-handler"; /** * A container for a Pokemon's sprite and icons to get displayed in the egg summary screen @@ -12,7 +13,6 @@ import PokemonIconAnimHandler, { PokemonIconAnimMode } from "./pokemon-icon-anim * shiny variant, hidden ability, new egg move, new catch */ export class HatchedPokemonContainer extends Phaser.GameObjects.Container { - public scene: BattleScene; public species: PokemonSpecies; public icon: Phaser.GameObjects.Sprite; public shinyIcon: Phaser.GameObjects.Image; @@ -21,13 +21,12 @@ export class HatchedPokemonContainer extends Phaser.GameObjects.Container { public eggMoveIcon: Phaser.GameObjects.Image; /** - * @param scene the current {@linkcode BattleScene} * @param x x position * @param y y position * @param hatchData the {@linkcode EggHatchData} to load the icons and sprites for */ - constructor(scene: BattleScene, x: number, y: number, hatchData: EggHatchData) { - super(scene, x, y); + constructor(x: number, y: number, hatchData: EggHatchData) { + super(globalScene, x, y); const displayPokemon = hatchData.pokemon; this.species = displayPokemon.species; @@ -41,7 +40,7 @@ export class HatchedPokemonContainer extends Phaser.GameObjects.Container { const isShiny = displayPokemon.shiny; // Pokemon sprite - const pokemonIcon = this.scene.add.sprite(-offset, offset, species.getIconAtlasKey(formIndex, isShiny, variant)); + const pokemonIcon = globalScene.add.sprite(-offset, offset, species.getIconAtlasKey(formIndex, isShiny, variant)); pokemonIcon.setScale(0.5); pokemonIcon.setOrigin(0, 0); pokemonIcon.setFrame(species.getIconId(female, formIndex, isShiny, variant)); @@ -50,27 +49,27 @@ export class HatchedPokemonContainer extends Phaser.GameObjects.Container { this.add(this.icon); // Shiny icon - this.shinyIcon = this.scene.add.image(rightSideX, offset, "shiny_star_small"); + this.shinyIcon = globalScene.add.image(rightSideX, offset, "shiny_star_small"); this.shinyIcon.setOrigin(0, 0); this.shinyIcon.setScale(0.5); this.add(this.shinyIcon); // Hidden ability icon - const haIcon = this.scene.add.image(rightSideX, offset * 4, "ha_capsule"); + const haIcon = globalScene.add.image(rightSideX, offset * 4, "ha_capsule"); haIcon.setOrigin(0, 0); haIcon.setScale(0.5); this.hiddenAbilityIcon = haIcon; this.add(this.hiddenAbilityIcon); // Pokeball icon - const pokeballIcon = this.scene.add.image(rightSideX, offset * 7, "icon_owned"); + const pokeballIcon = globalScene.add.image(rightSideX, offset * 7, "icon_owned"); pokeballIcon.setOrigin(0, 0); pokeballIcon.setScale(0.5); this.pokeballIcon = pokeballIcon; this.add(this.pokeballIcon); // Egg move icon - const eggMoveIcon = this.scene.add.image(0, offset, "icon_egg_move"); + const eggMoveIcon = globalScene.add.image(0, offset, "icon_egg_move"); eggMoveIcon.setOrigin(0, 0); eggMoveIcon.setScale(0.5); this.eggMoveIcon = eggMoveIcon; diff --git a/src/ui/loading-modal-ui-handler.ts b/src/ui/loading-modal-ui-handler.ts index d86f7afd3b6..7ee0949c71e 100644 --- a/src/ui/loading-modal-ui-handler.ts +++ b/src/ui/loading-modal-ui-handler.ts @@ -1,12 +1,11 @@ import i18next from "i18next"; -import BattleScene from "../battle-scene"; import { ModalUiHandler } from "./modal-ui-handler"; import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; export default class LoadingModalUiHandler extends ModalUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } getModalTitle(): string { @@ -32,7 +31,7 @@ export default class LoadingModalUiHandler extends ModalUiHandler { setup(): void { super.setup(); - const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, i18next.t("menu:loading"), TextStyle.WINDOW); + const label = addTextObject(this.getWidth() / 2, this.getHeight() / 2, i18next.t("menu:loading"), TextStyle.WINDOW); label.setOrigin(0.5, 0.5); this.modalContainer.add(label); diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 78755423604..55909b63e58 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -1,13 +1,14 @@ -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; -import { ModalConfig } from "./modal-ui-handler"; +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; import * as Utils from "../utils"; import { Mode } from "./ui"; import i18next from "i18next"; -import BattleScene from "#app/battle-scene"; import { addTextObject, TextStyle } from "./text"; import { addWindow } from "./ui-theme"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { globalScene } from "#app/global-scene"; import JSZip from "jszip"; interface BuildInteractableImageOpts { @@ -33,15 +34,15 @@ export default class LoginFormUiHandler extends FormModalUiHandler { private infoContainer: Phaser.GameObjects.Container; private externalPartyBg: Phaser.GameObjects.NineSlice; private externalPartyTitle: Phaser.GameObjects.Text; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } setup(): void { super.setup(); this.buildExternalPartyContainer(); - this.infoContainer = this.scene.add.container(0, 0); + this.infoContainer = globalScene.add.container(0, 0); this.usernameInfoImage = this.buildInteractableImage("settings_icon", "username-info-icon", { x: 20, @@ -61,11 +62,11 @@ export default class LoginFormUiHandler extends FormModalUiHandler { } private buildExternalPartyContainer() { - this.externalPartyContainer = this.scene.add.container(0, 0); - this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 12, this.scene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains); - this.externalPartyTitle = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL); + this.externalPartyContainer = globalScene.add.container(0, 0); + this.externalPartyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 12, globalScene.game.canvas.height / 12), Phaser.Geom.Rectangle.Contains); + this.externalPartyTitle = addTextObject(0, 4, "", TextStyle.SETTINGS_LABEL); this.externalPartyTitle.setOrigin(0.5, 0); - this.externalPartyBg = addWindow(this.scene, 0, 0, 0, 0); + this.externalPartyBg = addWindow(0, 0, 0, 0); this.externalPartyContainer.add(this.externalPartyBg); this.externalPartyContainer.add(this.externalPartyTitle); @@ -140,10 +141,10 @@ export default class LoginFormUiHandler extends FormModalUiHandler { // Prevent overlapping overrides on action modification this.submitAction = originalLoginAction; this.sanitizeInputs(); - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); const onFail = error => { - this.scene.ui.setMode(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); - this.scene.ui.playError(); + globalScene.ui.setMode(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); }; if (!this.inputs[0].text) { return onFail(i18next.t("menu:emptyUsername")); @@ -207,9 +208,9 @@ export default class LoginFormUiHandler extends FormModalUiHandler { }); const onFail = error => { - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); - this.scene.ui.setModeForceTransition(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); - this.scene.ui.playError(); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setModeForceTransition(Mode.LOGIN_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); }; this.usernameInfoImage.on("pointerdown", () => { @@ -222,17 +223,17 @@ export default class LoginFormUiHandler extends FormModalUiHandler { options.push({ label: dataKeys[i].replace(keyToFind, ""), handler: () => { - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); this.infoContainer.disableInteractive(); return true; } }); } - this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options, delay: 1000 }); - this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width, this.scene.game.canvas.height), Phaser.Geom.Rectangle.Contains); + this.infoContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height), Phaser.Geom.Rectangle.Contains); } else { if (dataKeys.length > 2) { return onFail(this.ERR_TOO_MANY_SAVES); @@ -271,7 +272,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { }); this.externalPartyContainer.setAlpha(0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.externalPartyContainer, duration: Utils.fixedInt(1000), ease: "Sine.easeInOut", @@ -280,7 +281,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { }); this.infoContainer.setAlpha(0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.infoContainer, duration: Utils.fixedInt(1000), ease: "Sine.easeInOut", @@ -296,7 +297,7 @@ export default class LoginFormUiHandler extends FormModalUiHandler { y = 0, origin = { x: 0, y: 0 } } = opts; - const img = this.scene.add.image(x, y, texture); + const img = globalScene.add.image(x, y, texture); img.setName(name); img.setOrigin(origin.x, origin.y); img.setScale(scale); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 3ce3f3b7cf0..bb950f328e8 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -1,17 +1,18 @@ -import BattleScene, { bypassLogin } from "../battle-scene"; +import { bypassLogin } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject, getTextStyleOptions } from "./text"; import { Mode } from "./ui"; import * as Utils from "../utils"; import { addWindow, WindowVariant } from "./ui-theme"; import MessageUiHandler from "./message-ui-handler"; -import { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler"; +import type { OptionSelectConfig, OptionSelectItem } from "./abstact-option-select-ui-handler"; import { Tutorial, handleTutorial } from "../tutorial"; import { loggedInUser, updateUserInfo } from "../account"; import i18next from "i18next"; import { Button } from "#enums/buttons"; import { GameDataType } from "#enums/game-data-type"; import BgmBar from "#app/ui/bgm-bar"; -import AwaitableUiHandler from "./awaitable-ui-handler"; +import type AwaitableUiHandler from "./awaitable-ui-handler"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { AdminMode, getAdminModeName } from "./admin-ui-handler"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; @@ -63,8 +64,8 @@ export default class MenuUiHandler extends MessageUiHandler { public bgmBar: BgmBar; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.excludedMenus = () => [ { condition: [ Mode.COMMAND, Mode.TITLE ].includes(mode ?? Mode.TITLE), options: [ MenuOptions.EGG_GACHA, MenuOptions.EGG_LIST ]}, @@ -86,16 +87,16 @@ export default class MenuUiHandler extends MessageUiHandler { wikiUrl = `https://wiki.pokerogue.net/${lang}:start`; } - this.bgmBar = new BgmBar(this.scene); + this.bgmBar = new BgmBar(); this.bgmBar.setup(); ui.bgmBar = this.bgmBar; - this.menuContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.menuContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); this.menuContainer.setName("menu"); - this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - this.menuOverlay = new Phaser.GameObjects.Rectangle(this.scene, -1, -1, this.scene.scaledCanvas.width, this.scene.scaledCanvas.height, 0xffffff, 0.3); + this.menuOverlay = new Phaser.GameObjects.Rectangle(globalScene, -1, -1, globalScene.scaledCanvas.width, globalScene.scaledCanvas.height, 0xffffff, 0.3); this.menuOverlay.setName("menu-overlay"); this.menuOverlay.setOrigin(0, 0); this.menuContainer.add(this.menuOverlay); @@ -110,7 +111,7 @@ export default class MenuUiHandler extends MessageUiHandler { render() { const ui = this.getUi(); this.excludedMenus = () => [ - { condition: this.scene.getCurrentPhase() instanceof SelectModifierPhase, options: [ MenuOptions.EGG_GACHA, MenuOptions.EGG_LIST ]}, + { condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, options: [ MenuOptions.EGG_GACHA, MenuOptions.EGG_LIST ]}, { condition: bypassLogin, options: [ MenuOptions.LOG_OUT ]} ]; @@ -120,15 +121,15 @@ export default class MenuUiHandler extends MessageUiHandler { return !this.excludedMenus().some(exclusion => exclusion.condition && exclusion.options.includes(m)); }); - this.optionSelectText = addTextObject(this.scene, 0, 0, this.menuOptions.map(o => `${i18next.t(`menuUiHandler:${MenuOptions[o]}`)}`).join("\n"), TextStyle.WINDOW, { maxLines: this.menuOptions.length }); + this.optionSelectText = addTextObject(0, 0, this.menuOptions.map(o => `${i18next.t(`menuUiHandler:${MenuOptions[o]}`)}`).join("\n"), TextStyle.WINDOW, { maxLines: this.menuOptions.length }); this.optionSelectText.setLineSpacing(12); - this.scale = getTextStyleOptions(TextStyle.WINDOW, (this.scene as BattleScene).uiTheme).scale; - this.menuBg = addWindow(this.scene, - (this.scene.game.canvas.width / 6) - (this.optionSelectText.displayWidth + 25), + this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; + this.menuBg = addWindow( + (globalScene.game.canvas.width / 6) - (this.optionSelectText.displayWidth + 25), 0, this.optionSelectText.displayWidth + 19 + 24 * this.scale, - (this.scene.game.canvas.height / 6) - 2 + (globalScene.game.canvas.height / 6) - 2 ); this.menuBg.setOrigin(0, 0); @@ -140,21 +141,21 @@ export default class MenuUiHandler extends MessageUiHandler { ui.add(this.menuContainer); - this.menuMessageBoxContainer = this.scene.add.container(0, 130); + this.menuMessageBoxContainer = globalScene.add.container(0, 130); this.menuMessageBoxContainer.setName("menu-message-box"); this.menuMessageBoxContainer.setVisible(false); // Window for general messages - this.menuMessageBox = addWindow(this.scene, 0, 0, this.defaultMessageBoxWidth, 48); + this.menuMessageBox = addWindow(0, 0, this.defaultMessageBoxWidth, 48); this.menuMessageBox.setOrigin(0, 0); this.menuMessageBoxContainer.add(this.menuMessageBox); // Full-width window used for testing dialog messages in debug mode - this.dialogueMessageBox = addWindow(this.scene, -this.textPadding, 0, this.scene.game.canvas.width / 6 + this.textPadding * 2, 49, false, false, 0, 0, WindowVariant.THIN); + this.dialogueMessageBox = addWindow(-this.textPadding, 0, globalScene.game.canvas.width / 6 + this.textPadding * 2, 49, false, false, 0, 0, WindowVariant.THIN); this.dialogueMessageBox.setOrigin(0, 0); this.menuMessageBoxContainer.add(this.dialogueMessageBox); - const menuMessageText = addTextObject(this.scene, this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 }); + const menuMessageText = addTextObject(this.textPadding, this.textPadding, "", TextStyle.WINDOW, { maxLines: 2 }); menuMessageText.setName("menu-message"); menuMessageText.setOrigin(0, 0); this.menuMessageBoxContainer.add(menuMessageText); @@ -203,7 +204,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:importSession"), handler: () => { - confirmSlot(i18next.t("menuUiHandler:importSlotSelect"), () => true, slotId => this.scene.gameData.importData(GameDataType.SESSION, slotId)); + confirmSlot(i18next.t("menuUiHandler:importSlotSelect"), () => true, slotId => globalScene.gameData.importData(GameDataType.SESSION, slotId)); return true; }, keepOpen: true @@ -216,7 +217,7 @@ export default class MenuUiHandler extends MessageUiHandler { Promise.all( new Array(5).fill(null).map((_, i) => { const slotId = i; - return this.scene.gameData.getSession(slotId).then(data => { + return globalScene.gameData.getSession(slotId).then(data => { if (data) { dataSlots.push(slotId); } @@ -224,7 +225,7 @@ export default class MenuUiHandler extends MessageUiHandler { })).then(() => { confirmSlot(i18next.t("menuUiHandler:exportSlotSelect"), i => dataSlots.indexOf(i) > -1, - slotId => this.scene.gameData.tryExportData(GameDataType.SESSION, slotId)); + slotId => globalScene.gameData.tryExportData(GameDataType.SESSION, slotId)); }); return true; }, @@ -233,7 +234,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:importRunHistory"), handler: () => { - this.scene.gameData.importData(GameDataType.RUN_HISTORY); + globalScene.gameData.importData(GameDataType.RUN_HISTORY); return true; }, keepOpen: true @@ -241,7 +242,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:exportRunHistory"), handler: () => { - this.scene.gameData.tryExportData(GameDataType.RUN_HISTORY); + globalScene.gameData.tryExportData(GameDataType.RUN_HISTORY); return true; }, keepOpen: true @@ -251,7 +252,7 @@ export default class MenuUiHandler extends MessageUiHandler { label: i18next.t("menuUiHandler:importData"), handler: () => { ui.revertMode(); - this.scene.gameData.importData(GameDataType.SYSTEM); + globalScene.gameData.importData(GameDataType.SYSTEM); return true; }, keepOpen: true @@ -260,7 +261,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:exportData"), handler: () => { - this.scene.gameData.tryExportData(GameDataType.SYSTEM); + globalScene.gameData.tryExportData(GameDataType.SYSTEM); return true; }, keepOpen: true @@ -311,7 +312,7 @@ export default class MenuUiHandler extends MessageUiHandler { } // Switch to the dialog test window this.setDialogTestMode(true); - ui.showText(String(i18next.t(translatedString, interpolatorOptions)), null, () => this.scene.ui.showText("", 0, () => { + ui.showText(String(i18next.t(translatedString, interpolatorOptions)), null, () => globalScene.ui.showText("", 0, () => { handler.tutorialActive = false; // Go back to the default message window this.setDialogTestMode(false); @@ -330,7 +331,7 @@ export default class MenuUiHandler extends MessageUiHandler { manageDataOptions.push({ label: i18next.t("menuUiHandler:cancel"), handler: () => { - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); return true; }, keepOpen: true @@ -421,7 +422,7 @@ export default class MenuUiHandler extends MessageUiHandler { return true; } }); - this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + globalScene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options, delay: 0 }); @@ -433,7 +434,7 @@ export default class MenuUiHandler extends MessageUiHandler { communityOptions.push({ label: i18next.t("menuUiHandler:cancel"), handler: () => { - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); return true; } }); @@ -461,12 +462,12 @@ export default class MenuUiHandler extends MessageUiHandler { this.getUi().hideTooltip(); - this.scene.playSound("ui/menu_open"); + globalScene.playSound("ui/menu_open"); // Make sure the tutorial overlay sits above everything, but below the message box this.menuContainer.bringToTop(this.tutorialOverlay); this.menuContainer.bringToTop(this.menuMessageBoxContainer); - handleTutorial(this.scene, Tutorial.Menu); + handleTutorial(Tutorial.Menu); this.bgmBar.toggleBgmBar(true); @@ -512,7 +513,7 @@ export default class MenuUiHandler extends MessageUiHandler { success = true; break; case MenuOptions.EGG_LIST: - if (this.scene.gameData.eggs.length) { + if (globalScene.gameData.eggs.length) { ui.revertMode(); ui.setOverlayMode(Mode.EGG_LIST); success = true; @@ -541,7 +542,7 @@ export default class MenuUiHandler extends MessageUiHandler { return true; } else { pokerogueApi.unlinkDiscord().then(_isSuccess => { - updateUserInfo().then(() => this.scene.reset(true, true)); + updateUserInfo().then(() => globalScene.reset(true, true)); }); return true; } @@ -559,7 +560,7 @@ export default class MenuUiHandler extends MessageUiHandler { return true; } else { pokerogueApi.unlinkGoogle().then(_isSuccess => { - updateUserInfo().then(() => this.scene.reset(true, true)); + updateUserInfo().then(() => globalScene.reset(true, true)); }); return true; } @@ -574,18 +575,18 @@ export default class MenuUiHandler extends MessageUiHandler { success = true; break; case MenuOptions.SAVE_AND_QUIT: - if (this.scene.currentBattle) { + if (globalScene.currentBattle) { success = true; const doSaveQuit = () => { ui.setMode(Mode.LOADING, { buttonActions: [], fadeOut: () => - this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { + globalScene.gameData.saveAll(true, true, true, true).then(() => { - this.scene.reset(true); + globalScene.reset(true); }) }); }; - if (this.scene.currentBattle.turn > 1) { + if (globalScene.currentBattle.turn > 1) { ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { if (!this.active) { this.showText("", 0); @@ -608,11 +609,11 @@ export default class MenuUiHandler extends MessageUiHandler { const doLogout = () => { ui.setMode(Mode.LOADING, { buttonActions: [], fadeOut: () => pokerogueApi.account.logout().then(() => { - updateUserInfo().then(() => this.scene.reset(true, true)); + updateUserInfo().then(() => globalScene.reset(true, true)); }) }); }; - if (this.scene.currentBattle) { + if (globalScene.currentBattle) { ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { if (!this.active) { this.showText("", 0); @@ -673,7 +674,7 @@ export default class MenuUiHandler extends MessageUiHandler { this.menuMessageBox.setVisible(!isDialogMode); this.dialogueMessageBox.setVisible(isDialogMode); // If we're testing dialog, we use the same word wrapping as the battle message handler - this.message.setWordWrapWidth(isDialogMode ? this.scene.ui.getMessageHandler().wordWrapWidth : this.defaultWordWrapWidth); + this.message.setWordWrapWidth(isDialogMode ? globalScene.ui.getMessageHandler().wordWrapWidth : this.defaultWordWrapWidth); this.message.setX(isDialogMode ? this.textPadding + 1 : this.textPadding); this.message.setY(isDialogMode ? this.textPadding + 0.4 : this.textPadding); } @@ -688,7 +689,7 @@ export default class MenuUiHandler extends MessageUiHandler { const ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.cursorObj.setOrigin(0, 0); this.menuContainer.add(this.cursorObj); } diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index 5ae4707e329..81a09e90167 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -1,7 +1,7 @@ -import BattleScene from "../battle-scene"; import AwaitableUiHandler from "./awaitable-ui-handler"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; import * as Utils from "../utils"; +import { globalScene } from "#app/global-scene"; export default abstract class MessageUiHandler extends AwaitableUiHandler { protected textTimer: Phaser.Time.TimerEvent | null; @@ -11,8 +11,8 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { public message: Phaser.GameObjects.Text; public prompt: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.pendingPrompt = false; } @@ -23,7 +23,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { */ initPromptSprite(container: Phaser.GameObjects.Container) { if (!this.prompt) { - const promptSprite = this.scene.add.sprite(0, 0, "prompt"); + const promptSprite = globalScene.add.sprite(0, 0, "prompt"); promptSprite.setVisible(false); promptSprite.setOrigin(0, 0); this.prompt = promptSprite; @@ -108,7 +108,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { callback = () => { const showPrompt = () => this.showPrompt(originalCallback, callbackDelay); if (promptDelay) { - this.scene.time.delayedCall(promptDelay, showPrompt); + globalScene.time.delayedCall(promptDelay, showPrompt); } else { showPrompt(); } @@ -119,7 +119,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { if (prompt) { this.pendingPrompt = true; } - this.textTimer = this.scene.time.addEvent({ + this.textTimer = globalScene.time.addEvent({ delay: delay, callback: () => { const charIndex = text.length - (this.textTimer?.repeatCount!); // TODO: is this bang correct? @@ -130,14 +130,14 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { this.message.setText(text.slice(0, charIndex)); const advance = () => { if (charVar) { - this.scene.charSprite.setVariant(charVar); + globalScene.charSprite.setVariant(charVar); } if (charSound) { - this.scene.playSound(charSound); + globalScene.playSound(charSound); } if (callback && !this.textTimer?.repeatCount) { if (callbackDelay && !prompt) { - this.textCallbackTimer = this.scene.time.delayedCall(callbackDelay, () => { + this.textCallbackTimer = globalScene.time.delayedCall(callbackDelay, () => { if (this.textCallbackTimer) { this.textCallbackTimer.destroy(); this.textCallbackTimer = null; @@ -151,7 +151,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { }; if (charDelay) { this.textTimer!.paused = true; // TODO: is the bang correct? - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.getFrameMs(charDelay), onComplete: () => { this.textTimer!.paused = false; // TODO: is the bang correct? @@ -160,11 +160,11 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { }); } else if (charFade) { this.textTimer!.paused = true; - this.scene.time.delayedCall(150, () => { - this.scene.ui.fadeOut(750).then(() => { + globalScene.time.delayedCall(150, () => { + globalScene.ui.fadeOut(750).then(() => { const delay = Utils.getFrameMs(charFade); - this.scene.time.delayedCall(delay, () => { - this.scene.ui.fadeIn(500).then(() => { + globalScene.time.delayedCall(delay, () => { + globalScene.ui.fadeIn(500).then(() => { this.textTimer!.paused = false; advance(); }); @@ -192,7 +192,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { const wrappedTextLines = this.message.runWordWrap(this.message.text).split(/\n/g); const textLinesCount = wrappedTextLines.length; const lastTextLine = wrappedTextLines[wrappedTextLines.length - 1]; - const lastLineTest = this.scene.add.text(0, 0, lastTextLine, { font: "96px emerald" }); + const lastLineTest = globalScene.add.text(0, 0, lastTextLine, { font: "96px emerald" }); lastLineTest.setScale(this.message.scale); const lastLineWidth = lastLineTest.displayWidth; lastLineTest.destroy(); @@ -209,7 +209,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { } if (callback) { if (callbackDelay) { - this.textCallbackTimer = this.scene.time.delayedCall(callbackDelay, () => { + this.textCallbackTimer = globalScene.time.delayedCall(callbackDelay, () => { if (this.textCallbackTimer) { this.textCallbackTimer.destroy(); this.textCallbackTimer = null; diff --git a/src/ui/modal-ui-handler.ts b/src/ui/modal-ui-handler.ts index 79f1e8afeed..909010e3566 100644 --- a/src/ui/modal-ui-handler.ts +++ b/src/ui/modal-ui-handler.ts @@ -1,9 +1,9 @@ -import BattleScene from "../battle-scene"; import { TextStyle, addTextObject } from "./text"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { WindowVariant, addWindow } from "./ui-theme"; -import { Button } from "#enums/buttons"; +import type { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; export interface ModalConfig { buttonActions: Function[]; @@ -17,8 +17,8 @@ export abstract class ModalUiHandler extends UiHandler { protected buttonBgs: Phaser.GameObjects.NineSlice[]; protected buttonLabels: Phaser.GameObjects.Text[]; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.buttonContainers = []; this.buttonBgs = []; @@ -42,15 +42,15 @@ export abstract class ModalUiHandler extends UiHandler { setup() { const ui = this.getUi(); - this.modalContainer = this.scene.add.container(0, 0); + this.modalContainer = globalScene.add.container(0, 0); - this.modalContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.modalContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - this.modalBg = addWindow(this.scene, 0, 0, 0, 0); + this.modalBg = addWindow(0, 0, 0, 0); this.modalContainer.add(this.modalBg); - this.titleText = addTextObject(this.scene, 0, 4, "", TextStyle.SETTINGS_LABEL); + this.titleText = addTextObject(0, 4, "", TextStyle.SETTINGS_LABEL); this.titleText.setOrigin(0.5, 0); this.modalContainer.add(this.titleText); @@ -68,14 +68,14 @@ export abstract class ModalUiHandler extends UiHandler { private addButton(label: string) { const buttonTopMargin = this.getButtonTopMargin(); - const buttonLabel = addTextObject(this.scene, 0, 8, label, TextStyle.TOOLTIP_CONTENT); + const buttonLabel = addTextObject(0, 8, label, TextStyle.TOOLTIP_CONTENT); buttonLabel.setOrigin(0.5, 0.5); - const buttonBg = addWindow(this.scene, 0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN); + const buttonBg = addWindow(0, 0, buttonLabel.getBounds().width + 8, 16, false, false, 0, 0, WindowVariant.THIN); buttonBg.setOrigin(0.5, 0); buttonBg.setInteractive(new Phaser.Geom.Rectangle(0, 0, buttonBg.width, buttonBg.height), Phaser.Geom.Rectangle.Contains); - const buttonContainer = this.scene.add.container(0, buttonTopMargin); + const buttonContainer = globalScene.add.container(0, buttonTopMargin); this.buttonLabels.push(buttonLabel); this.buttonBgs.push(buttonBg); @@ -95,7 +95,7 @@ export abstract class ModalUiHandler extends UiHandler { if (args[0].hasOwnProperty("fadeOut") && typeof args[0].fadeOut === "function") { const [ marginTop, marginRight, marginBottom, marginLeft ] = this.getMargin(); - const overlay = this.scene.add.rectangle(( this.getWidth() + marginLeft + marginRight) / 2, (this.getHeight() + marginTop + marginBottom) / 2, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0); + const overlay = globalScene.add.rectangle(( this.getWidth() + marginLeft + marginRight) / 2, (this.getHeight() + marginTop + marginBottom) / 2, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0); overlay.setOrigin(0.5, 0.5); overlay.setName("rect-ui-overlay-modal"); overlay.setAlpha(0); @@ -103,7 +103,7 @@ export abstract class ModalUiHandler extends UiHandler { this.modalContainer.add(overlay); this.modalContainer.moveTo(overlay, 0); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: overlay, alpha: 1, duration: 250, @@ -136,7 +136,7 @@ export abstract class ModalUiHandler extends UiHandler { const [ marginTop, marginRight, marginBottom, marginLeft ] = this.getMargin(config); const [ width, height ] = [ this.getWidth(config), this.getHeight(config) ]; - this.modalContainer.setPosition((((this.scene.game.canvas.width / 6) - (width + (marginRight - marginLeft))) / 2), (((-this.scene.game.canvas.height / 6) - (height + (marginBottom - marginTop))) / 2)); + this.modalContainer.setPosition((((globalScene.game.canvas.width / 6) - (width + (marginRight - marginLeft))) / 2), (((-globalScene.game.canvas.height / 6) - (height + (marginBottom - marginTop))) / 2)); this.modalBg.setSize(width, height); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index a0358b5ca8c..05740a349c6 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -1,5 +1,6 @@ -import BattleScene from "../battle-scene"; -import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "../modifier/modifier-type"; +import { globalScene } from "#app/global-scene"; +import type { ModifierTypeOption } from "../modifier/modifier-type"; +import { getPlayerShopModifierTypeOptionsForWave, TmModifierType } from "../modifier/modifier-type"; import { getPokeballAtlasKey } from "#app/data/pokeball"; import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; import AwaitableUiHandler from "./awaitable-ui-handler"; @@ -49,8 +50,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private cursorObj: Phaser.GameObjects.Image | null; - constructor(scene: BattleScene) { - super(scene, Mode.CONFIRM); + constructor() { + super(Mode.CONFIRM); this.options = []; this.shopOptionsRows = []; @@ -59,12 +60,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { setup() { const ui = this.getUi(); - this.modifierContainer = this.scene.add.container(0, 0); + this.modifierContainer = globalScene.add.container(0, 0); ui.add(this.modifierContainer); const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); - const styleOptions = getTextStyleOptions(TextStyle.PARTY, (this.scene as BattleScene).uiTheme).styleOptions; + const styleOptions = getTextStyleOptions(TextStyle.PARTY, globalScene.uiTheme).styleOptions; if (context) { context.font = styleOptions.fontSize + "px " + styleOptions.fontFamily; @@ -72,78 +73,78 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width; } - this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION); + this.transferButtonContainer = globalScene.add.container((globalScene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION); this.transferButtonContainer.setName("transfer-btn"); this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); - const transferButtonText = addTextObject(this.scene, -4, -2, i18next.t("modifierSelectUiHandler:transfer"), TextStyle.PARTY); + const transferButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:transfer"), TextStyle.PARTY); transferButtonText.setName("text-transfer-btn"); transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); - this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION); + this.checkButtonContainer = globalScene.add.container((globalScene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION); this.checkButtonContainer.setName("use-btn"); this.checkButtonContainer.setVisible(false); ui.add(this.checkButtonContainer); - const checkButtonText = addTextObject(this.scene, -4, -2, i18next.t("modifierSelectUiHandler:checkTeam"), TextStyle.PARTY); + const checkButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:checkTeam"), TextStyle.PARTY); checkButtonText.setName("text-use-btn"); checkButtonText.setOrigin(1, 0); this.checkButtonContainer.add(checkButtonText); - this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); + this.rerollButtonContainer = globalScene.add.container(16, OPTION_BUTTON_YPOSITION); this.rerollButtonContainer.setName("reroll-brn"); this.rerollButtonContainer.setVisible(false); ui.add(this.rerollButtonContainer); - const rerollButtonText = addTextObject(this.scene, -4, -2, i18next.t("modifierSelectUiHandler:reroll"), TextStyle.PARTY); + const rerollButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:reroll"), TextStyle.PARTY); rerollButtonText.setName("text-reroll-btn"); rerollButtonText.setOrigin(0, 0); this.rerollButtonContainer.add(rerollButtonText); - this.rerollCostText = addTextObject(this.scene, 0, 0, "", TextStyle.MONEY); + this.rerollCostText = addTextObject(0, 0, "", TextStyle.MONEY); this.rerollCostText.setName("text-reroll-cost"); this.rerollCostText.setOrigin(0, 0); this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1); this.rerollButtonContainer.add(this.rerollCostText); - this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); + this.lockRarityButtonContainer = globalScene.add.container(16, OPTION_BUTTON_YPOSITION); this.lockRarityButtonContainer.setVisible(false); ui.add(this.lockRarityButtonContainer); - this.lockRarityButtonText = addTextObject(this.scene, -4, -2, i18next.t("modifierSelectUiHandler:lockRarities"), TextStyle.PARTY); + this.lockRarityButtonText = addTextObject(-4, -2, i18next.t("modifierSelectUiHandler:lockRarities"), TextStyle.PARTY); this.lockRarityButtonText.setOrigin(0, 0); this.lockRarityButtonContainer.add(this.lockRarityButtonText); - this.continueButtonContainer = this.scene.add.container((this.scene.game.canvas.width / 12), -(this.scene.game.canvas.height / 12)); + this.continueButtonContainer = globalScene.add.container((globalScene.game.canvas.width / 12), -(globalScene.game.canvas.height / 12)); this.continueButtonContainer.setVisible(false); ui.add(this.continueButtonContainer); // Create continue button - const continueButtonText = addTextObject(this.scene, -24, 5, i18next.t("modifierSelectUiHandler:continueNextWaveButton"), TextStyle.MESSAGE); + const continueButtonText = addTextObject(-24, 5, i18next.t("modifierSelectUiHandler:continueNextWaveButton"), TextStyle.MESSAGE); continueButtonText.setName("text-continue-btn"); this.continueButtonContainer.add(continueButtonText); // prepare move overlay const overlayScale = 1; - this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + this.moveInfoOverlay = new MoveInfoOverlay({ delayVisibility: true, scale: overlayScale, onSide: true, right: true, x: 1, y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1, - width: (this.scene.game.canvas.width / 6) - 2, + width: (globalScene.game.canvas.width / 6) - 2, }); ui.add(this.moveInfoOverlay); // register the overlay to receive toggle events - this.scene.addInfoToggle(this.moveInfoOverlay); + globalScene.addInfoToggle(this.moveInfoOverlay); } show(args: any[]): boolean { - this.scene.disableMenu = false; + globalScene.disableMenu = false; if (this.active) { if (args.length >= 3) { @@ -164,8 +165,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.player = args[0]; - const partyHasHeldItem = this.player && !!this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable).length; - const canLockRarities = !!this.scene.findModifier(m => m instanceof LockModifierTiersModifier); + const partyHasHeldItem = this.player && !!globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable).length; + const canLockRarities = !!globalScene.findModifier(m => m instanceof LockModifierTiersModifier); this.transferButtonContainer.setVisible(false); this.transferButtonContainer.setAlpha(0); @@ -189,19 +190,19 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.updateRerollCostText(); const typeOptions = args[1] as ModifierTypeOption[]; - const removeHealShop = this.scene.gameMode.hasNoShop; - const baseShopCost = new IntegerHolder(this.scene.getWaveMoneyAmount(1)); - this.scene.applyModifier(HealShopCostModifier, true, baseShopCost); + const removeHealShop = globalScene.gameMode.hasNoShop; + const baseShopCost = new IntegerHolder(globalScene.getWaveMoneyAmount(1)); + globalScene.applyModifier(HealShopCostModifier, true, baseShopCost); const shopTypeOptions = !removeHealShop - ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value) + ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) : []; const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; for (let m = 0; m < typeOptions.length; m++) { - const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2); - const option = new ModifierOption(this.scene, sliceWidth * (m + 1) + (sliceWidth * 0.5), -this.scene.game.canvas.height / 12 + optionsYOffset, typeOptions[m]); + const sliceWidth = (globalScene.game.canvas.width / 6) / (typeOptions.length + 2); + const option = new ModifierOption(sliceWidth * (m + 1) + (sliceWidth * 0.5), -globalScene.game.canvas.height / 12 + optionsYOffset, typeOptions[m]); option.setScale(0.5); - this.scene.add.existing(option); + globalScene.add.existing(option); this.modifierContainer.add(option); this.options.push(option); } @@ -215,10 +216,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1; const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT; const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT); - const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2); - const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]); + const sliceWidth = (globalScene.game.canvas.width / 6) / (rowOptions.length + 2); + const option = new ModifierOption(sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-globalScene.game.canvas.height / 12) - (globalScene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]); option.setScale(0.375); - this.scene.add.existing(option); + globalScene.add.existing(option); this.modifierContainer.add(option); if (row >= this.shopOptionsRows.length) { @@ -230,17 +231,17 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const maxUpgradeCount = typeOptions.map(to => to.upgradeCount).reduce((max, current) => Math.max(current, max), 0); /* Force updateModifiers without pokemonSpecificModifiers */ - this.scene.getModifierBar().updateModifiers(this.scene.modifiers, true); + globalScene.getModifierBar().updateModifiers(globalScene.modifiers, true); /* Multiplies the appearance duration by the speed parameter so that it is always constant, and avoids "flashbangs" at game speed x5 */ - this.scene.showShopOverlay(750 * this.scene.gameSpeed); - this.scene.updateAndShowText(750); - this.scene.updateBiomeWaveText(); - this.scene.updateMoneyText(); + globalScene.showShopOverlay(750 * globalScene.gameSpeed); + globalScene.updateAndShowText(750); + globalScene.updateBiomeWaveText(); + globalScene.updateMoneyText(); let i = 0; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ ease: "Sine.easeIn", duration: 1250, onUpdate: t => { @@ -254,17 +255,17 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } }); - this.scene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => { + globalScene.time.delayedCall(1000 + maxUpgradeCount * 2000, () => { for (const shopOption of this.shopOptionsRows.flat()) { shopOption.show(0, 0); } }); - this.scene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => { + globalScene.time.delayedCall(4000 + maxUpgradeCount * 2000, () => { if (partyHasHeldItem) { this.transferButtonContainer.setAlpha(0); this.transferButtonContainer.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.transferButtonContainer, alpha: 1, duration: 250 @@ -280,34 +281,34 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.continueButtonContainer.setVisible(this.rerollCost < 0); this.lockRarityButtonContainer.setVisible(canLockRarities); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.checkButtonContainer, this.continueButtonContainer ], alpha: 1, duration: 250 }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.rerollButtonContainer, this.lockRarityButtonContainer ], alpha: this.rerollCost < 0 ? 0.5 : 1, duration: 250 }); const updateCursorTarget = () => { - if (this.scene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) { + if (globalScene.shopCursorTarget === ShopCursorTarget.CHECK_TEAM) { this.setRowCursor(0); this.setCursor(2); - } else if ((this.scene.shopCursorTarget === ShopCursorTarget.SHOP) && this.scene.gameMode.hasNoShop) { + } else if ((globalScene.shopCursorTarget === ShopCursorTarget.SHOP) && globalScene.gameMode.hasNoShop) { this.setRowCursor(ShopCursorTarget.REWARDS); this.setCursor(0); } else { - this.setRowCursor(this.scene.shopCursorTarget); + this.setRowCursor(globalScene.shopCursorTarget); this.setCursor(0); } }; updateCursorTarget(); - handleTutorial(this.scene, Tutorial.Select_Item).then((res) => { + handleTutorial(Tutorial.Select_Item).then((res) => { if (res) { updateCursorTarget(); } @@ -446,7 +447,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const ret = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.modifierContainer.add(this.cursorObj); } @@ -460,22 +461,22 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { if (this.rowCursor === 1 && options.length === 0) { // Continue button when no shop items this.cursorObj.setScale(1.25); - this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); + this.cursorObj.setPosition((globalScene.game.canvas.width / 18) + 23, (-globalScene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription")); return ret; } - const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2); + const sliceWidth = (globalScene.game.canvas.width / 6) / (options.length + 2); if (this.rowCursor < 2) { // Cursor on free items - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-globalScene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); } else { // Cursor on paying items - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-globalScene.game.canvas.height / 12 - globalScene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); } const type = options[this.cursor].modifierTypeOption.type; - type && ui.showText(type.getDescription(this.scene)); + type && ui.showText(type.getDescription()); if (type instanceof TmModifierType) { // prepare the move overlay to be shown with the toggle this.moveInfoOverlay.show(allMoves[type.moveId]); @@ -484,10 +485,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc")); } else if (cursor === 1) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4); + this.cursorObj.setPosition((globalScene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:transferDesc")); } else if (cursor === 2) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4); + this.cursorObj.setPosition((globalScene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc")); } else { this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4); @@ -558,9 +559,9 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } else { this.rerollCostText.setVisible(true); } - const canReroll = this.scene.money >= this.rerollCost; + const canReroll = globalScene.money >= this.rerollCost; - const formattedMoney = Utils.formatMoney(this.scene.moneyFormat, this.rerollCost); + const formattedMoney = Utils.formatMoney(globalScene.moneyFormat, this.rerollCost); this.rerollCostText.setText(i18next.t("modifierSelectUiHandler:rerollCost", { formattedMoney })); this.rerollCostText.setColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED)); @@ -568,7 +569,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } updateLockRaritiesText(): void { - const textStyle = this.scene.lockModifierTiers ? TextStyle.SUMMARY_BLUE : TextStyle.PARTY; + const textStyle = globalScene.lockModifierTiers ? TextStyle.SUMMARY_BLUE : TextStyle.PARTY; this.lockRarityButtonText.setColor(this.getTextColor(textStyle)); this.lockRarityButtonText.setShadowColor(this.getTextColor(textStyle, true)); } @@ -588,17 +589,17 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.rowCursor = 0; /* Multiplies the fade time duration by the speed parameter so that it is always constant, and avoids "flashbangs" at game speed x5 */ - this.scene.hideShopOverlay(750 * this.scene.gameSpeed); - this.scene.hideLuckText(250); + globalScene.hideShopOverlay(750 * globalScene.gameSpeed); + globalScene.hideLuckText(250); /* Normally already called just after the shop, but not sure if it happens in 100% of cases */ - this.scene.getModifierBar().updateModifiers(this.scene.modifiers); + globalScene.getModifierBar().updateModifiers(globalScene.modifiers); const options = this.options.concat(this.shopOptionsRows.flat()); this.options.splice(0, this.options.length); this.shopOptionsRows.splice(0, this.shopOptionsRows.length); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: options, scale: 0.01, duration: 250, @@ -608,7 +609,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { [ this.rerollButtonContainer, this.checkButtonContainer, this.transferButtonContainer, this.lockRarityButtonContainer, this.continueButtonContainer ].forEach(container => { if (container.visible) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: container, alpha: 0, duration: 250, @@ -643,8 +644,8 @@ class ModifierOption extends Phaser.GameObjects.Container { private itemText: Phaser.GameObjects.Text; private itemCostText: Phaser.GameObjects.Text; - constructor(scene: BattleScene, x: number, y: number, modifierTypeOption: ModifierTypeOption) { - super(scene, x, y); + constructor(x: number, y: number, modifierTypeOption: ModifierTypeOption) { + super(globalScene, x, y); this.modifierTypeOption = modifierTypeOption; @@ -654,7 +655,7 @@ class ModifierOption extends Phaser.GameObjects.Container { setup() { if (!this.modifierTypeOption.cost) { const getPb = (): Phaser.GameObjects.Sprite => { - const pb = this.scene.add.sprite(0, -182, "pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount)); + const pb = globalScene.add.sprite(0, -182, "pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount)); pb.setScale(2); return pb; }; @@ -667,13 +668,13 @@ class ModifierOption extends Phaser.GameObjects.Container { this.add(this.pbTint); } - this.itemContainer = this.scene.add.container(0, 0); + this.itemContainer = globalScene.add.container(0, 0); this.itemContainer.setScale(0.5); this.itemContainer.setAlpha(0); this.add(this.itemContainer); const getItem = () => { - const item = this.scene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.iconImage); + const item = globalScene.add.sprite(0, 0, "items", this.modifierTypeOption.type?.iconImage); return item; }; @@ -686,14 +687,14 @@ class ModifierOption extends Phaser.GameObjects.Container { this.itemContainer.add(this.itemTint); } - this.itemText = addTextObject(this.scene, 0, 35, this.modifierTypeOption.type?.name!, TextStyle.PARTY, { align: "center" }); // TODO: is this bang correct? + this.itemText = addTextObject(0, 35, this.modifierTypeOption.type?.name!, TextStyle.PARTY, { align: "center" }); // TODO: is this bang correct? this.itemText.setOrigin(0.5, 0); this.itemText.setAlpha(0); this.itemText.setTint(this.modifierTypeOption.type?.tier ? getModifierTierTextTint(this.modifierTypeOption.type?.tier) : undefined); this.add(this.itemText); if (this.modifierTypeOption.cost) { - this.itemCostText = addTextObject(this.scene, 0, 45, "", TextStyle.MONEY, { align: "center" }); + this.itemCostText = addTextObject(0, 45, "", TextStyle.MONEY, { align: "center" }); this.itemCostText.setOrigin(0.5, 0); this.itemCostText.setAlpha(0); @@ -705,7 +706,7 @@ class ModifierOption extends Phaser.GameObjects.Container { show(remainingDuration: integer, upgradeCountOffset: integer) { if (!this.modifierTypeOption.cost) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pb, y: 0, duration: 1250, @@ -716,18 +717,18 @@ class ModifierOption extends Phaser.GameObjects.Container { let bounceCount = 0; let bounce = false; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ from: 1, to: 0, duration: 1250, ease: "Bounce.Out", onUpdate: t => { - if (!this.scene) { + if (!globalScene) { return; } const value = t.getValue(); if (!bounce && value > lastValue) { - (this.scene as BattleScene).playSound("se/pb_bounce_1", { volume: 1 / ++bounceCount }); + globalScene.playSound("se/pb_bounce_1", { volume: 1 / ++bounceCount }); bounce = true; } else if (bounce && value < lastValue) { bounce = false; @@ -738,20 +739,20 @@ class ModifierOption extends Phaser.GameObjects.Container { for (let u = 0; u < this.modifierTypeOption.upgradeCount; u++) { const upgradeIndex = u; - this.scene.time.delayedCall(remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (upgradeIndex + 1 + upgradeCountOffset)), () => { - (this.scene as BattleScene).playSound("se/upgrade", { rate: 1 + 0.25 * upgradeIndex }); + globalScene.time.delayedCall(remainingDuration - 2000 * (this.modifierTypeOption.upgradeCount - (upgradeIndex + 1 + upgradeCountOffset)), () => { + globalScene.playSound("se/upgrade", { rate: 1 + 0.25 * upgradeIndex }); this.pbTint.setPosition(this.pb.x, this.pb.y); this.pbTint.setTintFill(0xFFFFFF); this.pbTint.setAlpha(0); this.pbTint.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pbTint, alpha: 1, duration: 1000, ease: "Sine.easeIn", onComplete: () => { this.pb.setTexture("pb", this.getPbAtlasKey(-this.modifierTypeOption.upgradeCount + (upgradeIndex + 1))); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pbTint, alpha: 0, duration: 750, @@ -766,16 +767,16 @@ class ModifierOption extends Phaser.GameObjects.Container { } } - this.scene.time.delayedCall(remainingDuration + 2000, () => { - if (!this.scene) { + globalScene.time.delayedCall(remainingDuration + 2000, () => { + if (!globalScene) { return; } if (!this.modifierTypeOption.cost) { this.pb.setTexture("pb", `${this.getPbAtlasKey(0)}_open`); - (this.scene as BattleScene).playSound("se/pb_rel"); + globalScene.playSound("se/pb_rel"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pb, duration: 500, delay: 250, @@ -785,7 +786,7 @@ class ModifierOption extends Phaser.GameObjects.Container { }); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.itemContainer, duration: 500, ease: "Elastic.Out", @@ -793,7 +794,7 @@ class ModifierOption extends Phaser.GameObjects.Container { alpha: 1 }); if (!this.modifierTypeOption.cost) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.itemTint, alpha: 0, duration: 500, @@ -801,7 +802,7 @@ class ModifierOption extends Phaser.GameObjects.Container { onComplete: () => this.itemTint.destroy() }); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.itemText, duration: 500, alpha: 1, @@ -809,7 +810,7 @@ class ModifierOption extends Phaser.GameObjects.Container { ease: "Cubic.easeInOut" }); if (this.itemCostText) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.itemCostText, duration: 500, alpha: 1, @@ -825,14 +826,13 @@ class ModifierOption extends Phaser.GameObjects.Container { } updateCostText(): void { - const scene = this.scene as BattleScene; const cost = Overrides.WAIVE_ROLL_FEE_OVERRIDE ? 0 : this.modifierTypeOption.cost; - const textStyle = cost <= scene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; + const textStyle = cost <= globalScene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; - const formattedMoney = Utils.formatMoney(scene.moneyFormat, cost); + const formattedMoney = Utils.formatMoney(globalScene.moneyFormat, cost); this.itemCostText.setText(i18next.t("modifierSelectUiHandler:itemCost", { formattedMoney })); - this.itemCostText.setColor(getTextColor(textStyle, false, scene.uiTheme)); - this.itemCostText.setShadowColor(getTextColor(textStyle, true, scene.uiTheme)); + this.itemCostText.setColor(getTextColor(textStyle, false, globalScene.uiTheme)); + this.itemCostText.setShadowColor(getTextColor(textStyle, true, globalScene.uiTheme)); } } diff --git a/src/ui/move-info-overlay.ts b/src/ui/move-info-overlay.ts index d9c4200ea9b..5b3b30b14dd 100644 --- a/src/ui/move-info-overlay.ts +++ b/src/ui/move-info-overlay.ts @@ -1,8 +1,10 @@ -import BattleScene, { InfoToggle } from "../battle-scene"; +import type { InfoToggle } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "./text"; import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; -import Move, { MoveCategory } from "../data/move"; +import type Move from "../data/move"; +import { MoveCategory } from "../data/move"; import { Type } from "#enums/type"; import i18next from "i18next"; @@ -46,23 +48,23 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem private options : MoveInfoOverlaySettings; - constructor(scene: BattleScene, options?: MoveInfoOverlaySettings) { + constructor(options?: MoveInfoOverlaySettings) { if (options?.onSide) { options.top = false; } - super(scene, options?.x, options?.y); + super(globalScene, options?.x, options?.y); const scale = options?.scale || 1; // set up the scale this.setScale(scale); this.options = options || {}; // prepare the description box - const width = (options?.width || MoveInfoOverlay.getWidth(scale, scene)) / scale; // divide by scale as we always want this to be half a window wide - this.descBg = addWindow(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0), options?.top ? EFF_HEIGHT : 0, width - (options?.onSide ? EFF_WIDTH : 0), DESC_HEIGHT); + const width = (options?.width || MoveInfoOverlay.getWidth(scale)) / scale; // divide by scale as we always want this to be half a window wide + this.descBg = addWindow( (options?.onSide && !options?.right ? EFF_WIDTH : 0), options?.top ? EFF_HEIGHT : 0, width - (options?.onSide ? EFF_WIDTH : 0), DESC_HEIGHT); this.descBg.setOrigin(0, 0); this.add(this.descBg); // set up the description; wordWrap uses true pixels, unaffected by any scaling, while other values are affected - this.desc = addTextObject(scene, (options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER, (options?.top ? EFF_HEIGHT : 0) + BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (width - (BORDER - 2) * 2 - (options?.onSide ? EFF_WIDTH : 0)) * GLOBAL_SCALE }}); + this.desc = addTextObject((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER, (options?.top ? EFF_HEIGHT : 0) + BORDER - 2, "", TextStyle.BATTLE_INFO, { wordWrap: { width: (width - (BORDER - 2) * 2 - (options?.onSide ? EFF_WIDTH : 0)) * GLOBAL_SCALE }}); this.desc.setLineSpacing(i18next.resolvedLanguage === "ja" ? 25 : 5); // limit the text rendering, required for scrolling later on @@ -71,13 +73,13 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem y: (options?.y || 0), }; if (maskPointOrigin.x < 0) { - maskPointOrigin.x += this.scene.game.canvas.width / GLOBAL_SCALE; + maskPointOrigin.x += globalScene.game.canvas.width / GLOBAL_SCALE; } if (maskPointOrigin.y < 0) { - maskPointOrigin.y += this.scene.game.canvas.height / GLOBAL_SCALE; + maskPointOrigin.y += globalScene.game.canvas.height / GLOBAL_SCALE; } - const moveDescriptionTextMaskRect = this.scene.make.graphics(); + const moveDescriptionTextMaskRect = globalScene.make.graphics(); moveDescriptionTextMaskRect.fillStyle(0xFF0000); moveDescriptionTextMaskRect.fillRect( maskPointOrigin.x + ((options?.onSide && !options?.right ? EFF_WIDTH : 0) + BORDER) * scale, maskPointOrigin.y + ((options?.top ? EFF_HEIGHT : 0) + BORDER - 2) * scale, @@ -89,44 +91,44 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem this.desc.setMask(moveDescriptionTextMask); // prepare the effect box - this.val = new Phaser.GameObjects.Container(scene, options?.right ? width - EFF_WIDTH : 0, options?.top || options?.onSide ? 0 : DESC_HEIGHT); + this.val = new Phaser.GameObjects.Container(globalScene, options?.right ? width - EFF_WIDTH : 0, options?.top || options?.onSide ? 0 : DESC_HEIGHT); this.add(this.val); - const valuesBg = addWindow(scene, 0, 0, EFF_WIDTH, EFF_HEIGHT); + const valuesBg = addWindow(0, 0, EFF_WIDTH, EFF_HEIGHT); valuesBg.setOrigin(0, 0); this.val.add(valuesBg); - this.typ = this.scene.add.sprite(25, EFF_HEIGHT - 35, Utils.getLocalizedSpriteKey("types"), "unknown"); + this.typ = globalScene.add.sprite(25, EFF_HEIGHT - 35, Utils.getLocalizedSpriteKey("types"), "unknown"); this.typ.setScale(0.8); this.val.add(this.typ); - this.cat = this.scene.add.sprite(57, EFF_HEIGHT - 35, "categories", "physical"); + this.cat = globalScene.add.sprite(57, EFF_HEIGHT - 35, "categories", "physical"); this.val.add(this.cat); - const ppTxt = addTextObject(scene, 12, EFF_HEIGHT - 25, "PP", TextStyle.MOVE_INFO_CONTENT); + const ppTxt = addTextObject(12, EFF_HEIGHT - 25, "PP", TextStyle.MOVE_INFO_CONTENT); ppTxt.setOrigin(0.0, 0.5); ppTxt.setText(i18next.t("fightUiHandler:pp")); this.val.add(ppTxt); - this.pp = addTextObject(scene, 70, EFF_HEIGHT - 25, "--", TextStyle.MOVE_INFO_CONTENT); + this.pp = addTextObject(70, EFF_HEIGHT - 25, "--", TextStyle.MOVE_INFO_CONTENT); this.pp.setOrigin(1, 0.5); this.val.add(this.pp); - const powTxt = addTextObject(scene, 12, EFF_HEIGHT - 17, "POWER", TextStyle.MOVE_INFO_CONTENT); + const powTxt = addTextObject(12, EFF_HEIGHT - 17, "POWER", TextStyle.MOVE_INFO_CONTENT); powTxt.setOrigin(0.0, 0.5); powTxt.setText(i18next.t("fightUiHandler:power")); this.val.add(powTxt); - this.pow = addTextObject(scene, 70, EFF_HEIGHT - 17, "---", TextStyle.MOVE_INFO_CONTENT); + this.pow = addTextObject(70, EFF_HEIGHT - 17, "---", TextStyle.MOVE_INFO_CONTENT); this.pow.setOrigin(1, 0.5); this.val.add(this.pow); - const accTxt = addTextObject(scene, 12, EFF_HEIGHT - 9, "ACC", TextStyle.MOVE_INFO_CONTENT); + const accTxt = addTextObject(12, EFF_HEIGHT - 9, "ACC", TextStyle.MOVE_INFO_CONTENT); accTxt.setOrigin(0.0, 0.5); accTxt.setText(i18next.t("fightUiHandler:accuracy")); this.val.add(accTxt); - this.acc = addTextObject(scene, 70, EFF_HEIGHT - 9, "---", TextStyle.MOVE_INFO_CONTENT); + this.acc = addTextObject(70, EFF_HEIGHT - 9, "---", TextStyle.MOVE_INFO_CONTENT); this.acc.setOrigin(1, 0.5); this.val.add(this.acc); @@ -144,7 +146,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem // show this component with infos for the specific move show(move : Move):boolean { - if (!(this.scene as BattleScene).enableMoveInfo) { + if (!globalScene.enableMoveInfo) { return false; // move infos have been disabled // TODO:: is `false` correct? i used to be `undeefined` } this.move = move; @@ -167,7 +169,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem const moveDescriptionLineCount = Math.floor(this.desc.displayHeight * (96 / 72) / 14.83); if (moveDescriptionLineCount > 3) { // generate scrolling effects - this.descScroll = this.scene.tweens.add({ + this.descScroll = globalScene.tweens.add({ targets: this.desc, delay: Utils.fixedInt(2000), loop: -1, @@ -193,7 +195,7 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem if (visible) { this.setVisible(true); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.desc, duration: Utils.fixedInt(125), ease: "Sine.easeInOut", @@ -209,8 +211,8 @@ export default class MoveInfoOverlay extends Phaser.GameObjects.Container implem } // width of this element - static getWidth(scale:number, scene: BattleScene):number { - return scene.game.canvas.width / GLOBAL_SCALE / 2; + static getWidth(scale:number):number { + return globalScene.game.canvas.width / GLOBAL_SCALE / 2; } // height of this element diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index cdb1c9024c5..383f4ab73d3 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -1,21 +1,21 @@ -import BattleScene from "../battle-scene"; import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "./text"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import { Button } from "#enums/buttons"; import { addWindow, WindowVariant } from "./ui-theme"; -import { MysteryEncounterPhase } from "../phases/mystery-encounter-phases"; +import type { MysteryEncounterPhase } from "../phases/mystery-encounter-phases"; import { PartyUiMode } from "./party-ui-handler"; -import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; +import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import * as Utils from "../utils"; import { isNullOrUndefined } from "../utils"; import { getPokeballAtlasKey } from "../data/pokeball"; -import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import type { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import i18next from "i18next"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import { globalScene } from "#app/global-scene"; export default class MysteryEncounterUiHandler extends UiHandler { private cursorContainer: Phaser.GameObjects.Container; @@ -47,45 +47,45 @@ export default class MysteryEncounterUiHandler extends UiHandler { protected blockInput: boolean = true; - constructor(scene: BattleScene) { - super(scene, Mode.MYSTERY_ENCOUNTER); + constructor() { + super(Mode.MYSTERY_ENCOUNTER); } override setup() { const ui = this.getUi(); - this.cursorContainer = this.scene.add.container(18, -38.7); + this.cursorContainer = globalScene.add.container(18, -38.7); this.cursorContainer.setVisible(false); ui.add(this.cursorContainer); - this.optionsContainer = this.scene.add.container(12, -38.7); + this.optionsContainer = globalScene.add.container(12, -38.7); this.optionsContainer.setVisible(false); ui.add(this.optionsContainer); - this.dexProgressContainer = this.scene.add.container(214, -43); + this.dexProgressContainer = globalScene.add.container(214, -43); this.dexProgressContainer.setVisible(false); ui.add(this.dexProgressContainer); - this.descriptionContainer = this.scene.add.container(0, -152); + this.descriptionContainer = globalScene.add.container(0, -152); this.descriptionContainer.setVisible(false); ui.add(this.descriptionContainer); - this.tooltipContainer = this.scene.add.container(210, -48); + this.tooltipContainer = globalScene.add.container(210, -48); this.tooltipContainer.setVisible(false); ui.add(this.tooltipContainer); this.setCursor(this.getCursor()); - this.descriptionWindow = addWindow(this.scene, 0, 0, 150, 105, false, false, 0, 0, WindowVariant.THIN); + this.descriptionWindow = addWindow(0, 0, 150, 105, false, false, 0, 0, WindowVariant.THIN); this.descriptionContainer.add(this.descriptionWindow); - this.tooltipWindow = addWindow(this.scene, 0, 0, 110, 48, false, false, 0, 0, WindowVariant.THIN); + this.tooltipWindow = addWindow(0, 0, 110, 48, false, false, 0, 0, WindowVariant.THIN); this.tooltipContainer.add(this.tooltipWindow); - this.dexProgressWindow = addWindow(this.scene, 0, 0, 24, 28, false, false, 0, 0, WindowVariant.THIN); + this.dexProgressWindow = addWindow(0, 0, 24, 28, false, false, 0, 0, WindowVariant.THIN); this.dexProgressContainer.add(this.dexProgressWindow); - this.rarityBall = this.scene.add.sprite(141, 9, "pb"); + this.rarityBall = globalScene.add.sprite(141, 9, "pb"); this.rarityBall.setScale(0.75); this.descriptionContainer.add(this.rarityBall); - const dexProgressIndicator = this.scene.add.sprite(12, 10, "encounter_radar"); + const dexProgressIndicator = globalScene.add.sprite(12, 10, "encounter_radar"); dexProgressIndicator.setScale(0.80); this.dexProgressContainer.add(dexProgressIndicator); this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains); @@ -138,8 +138,8 @@ export default class MysteryEncounterUiHandler extends UiHandler { ...this.overrideSettings, slideInDescription: false }; - this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, overrideSettings); + globalScene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => { + globalScene.ui.setMode(Mode.MYSTERY_ENCOUNTER, overrideSettings); setTimeout(() => { this.setCursor(this.viewPartyIndex); this.unblockInput(); @@ -148,7 +148,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { } else if (this.blockInput || (!this.optionsMeetsReqs[cursor] && (selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL))) { success = false; } else { - if ((this.scene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) { + if ((globalScene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) { success = true; } else { ui.playError(); @@ -315,7 +315,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.viewPartyIndex = this.optionsContainer.getAll()?.length - 1; if (!this.cursorObj) { - this.cursorObj = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.image(0, 0, "cursor"); this.cursorContainer.add(this.cursorObj); } @@ -334,13 +334,13 @@ export default class MysteryEncounterUiHandler extends UiHandler { displayEncounterOptions(slideInDescription: boolean = true): void { this.getUi().clearText(); - const mysteryEncounter = this.scene.currentBattle.mysteryEncounter!; + const mysteryEncounter = globalScene.currentBattle.mysteryEncounter!; this.encounterOptions = this.overrideSettings?.overrideOptions ?? mysteryEncounter.options; this.optionsMeetsReqs = []; - const titleText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.title, TextStyle.TOOLTIP_TITLE); - const descriptionText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.description, TextStyle.TOOLTIP_CONTENT); - const queryText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.query, TextStyle.TOOLTIP_CONTENT); + const titleText: string | null = getEncounterText(mysteryEncounter.dialogue.encounterOptionsDialogue?.title, TextStyle.TOOLTIP_TITLE); + const descriptionText: string | null = getEncounterText(mysteryEncounter.dialogue.encounterOptionsDialogue?.description, TextStyle.TOOLTIP_CONTENT); + const queryText: string | null = getEncounterText(mysteryEncounter.dialogue.encounterOptionsDialogue?.query, TextStyle.TOOLTIP_CONTENT); // Clear options container (except cursor) this.optionsContainer.removeAll(true); @@ -353,25 +353,25 @@ export default class MysteryEncounterUiHandler extends UiHandler { switch (this.encounterOptions.length) { default: case 2: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); break; case 3: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); break; case 4: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); break; } - this.optionsMeetsReqs.push(option.meetsRequirements(this.scene)); + this.optionsMeetsReqs.push(option.meetsRequirements()); const optionDialogue = option.dialogue!; const label = !this.optionsMeetsReqs[i] && optionDialogue.disabledButtonLabel ? optionDialogue.disabledButtonLabel : optionDialogue.buttonLabel; let text: string | null; if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) { // Options with special requirements that are met are automatically colored green - text = getEncounterText(this.scene, label, TextStyle.ME_OPTION_SPECIAL); + text = getEncounterText(label, TextStyle.ME_OPTION_SPECIAL); } else { - text = getEncounterText(this.scene, label, optionDialogue.style ? optionDialogue.style : TextStyle.ME_OPTION_DEFAULT); + text = getEncounterText(label, optionDialogue.style ? optionDialogue.style : TextStyle.ME_OPTION_DEFAULT); } if (text) { @@ -387,7 +387,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { // Sets up the mask that hides the option text to give an illusion of scrolling const nonScrollWidth = 90; - const optionTextMaskRect = this.scene.make.graphics({}); + const optionTextMaskRect = globalScene.make.graphics({}); optionTextMaskRect.setScale(6); optionTextMaskRect.fillStyle(0xFFFFFF); optionTextMaskRect.beginPath(); @@ -406,7 +406,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { // Animates the option text scrolling sideways if (optionTextWidth > nonScrollWidth) { - this.optionScrollTweens[i] = this.scene.tweens.add({ + this.optionScrollTweens[i] = globalScene.tweens.add({ targets: optionText, delay: Utils.fixedInt(2000), loop: -1, @@ -420,13 +420,13 @@ export default class MysteryEncounterUiHandler extends UiHandler { } // View Party Button - const viewPartyText = addBBCodeTextObject(this.scene, (this.scene.game.canvas.width) / 6, -24, getBBCodeFrag(i18next.t("mysteryEncounterMessages:view_party_button"), TextStyle.PARTY), TextStyle.PARTY); + const viewPartyText = addBBCodeTextObject((globalScene.game.canvas.width) / 6, -24, getBBCodeFrag(i18next.t("mysteryEncounterMessages:view_party_button"), TextStyle.PARTY), TextStyle.PARTY); this.optionsContainer.add(viewPartyText); viewPartyText.x -= (viewPartyText.displayWidth + 16); this.viewPartyXPosition = viewPartyText.x - 10; // Description Window - const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText ?? "", TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 }); + const titleTextObject = addBBCodeTextObject(0, 0, titleText ?? "", TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 }); this.descriptionContainer.add(titleTextObject); titleTextObject.setPosition(72 - titleTextObject.displayWidth / 2, 5.5); @@ -438,10 +438,10 @@ export default class MysteryEncounterUiHandler extends UiHandler { const ballType = getPokeballAtlasKey(index); this.rarityBall.setTexture("pb", ballType); - const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 }}); + const descriptionTextObject = addBBCodeTextObject(6, 25, descriptionText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 }}); // Sets up the mask that hides the description text to give an illusion of scrolling - const descriptionTextMaskRect = this.scene.make.graphics({}); + const descriptionTextMaskRect = globalScene.make.graphics({}); descriptionTextMaskRect.setScale(6); descriptionTextMaskRect.fillStyle(0xFFFFFF); descriptionTextMaskRect.beginPath(); @@ -460,7 +460,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { // Animates the description text moving upwards if (descriptionLineCount > 6) { - this.descriptionScrollTween = this.scene.tweens.add({ + this.descriptionScrollTween = globalScene.tweens.add({ targets: descriptionTextObject, delay: Utils.fixedInt(2000), loop: -1, @@ -472,14 +472,14 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.descriptionContainer.add(descriptionTextObject); - const queryTextObject = addBBCodeTextObject(this.scene, 0, 0, queryText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 }}); + const queryTextObject = addBBCodeTextObject(0, 0, queryText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 }}); this.descriptionContainer.add(queryTextObject); queryTextObject.setPosition(75 - queryTextObject.displayWidth / 2, 90); // Slide in description container if (slideInDescription) { this.descriptionContainer.x -= 150; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.descriptionContainer, x: "+=150", ease: "Sine.easeInOut", @@ -511,9 +511,9 @@ export default class MysteryEncounterUiHandler extends UiHandler { const cursorOption = this.encounterOptions[cursor]; const optionDialogue = cursorOption.dialogue!; if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) { - text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT); + text = getEncounterText(optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT); } else { - text = getEncounterText(this.scene, optionDialogue.buttonTooltip, TextStyle.TOOLTIP_CONTENT); + text = getEncounterText(optionDialogue.buttonTooltip, TextStyle.TOOLTIP_CONTENT); } // Auto-color options green/blue for good/bad by looking for (+)/(-) @@ -524,11 +524,11 @@ export default class MysteryEncounterUiHandler extends UiHandler { } if (text) { - const tooltipTextObject = addBBCodeTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" }); + const tooltipTextObject = addBBCodeTextObject(6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" }); this.tooltipContainer.add(tooltipTextObject); // Sets up the mask that hides the description text to give an illusion of scrolling - const tooltipTextMaskRect = this.scene.make.graphics({}); + const tooltipTextMaskRect = globalScene.make.graphics({}); tooltipTextMaskRect.setScale(6); tooltipTextMaskRect.fillStyle(0xFFFFFF); tooltipTextMaskRect.beginPath(); @@ -546,7 +546,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { // Animates the tooltip text moving upwards if (tooltipLineCount > 3) { - this.tooltipScrollTween = this.scene.tweens.add({ + this.tooltipScrollTween = globalScene.tweens.add({ targets: tooltipTextObject, delay: Utils.fixedInt(1200), loop: -1, @@ -593,25 +593,25 @@ export default class MysteryEncounterUiHandler extends UiHandler { private showHideDexProgress(show: boolean) { if (show && !this.showDexProgress) { this.showDexProgress = true; - this.scene.tweens.killTweensOf(this.dexProgressContainer); - this.scene.tweens.add({ + globalScene.tweens.killTweensOf(this.dexProgressContainer); + globalScene.tweens.add({ targets: this.dexProgressContainer, y: -63, ease: "Sine.easeInOut", duration: 750, onComplete: () => { this.dexProgressContainer.on("pointerover", () => { - (this.scene as BattleScene).ui.showTooltip("", i18next.t("mysteryEncounterMessages:affects_pokedex"), true); + globalScene.ui.showTooltip("", i18next.t("mysteryEncounterMessages:affects_pokedex"), true); }); this.dexProgressContainer.on("pointerout", () => { - (this.scene as BattleScene).ui.hideTooltip(); + globalScene.ui.hideTooltip(); }); } }); } else if (!show && this.showDexProgress) { this.showDexProgress = false; - this.scene.tweens.killTweensOf(this.dexProgressContainer); - this.scene.tweens.add({ + globalScene.tweens.killTweensOf(this.dexProgressContainer); + globalScene.tweens.add({ targets: this.dexProgressContainer, y: -43, ease: "Sine.easeInOut", diff --git a/src/ui/party-exp-bar.ts b/src/ui/party-exp-bar.ts index d2521225375..93e4117157a 100644 --- a/src/ui/party-exp-bar.ts +++ b/src/ui/party-exp-bar.ts @@ -1,5 +1,5 @@ -import BattleScene from "../battle-scene"; -import Pokemon from "../field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "../field/pokemon"; import { TextStyle, addTextObject } from "./text"; export default class PartyExpBar extends Phaser.GameObjects.Container { @@ -11,17 +11,17 @@ export default class PartyExpBar extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene) { - super(scene, (scene.game.canvas.width / 6), -((scene.game.canvas.height) / 6) + 15); + constructor() { + super(globalScene, (globalScene.game.canvas.width / 6), -((globalScene.game.canvas.height) / 6) + 15); } setup(): void { - this.bg = this.scene.add.nineslice(0, 0, "party_exp_bar", undefined, 8, 18, 21, 5, 6, 4); + this.bg = globalScene.add.nineslice(0, 0, "party_exp_bar", undefined, 8, 18, 21, 5, 6, 4); this.bg.setOrigin(0, 0); this.add(this.bg); - this.expText = addTextObject(this.scene, 22, 4, "", TextStyle.BATTLE_INFO); + this.expText = addTextObject(22, 4, "", TextStyle.BATTLE_INFO); this.expText.setOrigin(0, 0); this.add(this.expText); @@ -35,7 +35,7 @@ export default class PartyExpBar extends Phaser.GameObjects.Container { return resolve(); } - this.pokemonIcon = (this.scene as BattleScene).addPokemonIcon(pokemon, -8, 15, 0, 0.5); + this.pokemonIcon = globalScene.addPokemonIcon(pokemon, -8, 15, 0, 0.5); this.pokemonIcon.setScale(0.5); this.add(this.pokemonIcon); @@ -54,16 +54,16 @@ export default class PartyExpBar extends Phaser.GameObjects.Container { this.bg.width = this.expText.displayWidth + 28; - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); if (this.tween) { this.tween.stop(); } - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6) - (this.bg.width - 5), - duration: 500 / Math.pow(2, pokemon.scene.expGainsSpeed), + x: (globalScene.game.canvas.width / 6) - (this.bg.width - 5), + duration: 500 / Math.pow(2, globalScene.expGainsSpeed), ease: "Sine.easeOut", onComplete: () => { this.tween = null; @@ -86,9 +86,9 @@ export default class PartyExpBar extends Phaser.GameObjects.Container { this.tween.stop(); } - this.tween = this.scene.tweens.add({ + this.tween = globalScene.tweens.add({ targets: this, - x: (this.scene.game.canvas.width / 6), + x: (globalScene.game.canvas.width / 6), duration: 500, ease: "Sine.easeIn", onComplete: () => { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 0d20753f069..4a7716f7e62 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -1,5 +1,6 @@ -import BattleScene from "#app/battle-scene"; -import Pokemon, { MoveResult, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type Pokemon from "#app/field/pokemon"; +import { MoveResult } from "#app/field/pokemon"; import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "#app/ui/text"; import { Command } from "#app/ui/command-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; @@ -18,12 +19,13 @@ import { Button } from "#enums/buttons"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import i18next from "i18next"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages"; -import { CommandPhase } from "#app/phases/command-phase"; +import type { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { globalScene } from "#app/global-scene"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -197,7 +199,7 @@ export default class PartyUiHandler extends MessageUiHandler { */ private FilterChallengeLegal = (pokemon: PlayerPokemon) => { const challengeAllowed = new Utils.BooleanHolder(true); - applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); + applyChallenges(globalScene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); if (!challengeAllowed.value) { return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon) }); } @@ -207,8 +209,8 @@ export default class PartyUiHandler extends MessageUiHandler { private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { - const matchingModifier = pokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier)) as PokemonHeldItemModifier; - if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount(pokemon.scene)) { + const matchingModifier = globalScene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier)) as PokemonHeldItemModifier; + if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) { return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon) }); } return null; @@ -218,44 +220,44 @@ export default class PartyUiHandler extends MessageUiHandler { private localizedOptions = [ PartyOption.SEND_OUT, PartyOption.SUMMARY, PartyOption.CANCEL, PartyOption.APPLY, PartyOption.RELEASE, PartyOption.TEACH, PartyOption.SPLICE, PartyOption.UNSPLICE, PartyOption.REVIVE, PartyOption.TRANSFER, PartyOption.UNPAUSE_EVOLUTION, PartyOption.PASS_BATON, PartyOption.RENAME, PartyOption.SELECT ]; - constructor(scene: BattleScene) { - super(scene, Mode.PARTY); + constructor() { + super(Mode.PARTY); } setup() { const ui = this.getUi(); - const partyContainer = this.scene.add.container(0, 0); + const partyContainer = globalScene.add.container(0, 0); partyContainer.setName("party"); partyContainer.setVisible(false); ui.add(partyContainer); this.partyContainer = partyContainer; - this.partyBg = this.scene.add.image(0, 0, "party_bg"); + this.partyBg = globalScene.add.image(0, 0, "party_bg"); this.partyBg.setName("img-party-bg"); partyContainer.add(this.partyBg); this.partyBg.setOrigin(0, 1); - const partySlotsContainer = this.scene.add.container(0, 0); + const partySlotsContainer = globalScene.add.container(0, 0); partySlotsContainer.setName("party-slots"); partyContainer.add(partySlotsContainer); this.partySlotsContainer = partySlotsContainer; - const partyMessageBoxContainer = this.scene.add.container(0, -32); + const partyMessageBoxContainer = globalScene.add.container(0, -32); partyMessageBoxContainer.setName("party-msg-box"); partyContainer.add(partyMessageBoxContainer); - const partyMessageBox = addWindow(this.scene, 1, 31, 262, 30); + const partyMessageBox = addWindow(1, 31, 262, 30); partyMessageBox.setName("window-party-msg-box"); partyMessageBox.setOrigin(0, 1); partyMessageBoxContainer.add(partyMessageBox); this.partyMessageBox = partyMessageBox; - const partyMessageText = addTextObject(this.scene, 10, 8, defaultMessage, TextStyle.WINDOW, { maxLines: 2 }); + const partyMessageText = addTextObject(10, 8, defaultMessage, TextStyle.WINDOW, { maxLines: 2 }); partyMessageText.setName("text-party-msg"); partyMessageText.setOrigin(0, 0); @@ -263,25 +265,25 @@ export default class PartyUiHandler extends MessageUiHandler { this.message = partyMessageText; - const partyCancelButton = new PartyCancelButton(this.scene, 291, -16); + const partyCancelButton = new PartyCancelButton(291, -16); partyContainer.add(partyCancelButton); this.partyCancelButton = partyCancelButton; - this.optionsContainer = this.scene.add.container((this.scene.game.canvas.width / 6) - 1, -1); + this.optionsContainer = globalScene.add.container((globalScene.game.canvas.width / 6) - 1, -1); partyContainer.add(this.optionsContainer); this.iconAnimHandler = new PokemonIconAnimHandler(); - this.iconAnimHandler.setup(this.scene); + this.iconAnimHandler.setup(); // prepare move overlay. in case it appears to be too big, set the overlayScale to .5 const overlayScale = 1; - this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + this.moveInfoOverlay = new MoveInfoOverlay({ scale: overlayScale, top: true, x: 1, - y: -MoveInfoOverlay.getHeight(overlayScale) - 1, //this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, - width: this.scene.game.canvas.width / 12 - 30, + y: -MoveInfoOverlay.getHeight(overlayScale) - 1, + width: globalScene.game.canvas.width / 12 - 30, }); ui.add(this.moveInfoOverlay); @@ -315,7 +317,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.showMovePp = args.length > 6 && args[6]; this.partyContainer.setVisible(true); - this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? "_double" : ""}`); + this.partyBg.setTexture(`party_bg${globalScene.currentBattle.double ? "_double" : ""}`); this.populatePartySlots(); this.setCursor(0); @@ -346,22 +348,22 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.optionsMode) { const option = this.options[this.optionsCursor]; if (button === Button.ACTION) { - const pokemon = this.scene.getPlayerParty()[this.cursor]; + const pokemon = globalScene.getPlayerParty()[this.cursor]; if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { this.startTransfer(); let ableToTransfer: string; - for (let p = 0; p < this.scene.getPlayerParty().length; p++) { // this fore look goes through each of the party pokemon - const newPokemon = this.scene.getPlayerParty()[p]; + for (let p = 0; p < globalScene.getPlayerParty().length; p++) { // this for look goes through each of the party pokemon + const newPokemon = globalScene.getPlayerParty()[p]; // this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p] const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) => - this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[]; + globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[]; // this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; this returns undefined if the new pokemon doesn't have the item at all, otherwise it returns the pokemonHeldItemModifier for that item - const matchingModifier = newPokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor])) as PokemonHeldItemModifier; + const matchingModifier = globalScene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor])) as PokemonHeldItemModifier; const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer if (matchingModifier) { // if matchingModifier exists then the item exists on the new pokemon - if (matchingModifier.getMaxStackCount(this.scene) === matchingModifier.stackCount) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" + if (matchingModifier.getMaxStackCount() === matchingModifier.stackCount) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able" ableToTransfer = i18next.t("partyUiHandler:notAble"); } else { // if the pokemon isn't at max stack, make the label "Able" ableToTransfer = i18next.t("partyUiHandler:able"); @@ -399,7 +401,7 @@ export default class PartyUiHandler extends MessageUiHandler { || (option === PartyOption.RELEASE && this.partyUiMode === PartyUiMode.RELEASE)) { let filterResult: string | null; const getTransferrableItemsFromPokemon = (pokemon: PlayerPokemon) => - this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[]; + globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[]; if (option !== PartyOption.TRANSFER && option !== PartyOption.SPLICE) { filterResult = (this.selectFilter as PokemonSelectFilter)(pokemon); if (filterResult === null && (option === PartyOption.SEND_OUT || option === PartyOption.PASS_BATON)) { @@ -409,7 +411,7 @@ export default class PartyUiHandler extends MessageUiHandler { filterResult = this.moveSelectFilter(pokemon.moveset[this.optionsCursor]!); // TODO: is this bang correct? } } else { - filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)(pokemon, getTransferrableItemsFromPokemon(this.scene.getPlayerParty()[this.transferCursor])[this.transferOptionCursor]); + filterResult = (this.selectFilter as PokemonModifierTransferSelectFilter)(pokemon, getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor])[this.transferOptionCursor]); } if (filterResult === null) { if (this.partyUiMode !== PartyUiMode.SPLICE) { @@ -419,7 +421,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (option === PartyOption.TRANSFER) { if (this.transferCursor !== this.cursor) { if (this.transferAll) { - getTransferrableItemsFromPokemon(this.scene.getPlayerParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor)); + getTransferrableItemsFromPokemon(globalScene.getPlayerParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor)); } else { (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor); } @@ -441,15 +443,15 @@ export default class PartyUiHandler extends MessageUiHandler { selectCallback(this.cursor, option); } } else { - if (option >= PartyOption.FORM_CHANGE_ITEM && this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + if (option >= PartyOption.FORM_CHANGE_ITEM && globalScene.getCurrentPhase() instanceof SelectModifierPhase) { if (this.partyUiMode === PartyUiMode.CHECK) { const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; modifier.active = !modifier.active; - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeItemTrigger, false, true); } } else if (this.cursor) { - (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, this.cursor, option === PartyOption.PASS_BATON); + (globalScene.getCurrentPhase() as CommandPhase).handleCommand(Command.POKEMON, this.cursor, option === PartyOption.PASS_BATON); } } if (this.partyUiMode !== PartyUiMode.MODIFIER && this.partyUiMode !== PartyUiMode.TM_MODIFIER && this.partyUiMode !== PartyUiMode.MOVE_MODIFIER) { @@ -492,7 +494,7 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (option === PartyOption.RELEASE) { this.clearOptions(); ui.playSelect(); - if (this.cursor >= this.scene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { + if (this.cursor >= globalScene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { this.blockInput = true; this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => { this.blockInput = false; @@ -580,7 +582,7 @@ export default class PartyUiHandler extends MessageUiHandler { // show move description if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { const option = this.options[this.optionsCursor]; - const pokemon = this.scene.getPlayerParty()[this.cursor]; + const pokemon = globalScene.getPlayerParty()[this.cursor]; const move = allMoves[pokemon.getLearnableLevelMoves()[option]]; if (move) { this.moveInfoOverlay.show(move); @@ -595,8 +597,8 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.cursor < 6) { if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode) { /** Initialize item quantities for the selected Pokemon */ - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && m.isTransferable && m.pokemonId === this.scene.getPlayerParty()[this.cursor].id) as PokemonHeldItemModifier[]; + const itemModifiers = globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier + && m.isTransferable && m.pokemonId === globalScene.getPlayerParty()[this.cursor].id) as PokemonHeldItemModifier[]; this.transferQuantities = itemModifiers.map(item => item.getStackCount()); this.transferQuantitiesMax = itemModifiers.map(item => item.getStackCount()); } @@ -628,7 +630,7 @@ export default class PartyUiHandler extends MessageUiHandler { } const slotCount = this.partySlots.length; - const battlerCount = this.scene.currentBattle.getBattlerCount(); + const battlerCount = globalScene.currentBattle.getBattlerCount(); switch (button) { case Button.UP: @@ -664,7 +666,7 @@ export default class PartyUiHandler extends MessageUiHandler { } populatePartySlots() { - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); if (this.cursor < 6 && this.cursor >= party.length) { this.cursor = party.length - 1; @@ -677,8 +679,8 @@ export default class PartyUiHandler extends MessageUiHandler { for (const p in party) { const slotIndex = parseInt(p); - const partySlot = new PartySlot(this.scene, slotIndex, party[p], this.iconAnimHandler, this.partyUiMode, this.tmMoveId); - this.scene.add.existing(partySlot); + const partySlot = new PartySlot(slotIndex, party[p], this.iconAnimHandler, this.partyUiMode, this.tmMoveId); + globalScene.add.existing(partySlot); this.partySlotsContainer.add(partySlot); this.partySlots.push(partySlot); if (this.cursor === slotIndex) { @@ -721,7 +723,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.optionsCursor = cursor; } if (!this.optionsCursorObj) { - this.optionsCursorObj = this.scene.add.image(0, 0, "cursor"); + this.optionsCursorObj = globalScene.add.image(0, 0, "cursor"); this.optionsCursorObj.setOrigin(0, 0); this.optionsContainer.add(this.optionsCursorObj); } @@ -803,7 +805,7 @@ export default class PartyUiHandler extends MessageUiHandler { } updateOptions(): void { - const pokemon = this.scene.getPlayerParty()[this.cursor]; + const pokemon = globalScene.getPlayerParty()[this.cursor]; const learnableLevelMoves = this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER ? pokemon.getLearnableLevelMoves() @@ -815,7 +817,7 @@ export default class PartyUiHandler extends MessageUiHandler { } const itemModifiers = this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER - ? this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + ? globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === pokemon.id) as PokemonHeldItemModifier[] : []; @@ -832,13 +834,13 @@ export default class PartyUiHandler extends MessageUiHandler { case PartyUiMode.SWITCH: case PartyUiMode.FAINT_SWITCH: case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { + if (this.cursor >= globalScene.currentBattle.getBattlerCount()) { const allowBatonModifierSwitch = this.partyUiMode !== PartyUiMode.FAINT_SWITCH - && this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier - && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id); + && globalScene.findModifier(m => m instanceof SwitchEffectTransferModifier + && (m as SwitchEffectTransferModifier).pokemonId === globalScene.getPlayerField()[this.fieldIndex].id); - const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); + const moveHistory = globalScene.getPlayerField()[this.fieldIndex].getMoveHistory(); const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true @@ -877,7 +879,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.options.push(PartyOption.RELEASE); break; case PartyUiMode.CHECK: - if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + if (globalScene.getCurrentPhase() instanceof SelectModifierPhase) { formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); for (let i = 0; i < formChangeItemModifiers.length; i++) { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); @@ -941,7 +943,7 @@ export default class PartyUiHandler extends MessageUiHandler { this.options.push(PartyOption.CANCEL); - this.optionsBg = addWindow(this.scene, 0, 0, 0, 16 * this.options.length + 13); + this.optionsBg = addWindow(0, 0, 0, 16 * this.options.length + 13); this.optionsBg.setOrigin(1, 1); this.optionsContainer.add(this.optionsBg); @@ -1002,7 +1004,7 @@ export default class PartyUiHandler extends MessageUiHandler { } const yCoord = -6 - 16 * o; - const optionText = addBBCodeTextObject(this.scene, 0, yCoord - 16, optionName, TextStyle.WINDOW, { maxLines: 1 }); + const optionText = addBBCodeTextObject(0, yCoord - 16, optionName, TextStyle.WINDOW, { maxLines: 1 }); if (altText) { optionText.setColor("#40c8f8"); optionText.setShadowColor("#006090"); @@ -1059,13 +1061,13 @@ export default class PartyUiHandler extends MessageUiHandler { } doRelease(slotIndex: integer): void { - this.showText(this.getReleaseMessage(getPokemonNameWithAffix(this.scene.getPlayerParty()[slotIndex])), null, () => { + this.showText(this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex])), null, () => { this.clearPartySlots(); - this.scene.removePartyMemberModifiers(slotIndex); - const releasedPokemon = this.scene.getPlayerParty().splice(slotIndex, 1)[0]; + globalScene.removePartyMemberModifiers(slotIndex); + const releasedPokemon = globalScene.getPlayerParty().splice(slotIndex, 1)[0]; releasedPokemon.destroy(); this.populatePartySlots(); - if (this.cursor >= this.scene.getPlayerParty().length) { + if (this.cursor >= globalScene.getPlayerParty().length) { this.setCursor(this.cursor - 1); } if (this.partyUiMode === PartyUiMode.RELEASE) { @@ -1103,7 +1105,7 @@ export default class PartyUiHandler extends MessageUiHandler { } getFormChangeItemsModifiers(pokemon: Pokemon) { - let formChangeItemModifiers = this.scene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]; + let formChangeItemModifiers = globalScene.findModifiers(m => m instanceof PokemonFormChangeItemModifier && m.pokemonId === pokemon.id) as PokemonFormChangeItemModifier[]; const ultraNecrozmaModifiers = formChangeItemModifiers.filter(m => m.active && m.formChangeItem === FormChangeItem.ULTRANECROZIUM_Z); if (ultraNecrozmaModifiers.length > 0) { // ULTRANECROZIUM_Z is active and deactivating it should be the only option @@ -1177,10 +1179,10 @@ class PartySlot extends Phaser.GameObjects.Container { private pokemonIcon: Phaser.GameObjects.Container; private iconAnimHandler: PokemonIconAnimHandler; - constructor(scene: BattleScene, slotIndex: integer, pokemon: PlayerPokemon, iconAnimHandler: PokemonIconAnimHandler, partyUiMode: PartyUiMode, tmMoveId: Moves) { - super(scene, slotIndex >= scene.currentBattle.getBattlerCount() ? 230.5 : 64, - slotIndex >= scene.currentBattle.getBattlerCount() ? -184 + (scene.currentBattle.double ? -40 : 0) - + (28 + (scene.currentBattle.double ? 8 : 0)) * slotIndex : -124 + (scene.currentBattle.double ? -8 : 0) + slotIndex * 64); + constructor(slotIndex: integer, pokemon: PlayerPokemon, iconAnimHandler: PokemonIconAnimHandler, partyUiMode: PartyUiMode, tmMoveId: Moves) { + super(globalScene, slotIndex >= globalScene.currentBattle.getBattlerCount() ? 230.5 : 64, + slotIndex >= globalScene.currentBattle.getBattlerCount() ? -184 + (globalScene.currentBattle.double ? -40 : 0) + + (28 + (globalScene.currentBattle.double ? 8 : 0)) * slotIndex : -124 + (globalScene.currentBattle.double ? -8 : 0) + slotIndex * 64); this.slotIndex = slotIndex; this.pokemon = pokemon; @@ -1194,33 +1196,33 @@ class PartySlot extends Phaser.GameObjects.Container { } setup(partyUiMode: PartyUiMode, tmMoveId: Moves) { - const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount(); + const battlerCount = globalScene.currentBattle.getBattlerCount(); const slotKey = `party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`; - const slotBg = this.scene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? "" : "_fnt"}`); + const slotBg = globalScene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? "" : "_fnt"}`); this.slotBg = slotBg; this.add(slotBg); - const slotPb = this.scene.add.sprite(this.slotIndex >= battlerCount ? -85.5 : -51, this.slotIndex >= battlerCount ? 0 : -20.5, "party_pb"); + const slotPb = globalScene.add.sprite(this.slotIndex >= battlerCount ? -85.5 : -51, this.slotIndex >= battlerCount ? 0 : -20.5, "party_pb"); this.slotPb = slotPb; this.add(slotPb); - this.pokemonIcon = (this.scene as BattleScene).addPokemonIcon(this.pokemon, slotPb.x, slotPb.y, 0.5, 0.5, true); + this.pokemonIcon = globalScene.addPokemonIcon(this.pokemon, slotPb.x, slotPb.y, 0.5, 0.5, true); this.add(this.pokemonIcon); this.iconAnimHandler.addOrUpdate(this.pokemonIcon, PokemonIconAnimMode.PASSIVE); - const slotInfoContainer = this.scene.add.container(0, 0); + const slotInfoContainer = globalScene.add.container(0, 0); this.add(slotInfoContainer); let displayName = this.pokemon.getNameToRender(); let nameTextWidth: number; - const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY); + const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.PARTY); nameTextWidth = nameSizeTest.displayWidth; while (nameTextWidth > (this.slotIndex >= battlerCount ? 52 : (76 - (this.pokemon.fusionSpecies ? 8 : 0)))) { @@ -1231,15 +1233,15 @@ class PartySlot extends Phaser.GameObjects.Container { nameSizeTest.destroy(); - this.slotName = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY); + this.slotName = addTextObject(0, 0, displayName, TextStyle.PARTY); this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10); this.slotName.setOrigin(0, 0); - const slotLevelLabel = this.scene.add.image(0, 0, "party_slot_overlay_lv"); + const slotLevelLabel = globalScene.add.image(0, 0, "party_slot_overlay_lv"); slotLevelLabel.setPositionRelative(this.slotName, 8, 12); slotLevelLabel.setOrigin(0, 0); - const slotLevelText = addTextObject(this.scene, 0, 0, this.pokemon.level.toString(), this.pokemon.level < (this.scene as BattleScene).getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED); + const slotLevelText = addTextObject(0, 0, this.pokemon.level.toString(), this.pokemon.level < globalScene.getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED); slotLevelText.setPositionRelative(slotLevelLabel, 9, 0); slotLevelText.setOrigin(0, 0.25); @@ -1248,7 +1250,7 @@ class PartySlot extends Phaser.GameObjects.Container { const genderSymbol = getGenderSymbol(this.pokemon.getGender(true)); if (genderSymbol) { - const slotGenderText = addTextObject(this.scene, 0, 0, genderSymbol, TextStyle.PARTY); + const slotGenderText = addTextObject(0, 0, genderSymbol, TextStyle.PARTY); slotGenderText.setColor(getGenderColor(this.pokemon.getGender(true))); slotGenderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true)); if (this.slotIndex >= battlerCount) { @@ -1262,7 +1264,7 @@ class PartySlot extends Phaser.GameObjects.Container { } if (this.pokemon.fusionSpecies) { - const splicedIcon = this.scene.add.image(0, 0, "icon_spliced"); + const splicedIcon = globalScene.add.image(0, 0, "icon_spliced"); splicedIcon.setScale(0.5); splicedIcon.setOrigin(0, 0); if (this.slotIndex >= battlerCount) { @@ -1275,7 +1277,7 @@ class PartySlot extends Phaser.GameObjects.Container { } if (this.pokemon.status) { - const statusIndicator = this.scene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses")); + const statusIndicator = globalScene.add.sprite(0, 0, Utils.getLocalizedSpriteKey("statuses")); statusIndicator.setFrame(StatusEffect[this.pokemon.status?.effect].toLowerCase()); statusIndicator.setOrigin(0, 0); statusIndicator.setPositionRelative(slotLevelLabel, this.slotIndex >= battlerCount ? 43 : 55, 0); @@ -1286,7 +1288,7 @@ class PartySlot extends Phaser.GameObjects.Container { if (this.pokemon.isShiny()) { const doubleShiny = this.pokemon.isFusion() && this.pokemon.shiny && this.pokemon.fusionShiny; - const shinyStar = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); + const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); shinyStar.setOrigin(0, 0); shinyStar.setPositionRelative(this.slotName, -9, 3); shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant)); @@ -1294,7 +1296,7 @@ class PartySlot extends Phaser.GameObjects.Container { slotInfoContainer.add(shinyStar); if (doubleShiny) { - const fusionShinyStar = this.scene.add.image(0, 0, "shiny_star_small_2"); + const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2"); fusionShinyStar.setOrigin(0, 0); fusionShinyStar.setPosition(shinyStar.x, shinyStar.y); fusionShinyStar.setTint(getVariantTint(this.pokemon.fusionVariant)); @@ -1303,25 +1305,25 @@ class PartySlot extends Phaser.GameObjects.Container { } } - this.slotHpBar = this.scene.add.image(0, 0, "party_slot_hp_bar"); + this.slotHpBar = globalScene.add.image(0, 0, "party_slot_hp_bar"); this.slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 6 : 31); this.slotHpBar.setOrigin(0, 0); this.slotHpBar.setVisible(false); const hpRatio = this.pokemon.getHpRatio(); - this.slotHpOverlay = this.scene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low"); + this.slotHpOverlay = globalScene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low"); this.slotHpOverlay.setPositionRelative(this.slotHpBar, 16, 2); this.slotHpOverlay.setOrigin(0, 0); this.slotHpOverlay.setScale(hpRatio, 1); this.slotHpOverlay.setVisible(false); - this.slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY); + this.slotHpText = addTextObject(0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY); this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2); this.slotHpText.setOrigin(1, 0); this.slotHpText.setVisible(false); - this.slotDescriptionLabel = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE); + this.slotDescriptionLabel = addTextObject(0, 0, "", TextStyle.MESSAGE); this.slotDescriptionLabel.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 94 : 32, this.slotIndex >= battlerCount ? 16 : 46); this.slotDescriptionLabel.setOrigin(0, 1); this.slotDescriptionLabel.setVisible(false); @@ -1387,7 +1389,7 @@ class PartySlot extends Phaser.GameObjects.Container { } private updateSlotTexture(): void { - const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount(); + const battlerCount = globalScene.currentBattle.getBattlerCount(); this.slotBg.setTexture(`party_slot${this.slotIndex >= battlerCount ? "" : "_main"}`, `party_slot${this.slotIndex >= battlerCount ? "" : "_main"}${this.transfer ? "_swap" : this.pokemon.hp ? "" : "_fnt"}${this.selected ? "_sel" : ""}`); } @@ -1399,24 +1401,24 @@ class PartyCancelButton extends Phaser.GameObjects.Container { private partyCancelBg: Phaser.GameObjects.Sprite; private partyCancelPb: Phaser.GameObjects.Sprite; - constructor(scene: BattleScene, x: number, y: number) { - super(scene, x, y); + constructor(x: number, y: number) { + super(globalScene, x, y); this.setup(); } setup() { - const partyCancelBg = this.scene.add.sprite(0, 0, "party_cancel"); + const partyCancelBg = globalScene.add.sprite(0, 0, "party_cancel"); this.add(partyCancelBg); this.partyCancelBg = partyCancelBg; - const partyCancelPb = this.scene.add.sprite(-17, 0, "party_pb"); + const partyCancelPb = globalScene.add.sprite(-17, 0, "party_pb"); this.add(partyCancelPb); this.partyCancelPb = partyCancelPb; - const partyCancelText = addTextObject(this.scene, -8, -7, i18next.t("partyUiHandler:cancel"), TextStyle.PARTY); + const partyCancelText = addTextObject(-8, -7, i18next.t("partyUiHandler:cancel"), TextStyle.PARTY); this.add(partyCancelText); } diff --git a/src/ui/pokeball-tray.ts b/src/ui/pokeball-tray.ts index 0313812ef79..0c913d195a9 100644 --- a/src/ui/pokeball-tray.ts +++ b/src/ui/pokeball-tray.ts @@ -1,5 +1,5 @@ -import BattleScene from "../battle-scene"; -import Pokemon from "../field/pokemon"; +import { globalScene } from "#app/global-scene"; +import type Pokemon from "../field/pokemon"; export default class PokeballTray extends Phaser.GameObjects.Container { private player: boolean; @@ -9,18 +9,18 @@ export default class PokeballTray extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene, player: boolean) { - super(scene, player ? (scene.game.canvas.width / 6) : 0, player ? -72 : -144); + constructor(player: boolean) { + super(globalScene, player ? (globalScene.game.canvas.width / 6) : 0, player ? -72 : -144); this.player = player; } setup(): void { - this.bg = this.scene.add.nineslice(0, 0, `pb_tray_overlay_${this.player ? "player" : "enemy"}`, undefined, 104, 4, 48, 8, 0, 0); + this.bg = globalScene.add.nineslice(0, 0, `pb_tray_overlay_${this.player ? "player" : "enemy"}`, undefined, 104, 4, 48, 8, 0, 0); this.bg.setOrigin(this.player ? 1 : 0, 0); this.add(this.bg); - this.balls = new Array(6).fill(null).map((_, i) => this.scene.add.sprite((this.player ? -83 : 76) + (this.scene.game.canvas.width / 6) * (this.player ? -1 : 1) + 10 * i * (this.player ? 1 : -1), -8, "pb_tray_ball", "empty")); + this.balls = new Array(6).fill(null).map((_, i) => globalScene.add.sprite((this.player ? -83 : 76) + (globalScene.game.canvas.width / 6) * (this.player ? -1 : 1) + 10 * i * (this.player ? 1 : -1), -8, "pb_tray_ball", "empty")); for (const ball of this.balls) { ball.setOrigin(0, 0); @@ -37,7 +37,7 @@ export default class PokeballTray extends Phaser.GameObjects.Container { return resolve(); } - (this.scene as BattleScene).fieldUI.bringToTop(this); + globalScene.fieldUI.bringToTop(this); this.x += 104 * (this.player ? 1 : -1); @@ -45,7 +45,7 @@ export default class PokeballTray extends Phaser.GameObjects.Container { this.bg.alpha = 1; this.balls.forEach((ball, b) => { - ball.x += (this.scene.game.canvas.width / 6 + 104) * (this.player ? 1 : -1); + ball.x += (globalScene.game.canvas.width / 6 + 104) * (this.player ? 1 : -1); let ballFrame = "ball"; if (b >= party.length) { ballFrame = "empty"; @@ -57,21 +57,21 @@ export default class PokeballTray extends Phaser.GameObjects.Container { ball.setFrame(ballFrame); }); - (this.scene as BattleScene).playSound("se/pb_tray_enter"); + globalScene.playSound("se/pb_tray_enter"); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, x: `${this.player ? "-" : "+"}=104`, duration: 500, ease: "Sine.easeIn", onComplete: () => { this.balls.forEach((ball, b) => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: ball, x: `${this.player ? "-" : "+"}=104`, duration: b * 100, ease: "Sine.easeIn", - onComplete: () => (this.scene as BattleScene).playSound(`se/${(b < party.length ? "pb_tray_ball" : "pb_tray_empty")}`) + onComplete: () => globalScene.playSound(`se/${(b < party.length ? "pb_tray_ball" : "pb_tray_empty")}`) }); }); } @@ -80,7 +80,7 @@ export default class PokeballTray extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; - this.scene.time.delayedCall(1100, () => resolve()); + globalScene.time.delayedCall(1100, () => resolve()); }); } @@ -91,16 +91,16 @@ export default class PokeballTray extends Phaser.GameObjects.Container { } this.balls.forEach((ball, b) => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: ball, - x: `${this.player ? "-" : "+"}=${this.scene.game.canvas.width / 6}`, + x: `${this.player ? "-" : "+"}=${globalScene.game.canvas.width / 6}`, duration: 250, delay: b * 100, ease: "Sine.easeIn" }); }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.bg, width: 144, alpha: 0, @@ -108,7 +108,7 @@ export default class PokeballTray extends Phaser.GameObjects.Container { ease: "Sine.easeIn" }); - this.scene.time.delayedCall(850, () => { + globalScene.time.delayedCall(850, () => { this.setVisible(false); resolve(); }); diff --git a/src/ui/pokemon-hatch-info-container.ts b/src/ui/pokemon-hatch-info-container.ts index 494855d20fa..a9b8e260b34 100644 --- a/src/ui/pokemon-hatch-info-container.ts +++ b/src/ui/pokemon-hatch-info-container.ts @@ -1,5 +1,4 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container"; -import BattleScene from "#app/battle-scene"; import { Gender } from "#app/data/gender"; import { Type } from "#enums/type"; import * as Utils from "#app/utils"; @@ -9,9 +8,10 @@ import { allMoves } from "#app/data/move"; import { Species } from "#enums/species"; import { getEggTierForSpecies } from "#app/data/egg"; import { starterColors } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { argbFromRgba } from "@material/material-color-utilities"; -import { EggHatchData } from "#app/data/egg-hatch-data"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { EggHatchData } from "#app/data/egg-hatch-data"; +import type { PlayerPokemon } from "#app/field/pokemon"; import { getPokemonSpeciesForm } from "#app/data/pokemon-species"; /** @@ -32,8 +32,8 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { private pokemonCandyOverlayIcon: Phaser.GameObjects.Sprite; private pokemonCandyCountText: Phaser.GameObjects.Text; - constructor(scene: BattleScene, listContainer : Phaser.GameObjects.Container, x: number = 115, y: number = 9,) { - super(scene, x, y); + constructor(listContainer : Phaser.GameObjects.Container, x: number = 115, y: number = 9,) { + super(x, y); this.pokemonListContainer = listContainer; } @@ -41,37 +41,37 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { super.setup(); super.changeToEggSummaryLayout(); - this.currentPokemonSprite = this.scene.add.sprite(54, 80, "pkmn__sub"); + this.currentPokemonSprite = globalScene.add.sprite(54, 80, "pkmn__sub"); this.currentPokemonSprite.setScale(0.8); - this.currentPokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + this.currentPokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.pokemonListContainer.add(this.currentPokemonSprite); // setup name and number - this.pokemonNumberText = addTextObject(this.scene, 80, 107.5, "0000", TextStyle.SUMMARY, { fontSize: 74 }); + this.pokemonNumberText = addTextObject(80, 107.5, "0000", TextStyle.SUMMARY, { fontSize: 74 }); this.pokemonNumberText.setOrigin(0, 0); this.pokemonListContainer.add(this.pokemonNumberText); - this.pokemonNameText = addTextObject(this.scene, 7, 107.5, "", TextStyle.SUMMARY, { fontSize: 74 }); + this.pokemonNameText = addTextObject(7, 107.5, "", TextStyle.SUMMARY, { fontSize: 74 }); this.pokemonNameText.setOrigin(0, 0); this.pokemonListContainer.add(this.pokemonNameText); // setup egg icon and candy count - this.pokemonHatchedIcon = this.scene.add.sprite(-5, 90, "egg_icons"); + this.pokemonHatchedIcon = globalScene.add.sprite(-5, 90, "egg_icons"); this.pokemonHatchedIcon.setOrigin(0, 0.2); this.pokemonHatchedIcon.setScale(0.8); this.pokemonListContainer.add(this.pokemonHatchedIcon); - this.pokemonCandyIcon = this.scene.add.sprite(4.5, 40, "candy"); + this.pokemonCandyIcon = globalScene.add.sprite(4.5, 40, "candy"); this.pokemonCandyIcon.setScale(0.5); this.pokemonCandyIcon.setOrigin(0, 0); this.pokemonListContainer.add(this.pokemonCandyIcon); - this.pokemonCandyOverlayIcon = this.scene.add.sprite(4.5, 40, "candy_overlay"); + this.pokemonCandyOverlayIcon = globalScene.add.sprite(4.5, 40, "candy_overlay"); this.pokemonCandyOverlayIcon.setScale(0.5); this.pokemonCandyOverlayIcon.setOrigin(0, 0); this.pokemonListContainer.add(this.pokemonCandyOverlayIcon); - this.pokemonCandyCountText = addTextObject(this.scene, 14, 40, "x0", TextStyle.SUMMARY, { fontSize: "56px" }); + this.pokemonCandyCountText = addTextObject(14, 40, "x0", TextStyle.SUMMARY, { fontSize: "56px" }); this.pokemonCandyCountText.setOrigin(0, 0); this.pokemonListContainer.add(this.pokemonCandyCountText); @@ -79,17 +79,17 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { this.pokemonEggMoveContainers = []; this.pokemonEggMoveBgs = []; this.pokemonEggMoveLabels = []; - this.pokemonEggMovesContainer = this.scene.add.container(0, 200); + this.pokemonEggMovesContainer = globalScene.add.container(0, 200); this.pokemonEggMovesContainer.setVisible(false); this.pokemonEggMovesContainer.setScale(0.5); for (let m = 0; m < 4; m++) { - const eggMoveContainer = this.scene.add.container(0, 0 + 6 * m); + const eggMoveContainer = globalScene.add.container(0, 0 + 6 * m); - const eggMoveBg = this.scene.add.nineslice(70, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); + const eggMoveBg = globalScene.add.nineslice(70, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); eggMoveBg.setOrigin(1, 0); - const eggMoveLabel = addTextObject(this.scene, 70 - eggMoveBg.width / 2, 0, "???", TextStyle.PARTY); + const eggMoveLabel = addTextObject(70 - eggMoveBg.width / 2, 0, "???", TextStyle.PARTY); eggMoveLabel.setOrigin(0.5, 0); this.pokemonEggMoveBgs.push(eggMoveBg); @@ -126,9 +126,9 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { const shiny = pokemon.shiny; const variant = pokemon.variant; this.currentPokemonSprite.setVisible(false); - species.loadAssets(this.scene, female, formIndex, shiny, variant, true).then(() => { + species.loadAssets(female, formIndex, shiny, variant, true).then(() => { - getPokemonSpeciesForm(species.speciesId, pokemon.formIndex).cry(this.scene); + getPokemonSpeciesForm(species.speciesId, pokemon.formIndex).cry(); this.currentPokemonSprite.play(species.getSpriteKey(female, formIndex, shiny, variant)); this.currentPokemonSprite.setPipelineData("shiny", shiny); this.currentPokemonSprite.setPipelineData("variant", variant); @@ -156,7 +156,7 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { this.pokemonCandyIcon.setVisible(true); this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1]))); this.pokemonCandyOverlayIcon.setVisible(true); - this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`); + this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[species.speciesId].candyCount}`); this.pokemonCandyCountText.setVisible(true); this.pokemonNumberText.setText(Utils.padInt(species.speciesId, 4)); @@ -166,7 +166,7 @@ export default class PokemonHatchInfoContainer extends PokemonInfoContainer { for (let em = 0; em < 4; em++) { const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null; - const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em); + const eggMoveUnlocked = eggMove && globalScene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em); this.pokemonEggMoveBgs[em].setFrame(Type[eggMove ? eggMove.type : Type.UNKNOWN].toString().toLowerCase()); this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???"); diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index c7a24f69200..010d23315f2 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -1,4 +1,4 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; export enum PokemonIconAnimMode { @@ -13,7 +13,7 @@ export default class PokemonIconAnimHandler { private icons: Map; private toggled: boolean; - setup(scene: BattleScene): void { + setup(): void { this.icons = new Map(); this.toggled = false; @@ -26,7 +26,7 @@ export default class PokemonIconAnimHandler { i.y += delta * (this.toggled ? 1 : -1); } }; - scene.tweens.addCounter({ + globalScene.tweens.addCounter({ duration: Utils.fixedInt(200), from: 0, to: 1, diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index e0d432265a3..4f7a28f1d6d 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -1,12 +1,13 @@ import { getVariantTint } from "#app/data/variant"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; -import BattleScene from "../battle-scene"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import { globalScene } from "#app/global-scene"; import { Gender, getGenderColor, getGenderSymbol } from "../data/gender"; import { getNatureName } from "../data/nature"; import { Type } from "#enums/type"; -import Pokemon from "../field/pokemon"; +import type Pokemon from "../field/pokemon"; import i18next from "i18next"; -import { DexAttr, DexEntry, StarterDataEntry } from "../system/game-data"; +import type { DexEntry, StarterDataEntry } from "../system/game-data"; +import { DexAttr } from "../system/game-data"; import * as Utils from "../utils"; import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; @@ -57,8 +58,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { public shown: boolean; - constructor(scene: BattleScene, x: number = 372, y: number = 66) { - super(scene, x, y); + constructor(x: number = 372, y: number = 66) { + super(globalScene, x, y); this.initialX = x; } @@ -67,11 +68,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const currentLanguage = i18next.resolvedLanguage!; // TODO: is this bang correct? const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage?.includes(lang))!; // TODO: is this bang correct? const textSettings = languageSettings[langSettingKey]; - this.infoBg = addWindow(this.scene, 0, 0, this.infoWindowWidth, 132); + this.infoBg = addWindow(0, 0, this.infoWindowWidth, 132); this.infoBg.setOrigin(0.5, 0.5); this.infoBg.setName("window-info-bg"); - this.pokemonMovesContainer = this.scene.add.container(6, 14); + this.pokemonMovesContainer = globalScene.add.container(6, 14); this.pokemonMovesContainer.setName("pkmn-moves"); this.movesContainerInitialX = this.pokemonMovesContainer.x; @@ -80,26 +81,26 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonMoveBgs = []; this.pokemonMoveLabels = []; - const movesBg = addWindow(this.scene, 0, 0, 58, 52); + const movesBg = addWindow(0, 0, 58, 52); movesBg.setOrigin(1, 0); movesBg.setName("window-moves-bg"); this.pokemonMovesContainer.add(movesBg); - const movesLabel = addTextObject(this.scene, -movesBg.width / 2, 6, i18next.t("pokemonInfoContainer:moveset"), TextStyle.WINDOW, { fontSize: "64px" }); + const movesLabel = addTextObject(-movesBg.width / 2, 6, i18next.t("pokemonInfoContainer:moveset"), TextStyle.WINDOW, { fontSize: "64px" }); movesLabel.setOrigin(0.5, 0); movesLabel.setName("text-moves"); this.pokemonMovesContainer.add(movesLabel); for (let m = 0; m < 4; m++) { - const moveContainer = this.scene.add.container(-6, 18 + 7 * m); + const moveContainer = globalScene.add.container(-6, 18 + 7 * m); moveContainer.setScale(0.5); moveContainer.setName("move"); - const moveBg = this.scene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); + const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); moveBg.setOrigin(1, 0); moveBg.setName("nineslice-move-bg"); - const moveLabel = addTextObject(this.scene, -moveBg.width / 2, 0, "-", TextStyle.PARTY); + const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.PARTY); moveLabel.setOrigin(0.5, 0); moveLabel.setName("text-move-label"); @@ -115,7 +116,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.add(this.pokemonMovesContainer); - this.statsContainer = new StatsContainer(this.scene, -48, -64, true); + this.statsContainer = new StatsContainer(-48, -64, true); this.add(this.infoBg); this.add(this.statsContainer); @@ -127,62 +128,62 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { // The font size should be set by language const infoContainerTextSize = textSettings?.infoContainerTextSize || "64px"; - this.pokemonFormLabelText = addTextObject(this.scene, infoContainerLabelXPos, 19, i18next.t("pokemonInfoContainer:form"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonFormLabelText = addTextObject(infoContainerLabelXPos, 19, i18next.t("pokemonInfoContainer:form"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonFormLabelText.setOrigin(1, 0); this.pokemonFormLabelText.setVisible(false); this.add(this.pokemonFormLabelText); - this.pokemonFormText = addTextObject(this.scene, infoContainerTextXPos, 19, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonFormText = addTextObject(infoContainerTextXPos, 19, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonFormText.setOrigin(0, 0); this.pokemonFormText.setVisible(false); this.add(this.pokemonFormText); - this.pokemonGenderText = addTextObject(this.scene, -42, -61, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonGenderText = addTextObject(-42, -61, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonGenderText.setOrigin(0, 0); this.pokemonGenderText.setVisible(false); this.pokemonGenderText.setName("text-pkmn-gender"); this.add(this.pokemonGenderText); - this.pokemonGenderNewText = addTextObject(this.scene, -36, -61, "", TextStyle.WINDOW, { fontSize: "64px" }); + this.pokemonGenderNewText = addTextObject(-36, -61, "", TextStyle.WINDOW, { fontSize: "64px" }); this.pokemonGenderNewText.setOrigin(0, 0); this.pokemonGenderNewText.setVisible(false); this.pokemonGenderNewText.setName("text-pkmn-new-gender"); this.add(this.pokemonGenderNewText); - this.pokemonAbilityLabelText = addTextObject(this.scene, infoContainerLabelXPos, 29, i18next.t("pokemonInfoContainer:ability"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonAbilityLabelText = addTextObject(infoContainerLabelXPos, 29, i18next.t("pokemonInfoContainer:ability"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityLabelText.setOrigin(1, 0); this.pokemonAbilityLabelText.setName("text-pkmn-ability-label"); this.add(this.pokemonAbilityLabelText); - this.pokemonAbilityText = addTextObject(this.scene, infoContainerTextXPos, 29, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonAbilityText = addTextObject(infoContainerTextXPos, 29, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityText.setOrigin(0, 0); this.pokemonAbilityText.setName("text-pkmn-ability"); this.add(this.pokemonAbilityText); - this.pokemonNatureLabelText = addTextObject(this.scene, infoContainerLabelXPos, 39, i18next.t("pokemonInfoContainer:nature"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonNatureLabelText = addTextObject(infoContainerLabelXPos, 39, i18next.t("pokemonInfoContainer:nature"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonNatureLabelText.setOrigin(1, 0); this.pokemonNatureLabelText.setName("text-pkmn-nature-label"); this.add(this.pokemonNatureLabelText); - this.pokemonNatureText = addBBCodeTextObject(this.scene, infoContainerTextXPos, 39, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize, lineSpacing: 3, maxLines: 2 }); + this.pokemonNatureText = addBBCodeTextObject(infoContainerTextXPos, 39, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize, lineSpacing: 3, maxLines: 2 }); this.pokemonNatureText.setOrigin(0, 0); this.pokemonNatureText.setName("text-pkmn-nature"); this.add(this.pokemonNatureText); - this.pokemonShinyIcon = this.scene.add.image(-43.5, 48.5, "shiny_star"); + this.pokemonShinyIcon = globalScene.add.image(-43.5, 48.5, "shiny_star"); this.pokemonShinyIcon.setOrigin(0, 0); this.pokemonShinyIcon.setScale(0.75); this.pokemonShinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.pokemonShinyIcon.setName("img-pkmn-shiny-icon"); this.add(this.pokemonShinyIcon); - this.pokemonShinyNewIcon = addTextObject(this.scene, this.pokemonShinyIcon.x + 12, this.pokemonShinyIcon.y, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonShinyNewIcon = addTextObject(this.pokemonShinyIcon.x + 12, this.pokemonShinyIcon.y, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonShinyNewIcon.setOrigin(0, 0); this.pokemonShinyNewIcon.setName("text-pkmn-shiny-new-icon"); this.add(this.pokemonShinyNewIcon); this.pokemonShinyNewIcon.setVisible(false); - this.pokemonFusionShinyIcon = this.scene.add.image(this.pokemonShinyIcon.x, this.pokemonShinyIcon.y, "shiny_star_2"); + this.pokemonFusionShinyIcon = globalScene.add.image(this.pokemonShinyIcon.x, this.pokemonShinyIcon.y, "shiny_star_2"); this.pokemonFusionShinyIcon.setOrigin(0, 0); this.pokemonFusionShinyIcon.setScale(0.75); this.pokemonFusionShinyIcon.setName("img-pkmn-fusion-shiny-icon"); @@ -194,10 +195,10 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1, dexEntry?: DexEntry, starterEntry?: StarterDataEntry, eggInfo = false): Promise { return new Promise(resolve => { if (!dexEntry) { - dexEntry = pokemon.scene.gameData.dexData[pokemon.species.speciesId]; + dexEntry = globalScene.gameData.dexData[pokemon.species.speciesId]; } if (!starterEntry) { - starterEntry = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()]; + starterEntry = globalScene.gameData.starterData[pokemon.species.getRootSpeciesId()]; } const caughtAttr = BigInt(dexEntry.caughtAttr); @@ -209,8 +210,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const newGender = BigInt(1 << pokemon.gender) * DexAttr.MALE; this.pokemonGenderNewText.setText("(+)"); - this.pokemonGenderNewText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonGenderNewText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonGenderNewText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonGenderNewText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); this.pokemonGenderNewText.setVisible((newGender & caughtAttr) === BigInt(0)); } else { this.pokemonGenderNewText.setVisible(false); @@ -241,18 +242,18 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const newForm = BigInt(1 << pokemon.formIndex) * DexAttr.DEFAULT_FORM; if ((newForm & caughtAttr) === BigInt(0)) { - this.pokemonFormLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonFormLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); } else { - this.pokemonFormLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); - this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + this.pokemonFormLabelText.setColor(getTextColor(TextStyle.WINDOW, false, globalScene.uiTheme)); + this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, globalScene.uiTheme)); } this.pokemonFormText.setText(formName.length > this.numCharsBeforeCutoff ? formName.substring(0, this.numCharsBeforeCutoff - 3) + "..." : formName); if (formName.length > this.numCharsBeforeCutoff) { this.pokemonFormText.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.pokemonFormText.width, this.pokemonFormText.height), Phaser.Geom.Rectangle.Contains); - this.pokemonFormText.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", formName, true)); - this.pokemonFormText.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.pokemonFormText.on("pointerover", () => globalScene.ui.showTooltip("", formName, true)); + this.pokemonFormText.on("pointerout", () => globalScene.ui.hideTooltip()); } else { this.pokemonFormText.disableInteractive(); } @@ -264,31 +265,31 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const abilityTextStyle = pokemon.abilityIndex === 2 ? TextStyle.MONEY : TextStyle.WINDOW; this.pokemonAbilityText.setText(pokemon.getAbility(true).name); - this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, this.scene.uiTheme)); - this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, this.scene.uiTheme)); + this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, globalScene.uiTheme)); + this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, globalScene.uiTheme)); // Check if the player owns ability for the root form const playerOwnsThisAbility = pokemon.checkIfPlayerHasAbilityOfStarter(starterEntry.abilityAttr); if (!playerOwnsThisAbility) { - this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); } else { - this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); - this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.WINDOW, false, globalScene.uiTheme)); + this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, globalScene.uiTheme)); } - this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, this.scene.uiTheme)); + this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, globalScene.uiTheme)); const dexNatures = dexEntry.natureAttr; const newNature = 1 << (pokemon.nature + 1); if (!(dexNatures & newNature)) { - this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); } else { - this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); - this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.WINDOW, false, globalScene.uiTheme)); + this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, globalScene.uiTheme)); } const isFusion = pokemon.isFusion(); @@ -302,22 +303,22 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { const shinyDescriptor = doubleShiny || baseVariant ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` : ""; - this.pokemonShinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); - this.pokemonShinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.pokemonShinyIcon.on("pointerover", () => globalScene.ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); + this.pokemonShinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0)); const newVariant = BigInt(1 << (pokemon.variant + 4)); this.pokemonShinyNewIcon.setText("(+)"); - this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0)); this.pokemonShinyNewIcon.setVisible(!!newShinyOrVariant); } else if ((caughtAttr & DexAttr.NON_SHINY) === BigInt(0) && ((caughtAttr & DexAttr.SHINY) === DexAttr.SHINY)) { //If the player has *only* caught any shiny variant of this species, not a non-shiny this.pokemonShinyNewIcon.setVisible(true); this.pokemonShinyNewIcon.setText("(+)"); - this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); - this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)); + this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)); } else { this.pokemonShinyNewIcon.setVisible(false); } @@ -329,12 +330,12 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { } const starterSpeciesId = pokemon.species.getRootSpeciesId(); - const originalIvs: integer[] | null = eggInfo ? (dexEntry.caughtAttr ? dexEntry.ivs : null) : (this.scene.gameData.dexData[starterSpeciesId].caughtAttr - ? this.scene.gameData.dexData[starterSpeciesId].ivs : null); + const originalIvs: integer[] | null = eggInfo ? (dexEntry.caughtAttr ? dexEntry.ivs : null) : (globalScene.gameData.dexData[starterSpeciesId].caughtAttr + ? globalScene.gameData.dexData[starterSpeciesId].ivs : null); this.statsContainer.updateIvs(pokemon.ivs, originalIvs!); // TODO: is this bang correct? if (!eggInfo) { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", @@ -345,7 +346,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { }); if (showMoves) { - this.scene.tweens.add({ + globalScene.tweens.add({ delay: Utils.fixedInt(Math.floor(325 / speedMultiplier)), targets: this.pokemonMovesContainer, duration: Utils.fixedInt(Math.floor(325 / speedMultiplier)), @@ -365,7 +366,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(true); this.shown = true; - this.scene.hideEnemyModifierBar(); + globalScene.hideEnemyModifierBar(); }); } @@ -402,7 +403,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { makeRoomForConfirmUi(speedMultiplier: number = 1, fromCatch: boolean = false): Promise { const xPosition = fromCatch ? this.initialX - this.infoWindowWidth - 65 : this.initialX - this.infoWindowWidth - ConfirmUiHandler.windowWidth; return new Promise(resolve => { - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, duration: Utils.fixedInt(Math.floor(150 / speedMultiplier)), ease: "Cubic.easeInOut", @@ -417,18 +418,18 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { hide(speedMultiplier: number = 1): Promise { return new Promise(resolve => { if (!this.shown) { - this.scene.showEnemyModifierBar(); + globalScene.showEnemyModifierBar(); return resolve(); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.pokemonMovesContainer, duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", x: this.movesContainerInitialX }); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, duration: Utils.fixedInt(Math.floor(750 / speedMultiplier)), ease: "Cubic.easeInOut", @@ -437,8 +438,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(false); this.pokemonShinyIcon.off("pointerover"); this.pokemonShinyIcon.off("pointerout"); - (this.scene as BattleScene).ui.hideTooltip(); - this.scene.showEnemyModifierBar(); + globalScene.ui.hideTooltip(); + globalScene.showEnemyModifierBar(); resolve(); } }); @@ -447,7 +448,3 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { }); } } - -export default interface PokemonInfoContainer { - scene: BattleScene -} diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index 892f78bd1ba..3b229a47c38 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -1,9 +1,11 @@ -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; -import { ModalConfig } from "./modal-ui-handler"; +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; import { Mode } from "./ui"; import { TextStyle, addTextObject } from "./text"; import i18next from "i18next"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { globalScene } from "#app/global-scene"; interface LanguageSetting { @@ -78,7 +80,7 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { }); const warningMessageFontSize = languageSettings[i18next.resolvedLanguage!]?.warningMessageFontSize ?? "42px"; - const label = addTextObject(this.scene, 10, 87, i18next.t("menu:registrationAgeWarning"), TextStyle.TOOLTIP_CONTENT, { fontSize: warningMessageFontSize }); + const label = addTextObject(10, 87, i18next.t("menu:registrationAgeWarning"), TextStyle.TOOLTIP_CONTENT, { fontSize: warningMessageFontSize }); this.modalContainer.add(label); } @@ -92,10 +94,10 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { // Prevent overlapping overrides on action modification this.submitAction = originalRegistrationAction; this.sanitizeInputs(); - this.scene.ui.setMode(Mode.LOADING, { buttonActions: []}); + globalScene.ui.setMode(Mode.LOADING, { buttonActions: []}); const onFail = error => { - this.scene.ui.setMode(Mode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() })); - this.scene.ui.playError(); + globalScene.ui.setMode(Mode.REGISTRATION_FORM, Object.assign(config, { errorMessage: error?.trim() })); + globalScene.ui.playError(); const errorMessageFontSize = languageSettings[i18next.resolvedLanguage!]?.errorMessageFontSize; if (errorMessageFontSize) { this.errorMessage.setFontSize(errorMessageFontSize); diff --git a/src/ui/rename-form-ui-handler.ts b/src/ui/rename-form-ui-handler.ts index 6e4c4c6809d..3004530063e 100644 --- a/src/ui/rename-form-ui-handler.ts +++ b/src/ui/rename-form-ui-handler.ts @@ -1,7 +1,8 @@ -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; -import { ModalConfig } from "./modal-ui-handler"; +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; import i18next from "i18next"; -import { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon } from "#app/field/pokemon"; export default class RenameFormUiHandler extends FormModalUiHandler { getModalTitle(config?: ModalConfig): string { diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 061f15d0956..2a498f77b8d 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -1,15 +1,15 @@ -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { GameModes } from "../game-mode"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; -import PokemonData from "../system/pokemon-data"; +import type PokemonData from "../system/pokemon-data"; import MessageUiHandler from "./message-ui-handler"; import i18next from "i18next"; import { Button } from "../enums/buttons"; import { BattleType } from "../battle"; -import { RunEntry } from "../system/game-data"; +import type { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; import { TrainerVariant } from "../field/trainer"; import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; @@ -40,34 +40,34 @@ export default class RunHistoryUiHandler extends MessageUiHandler { private runContainerInitialY: number; - constructor(scene: BattleScene) { - super(scene, Mode.RUN_HISTORY); + constructor() { + super(Mode.RUN_HISTORY); } override setup() { const ui = this.getUi(); - this.runSelectContainer = this.scene.add.container(0, 0); + this.runSelectContainer = globalScene.add.container(0, 0); this.runSelectContainer.setVisible(false); ui.add(this.runSelectContainer); - const loadSessionBg = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, -this.scene.game.canvas.height / 6, 0x006860); + const loadSessionBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, -globalScene.game.canvas.height / 6, 0x006860); loadSessionBg.setOrigin(0, 0); this.runSelectContainer.add(loadSessionBg); - this.runContainerInitialY = -this.scene.game.canvas.height / 6 + 8; + this.runContainerInitialY = -globalScene.game.canvas.height / 6 + 8; - this.runsContainer = this.scene.add.container(8, this.runContainerInitialY); + this.runsContainer = globalScene.add.container(8, this.runContainerInitialY); this.runSelectContainer.add(this.runsContainer); this.runs = []; - this.scene.loadImage("hall_of_fame_red", "ui"); - this.scene.loadImage("hall_of_fame_blue", "ui"); + globalScene.loadImage("hall_of_fame_red", "ui"); + globalScene.loadImage("hall_of_fame_blue", "ui"); // For some reason, the game deletes/unloads the rival sprites. As a result, Run Info cannot access the rival sprites. // The rivals are loaded here to have some way of accessing those sprites. - this.scene.loadAtlas("rival_f", "trainer"); - this.scene.loadAtlas("rival_m", "trainer"); + globalScene.loadAtlas("rival_f", "trainer"); + globalScene.loadAtlas("rival_m", "trainer"); } override show(args: any[]): boolean { @@ -75,7 +75,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { this.getUi().bringToTop(this.runSelectContainer); this.runSelectContainer.setVisible(true); - this.populateRuns(this.scene).then(() => { + this.populateRuns().then(() => { this.setScrollCursor(0); this.setCursor(0); @@ -105,7 +105,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { if (button === Button.ACTION) { const cursor = this.cursor + this.scrollCursor; if (this.runs[cursor]) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true); + globalScene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true); } else { return false; } @@ -114,7 +114,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { } else { this.runSelectCallback = null; success = true; - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); } } else if (this.runs.length > 0) { switch (button) { @@ -153,12 +153,11 @@ export default class RunHistoryUiHandler extends MessageUiHandler { /** * This retrieves the player's run history and facilitates the processes necessary for the output display. - * @param scene: BattleScene * Runs are displayed from newest --> oldest in descending order. * In the for loop, each run is processed to create an RunEntryContainer used to display and store the run's unique information */ - private async populateRuns(scene: BattleScene) { - const response = await this.scene.gameData.getRunHistoryData(this.scene); + private async populateRuns() { + const response = await globalScene.gameData.getRunHistoryData(); const timestamps = Object.keys(response); if (timestamps.length === 0) { this.showEmpty(); @@ -170,8 +169,8 @@ export default class RunHistoryUiHandler extends MessageUiHandler { } const entryCount = timestamps.length; for (let s = 0; s < entryCount; s++) { - const entry = new RunEntryContainer(this.scene, response[timestampsNo[s]], s); - this.scene.add.existing(entry); + const entry = new RunEntryContainer(response[timestampsNo[s]], s); + globalScene.add.existing(entry); this.runsContainer.add(entry); this.runs.push(entry); } @@ -184,10 +183,10 @@ export default class RunHistoryUiHandler extends MessageUiHandler { * If the player has no runs saved so far, this creates a giant window labeled empty instead. */ private async showEmpty() { - const emptyWindow = addWindow(this.scene, 0, 0, 304, 165); + const emptyWindow = addWindow(0, 0, 304, 165); this.runsContainer.add(emptyWindow); const emptyWindowCoordinates = emptyWindow.getCenter(); - const emptyText = addTextObject(this.scene, 0, 0, i18next.t("saveSlotSelectUiHandler:empty"), TextStyle.WINDOW, { fontSize: "128px" }); + const emptyText = addTextObject(0, 0, i18next.t("saveSlotSelectUiHandler:empty"), TextStyle.WINDOW, { fontSize: "128px" }); emptyText.setPosition(emptyWindowCoordinates.x - 18, emptyWindowCoordinates.y - 15); this.runsContainer.add(emptyText); } @@ -196,7 +195,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { const changed = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 46, 6, 6, 6, 6); + this.cursorObj = globalScene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 46, 6, 6, 6, 6); this.cursorObj.setOrigin(0, 0); this.runsContainer.add(this.cursorObj); } @@ -210,7 +209,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { if (changed) { this.scrollCursor = scrollCursor; this.setCursor(this.cursor); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.runsContainer, y: this.runContainerInitialY - 56 * scrollCursor, duration: Utils.fixedInt(325), @@ -255,8 +254,8 @@ class RunEntryContainer extends Phaser.GameObjects.Container { private slotId: number; public entryData: RunEntry; - constructor(scene: BattleScene, entryData: RunEntry, slotId: number) { - super(scene, 0, slotId * 56); + constructor(entryData: RunEntry, slotId: number) { + super(globalScene, 0, slotId * 56); this.slotId = slotId; this.entryData = entryData; @@ -278,31 +277,31 @@ class RunEntryContainer extends Phaser.GameObjects.Container { private setup(run: RunEntry) { const victory = run.isVictory; - const data = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); + const data = globalScene.gameData.parseSessionData(JSON.stringify(run.entry)); - const slotWindow = addWindow(this.scene, 0, 0, 304, 52); + const slotWindow = addWindow(0, 0, 304, 52); this.add(slotWindow); // Run Result: Victory if (victory) { - const gameOutcomeLabel = addTextObject(this.scene, 8, 5, `${i18next.t("runHistory:victory")}`, TextStyle.WINDOW); + const gameOutcomeLabel = addTextObject(8, 5, `${i18next.t("runHistory:victory")}`, TextStyle.WINDOW); this.add(gameOutcomeLabel); } else { // Run Result: Defeats - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); // Defeats from wild Pokemon battles will show the Pokemon responsible by the text of the run result. if (data.battleType === BattleType.WILD || (data.battleType === BattleType.MYSTERY_ENCOUNTER && !data.trainer)) { - const enemyContainer = this.scene.add.container(8, 5); - const gameOutcomeLabel = addTextObject(this.scene, 0, 0, `${i18next.t("runHistory:defeatedWild", { context: genderStr })}`, TextStyle.WINDOW); + const enemyContainer = globalScene.add.container(8, 5); + const gameOutcomeLabel = addTextObject(0, 0, `${i18next.t("runHistory:defeatedWild", { context: genderStr })}`, TextStyle.WINDOW); enemyContainer.add(gameOutcomeLabel); data.enemyParty.forEach((enemyData, e) => { - const enemyIconContainer = this.scene.add.container(65 + (e * 25), -8); + const enemyIconContainer = globalScene.add.container(65 + (e * 25), -8); enemyIconContainer.setScale(0.75); enemyData.boss = false; enemyData["player"] = true; - const enemy = enemyData.toPokemon(this.scene); - const enemyIcon = this.scene.addPokemonIcon(enemy, 0, 0, 0, 0); - const enemyLevel = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + const enemy = enemyData.toPokemon(); + const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); + const enemyLevel = addTextObject(32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); enemyLevel.setShadow(0, 0, undefined); enemyLevel.setStroke("#424242", 14); enemyLevel.setOrigin(1, 0); @@ -313,15 +312,15 @@ class RunEntryContainer extends Phaser.GameObjects.Container { }); this.add(enemyContainer); } else if (data.battleType === BattleType.TRAINER || (data.battleType === BattleType.MYSTERY_ENCOUNTER && data.trainer)) { // Defeats from Trainers show the trainer's title and name - const tObj = data.trainer.toTrainer(this.scene); + const tObj = data.trainer.toTrainer(); // Because of the interesting mechanics behind rival names, the rival name and title have to be retrieved differently const RIVAL_TRAINER_ID_THRESHOLD = 375; if (data.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) { const rivalName = (tObj.variant === TrainerVariant.FEMALE) ? "trainerNames:rival_female" : "trainerNames:rival"; - const gameOutcomeLabel = addTextObject(this.scene, 8, 5, `${i18next.t("runHistory:defeatedRival", { context: genderStr })} ${i18next.t(rivalName)}`, TextStyle.WINDOW); + const gameOutcomeLabel = addTextObject(8, 5, `${i18next.t("runHistory:defeatedRival", { context: genderStr })} ${i18next.t(rivalName)}`, TextStyle.WINDOW); this.add(gameOutcomeLabel); } else { - const gameOutcomeLabel = addTextObject(this.scene, 8, 5, `${i18next.t("runHistory:defeatedTrainer", { context: genderStr })}${tObj.getName(0, true)}`, TextStyle.WINDOW); + const gameOutcomeLabel = addTextObject(8, 5, `${i18next.t("runHistory:defeatedTrainer", { context: genderStr })}${tObj.getName(0, true)}`, TextStyle.WINDOW); this.add(gameOutcomeLabel); } } @@ -330,7 +329,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { // Game Mode + Waves // Because Endless (Spliced) tends to have the longest name across languages, the line tends to spill into the party icons. // To fix this, the Spliced icon is used to indicate an Endless Spliced run - const gameModeLabel = addTextObject(this.scene, 8, 19, "", TextStyle.WINDOW); + const gameModeLabel = addTextObject(8, 19, "", TextStyle.WINDOW); let mode = ""; switch (data.gameMode) { case GameModes.DAILY: @@ -349,7 +348,7 @@ class RunEntryContainer extends Phaser.GameObjects.Container { } gameModeLabel.appendText(mode, false); if (data.gameMode === GameModes.SPLICED_ENDLESS) { - const splicedIcon = this.scene.add.image(0, 0, "icon_spliced"); + const splicedIcon = globalScene.add.image(0, 0, "icon_spliced"); splicedIcon.setScale(0.75); const coords = gameModeLabel.getTopRight(); splicedIcon.setPosition(coords.x + 5, 27); @@ -362,21 +361,21 @@ class RunEntryContainer extends Phaser.GameObjects.Container { gameModeLabel.appendText(i18next.t("saveSlotSelectUiHandler:wave") + " " + data.waveIndex, false); this.add(gameModeLabel); - const timestampLabel = addTextObject(this.scene, 8, 33, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); + const timestampLabel = addTextObject(8, 33, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); this.add(timestampLabel); // pokemonIconsContainer holds the run's party Pokemon icons and levels // Icons should be level with each other here, but there are significant number of icons that have a center axis / position far from the norm. // The code here does not account for icon weirdness. - const pokemonIconsContainer = this.scene.add.container(140, 17); + const pokemonIconsContainer = globalScene.add.container(140, 17); data.party.forEach((p: PokemonData, i: number) => { - const iconContainer = this.scene.add.container(26 * i, 0); + const iconContainer = globalScene.add.container(26 * i, 0); iconContainer.setScale(0.75); - const pokemon = p.toPokemon(this.scene); - const icon = this.scene.addPokemonIcon(pokemon, 0, 0, 0, 0); + const pokemon = p.toPokemon(); + const icon = globalScene.addPokemonIcon(pokemon, 0, 0, 0, 0); - const text = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + const text = addTextObject(32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); text.setShadow(0, 0, undefined); text.setStroke("#424242", 14); text.setOrigin(1, 0); @@ -392,8 +391,3 @@ class RunEntryContainer extends Phaser.GameObjects.Container { this.add(pokemonIconsContainer); } } - -interface RunEntryContainer { - scene: BattleScene; -} - diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 071690aee54..43b95cb3793 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -1,13 +1,12 @@ -import BattleScene from "../battle-scene"; import { GameModes } from "../game-mode"; import UiHandler from "./ui-handler"; -import { SessionSaveData } from "../system/game-data"; +import type { SessionSaveData } from "../system/game-data"; import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import { getPokeballAtlasKey } from "#app/data/pokeball"; import * as Utils from "../utils"; -import PokemonData from "../system/pokemon-data"; +import type PokemonData from "../system/pokemon-data"; import i18next from "i18next"; import { Button } from "../enums/buttons"; import { BattleType } from "../battle"; @@ -21,11 +20,12 @@ import { TypeColor, TypeShadow } from "#app/enums/color"; import { getNatureStatMultiplier, getNatureName } from "../data/nature"; import { getVariantTint } from "#app/data/variant"; import * as Modifier from "../modifier/modifier"; -import { Species } from "#enums/species"; +import type { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { getBiomeName } from "#app/data/balance/biomes"; -import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { globalScene } from "#app/global-scene"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -67,16 +67,16 @@ export default class RunInfoUiHandler extends UiHandler { private partyVisibility: Boolean; private modifiersModule: any; - constructor(scene: BattleScene) { - super(scene, Mode.RUN_INFO); + constructor() { + super(Mode.RUN_INFO); } override async setup() { - this.runContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.runContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); // The import of the modifiersModule is loaded here to sidestep async/await issues. this.modifiersModule = Modifier; this.runContainer.setVisible(false); - this.scene.loadImage("encounter_exclaim", "mystery-encounters"); + globalScene.loadImage("encounter_exclaim", "mystery-encounters"); } /** @@ -93,14 +93,14 @@ export default class RunInfoUiHandler extends UiHandler { override show(args: any[]): boolean { super.show(args); - const gameStatsBg = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width, this.scene.game.canvas.height, 0x006860); + const gameStatsBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height, 0x006860); gameStatsBg.setOrigin(0, 0); this.runContainer.add(gameStatsBg); const run = args[0]; this.runDisplayMode = args[1]; if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { - this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); + this.runInfo = globalScene.gameData.parseSessionData(JSON.stringify(run.entry)); this.isVictory = run.isVictory ?? false; } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { this.runInfo = args[0]; @@ -112,11 +112,11 @@ export default class RunInfoUiHandler extends UiHandler { // Creates Header and adds to this.runContainer this.addHeader(); - this.statsBgWidth = ((this.scene.game.canvas.width / 6) - 2) / 3; + this.statsBgWidth = ((globalScene.game.canvas.width / 6) - 2) / 3; // Creates Run Result Container - this.runResultContainer = this.scene.add.container(0, 24); - const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65); + this.runResultContainer = globalScene.add.container(0, 24); + const runResultWindow = addWindow(0, 0, this.statsBgWidth - 11, 65); runResultWindow.setOrigin(0, 0); runResultWindow.setName("Run_Result_Window"); this.runResultContainer.add(runResultWindow); @@ -127,18 +127,18 @@ export default class RunInfoUiHandler extends UiHandler { } // Creates Run Info Container - this.runInfoContainer = this.scene.add.container(0, 89); - const runInfoWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 90); + this.runInfoContainer = globalScene.add.container(0, 89); + const runInfoWindow = addWindow(0, 0, this.statsBgWidth - 11, 90); const runInfoWindowCoords = runInfoWindow.getBottomRight(); this.runInfoContainer.add(runInfoWindow); this.parseRunInfo(runInfoWindowCoords.x, runInfoWindowCoords.y); // Creates Player Party Container - this.partyContainer = this.scene.add.container(this.statsBgWidth - 10, 23); + this.partyContainer = globalScene.add.container(this.statsBgWidth - 10, 23); this.parsePartyInfo(); this.showParty(true); - this.runContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.runContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); this.getUi().bringToTop(this.runContainer); this.runContainer.setVisible(true); @@ -163,25 +163,25 @@ export default class RunInfoUiHandler extends UiHandler { * It does not check if the run has any PokemonHeldItemModifiers though. */ private addHeader() { - const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + const headerBg = addWindow(0, 0, (globalScene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); this.runContainer.add(headerBg); if (this.runInfo.modifiers.length !== 0) { const headerBgCoords = headerBg.getTopRight(); - const abilityButtonContainer = this.scene.add.container(0, 0); - const abilityButtonText = addTextObject(this.scene, 8, 0, i18next.t("runHistory:viewHeldItems"), TextStyle.WINDOW, { fontSize:"34px" }); + const abilityButtonContainer = globalScene.add.container(0, 0); + const abilityButtonText = addTextObject(8, 0, i18next.t("runHistory:viewHeldItems"), TextStyle.WINDOW, { fontSize:"34px" }); const gamepadType = this.getUi().getGamepadType(); let abilityButtonElement: Phaser.GameObjects.Sprite; if (gamepadType === "touch") { - abilityButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 2, "keyboard", "E.png"); + abilityButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 2, "keyboard", "E.png"); } else { - abilityButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 2, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Ability)); + abilityButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 2, gamepadType, globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Ability)); } abilityButtonContainer.add([ abilityButtonText, abilityButtonElement ]); abilityButtonContainer.setPosition(headerBgCoords.x - abilityButtonText.displayWidth - abilityButtonElement.displayWidth - 8, 10); this.runContainer.add(abilityButtonContainer); } - const headerText = addTextObject(this.scene, 0, 0, i18next.t("runHistory:runInfo"), TextStyle.SETTINGS_LABEL); + const headerText = addTextObject(0, 0, i18next.t("runHistory:runInfo"), TextStyle.SETTINGS_LABEL); headerText.setOrigin(0, 0); headerText.setPositionRelative(headerBg, 8, 4); this.runContainer.add(headerText); @@ -196,25 +196,25 @@ export default class RunInfoUiHandler extends UiHandler { * */ private async parseRunResult() { - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex]; const runResultTextStyle = this.isVictory ? TextStyle.PERFECT_IV : TextStyle.SUMMARY_RED; const runResultTitle = this.isVictory ? i18next.t("runHistory:victory") : i18next.t("runHistory:defeated", { context: genderStr }); - const runResultText = addTextObject(this.scene, 6, 5, `${runResultTitle} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, runResultTextStyle, { fontSize : "65px", lineSpacing: 0.1 }); + const runResultText = addTextObject(6, 5, `${runResultTitle} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, runResultTextStyle, { fontSize : "65px", lineSpacing: 0.1 }); if (this.isVictory) { - const hallofFameInstructionContainer = this.scene.add.container(0, 0); - const shinyButtonText = addTextObject(this.scene, 8, 0, i18next.t("runHistory:viewHallOfFame"), TextStyle.WINDOW, { fontSize:"65px" }); - const formButtonText = addTextObject(this.scene, 8, 12, i18next.t("runHistory:viewEndingSplash"), TextStyle.WINDOW, { fontSize:"65px" }); + const hallofFameInstructionContainer = globalScene.add.container(0, 0); + const shinyButtonText = addTextObject(8, 0, i18next.t("runHistory:viewHallOfFame"), TextStyle.WINDOW, { fontSize:"65px" }); + const formButtonText = addTextObject(8, 12, i18next.t("runHistory:viewEndingSplash"), TextStyle.WINDOW, { fontSize:"65px" }); const gamepadType = this.getUi().getGamepadType(); let shinyButtonElement: Phaser.GameObjects.Sprite; let formButtonElement: Phaser.GameObjects.Sprite; if (gamepadType === "touch") { - shinyButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 4, "keyboard", "R.png"); - formButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 16, "keyboard", "F.png"); + shinyButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 4, "keyboard", "R.png"); + formButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 16, "keyboard", "F.png"); } else { - shinyButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 4, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Shiny)); - formButtonElement = new Phaser.GameObjects.Sprite(this.scene, 0, 16, gamepadType, this.scene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Form)); + shinyButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 4, gamepadType, globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Shiny)); + formButtonElement = new Phaser.GameObjects.Sprite(globalScene, 0, 16, gamepadType, globalScene.inputController?.getIconForLatestInputRecorded(SettingKeyboard.Button_Cycle_Form)); } hallofFameInstructionContainer.add([ shinyButtonText, shinyButtonElement ]); @@ -227,7 +227,7 @@ export default class RunInfoUiHandler extends UiHandler { this.runResultContainer.add(runResultText); if (!this.isVictory) { - const enemyContainer = this.scene.add.container(0, 0); + const enemyContainer = globalScene.add.container(0, 0); // Wild - Single and Doubles if (this.runInfo.battleType === BattleType.WILD || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)) { switch (this.runInfo.enemyParty.length) { @@ -255,7 +255,7 @@ export default class RunInfoUiHandler extends UiHandler { * Mystery Encounters contain sprites associated with MEs + the title of the specific ME. */ private parseRunStatus() { - const enemyContainer = this.scene.add.container(0, 0); + const enemyContainer = globalScene.add.container(0, 0); this.runResultContainer.add(enemyContainer); if (this.runInfo.battleType === BattleType.WILD) { if (this.runInfo.enemyParty.length === 1) { @@ -267,13 +267,13 @@ export default class RunInfoUiHandler extends UiHandler { this.showTrainerSprites(enemyContainer); const row_limit = 3; this.runInfo.enemyParty.forEach((p, i) => { - const pokeball = this.scene.add.sprite(0, 0, "pb"); + const pokeball = globalScene.add.sprite(0, 0, "pb"); pokeball.setFrame(getPokeballAtlasKey(p.pokeball)); pokeball.setScale(0.5); pokeball.setPosition(58 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); enemyContainer.add(pokeball); }); - const trainerObj = this.runInfo.trainer.toTrainer(this.scene); + const trainerObj = this.runInfo.trainer.toTrainer(); const RIVAL_TRAINER_ID_THRESHOLD = 375; let trainerName = ""; if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) { @@ -282,21 +282,21 @@ export default class RunInfoUiHandler extends UiHandler { trainerName = trainerObj.getName(0, true); } const boxString = i18next.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", { trainerName: trainerName }).replace(/\n/g, " "); - const descContainer = this.scene.add.container(0, 0); - const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); + const descContainer = globalScene.add.container(0, 0); + const textBox = addTextObject(0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); descContainer.add(textBox); descContainer.setPosition(55, 32); this.runResultContainer.add(descContainer); } else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) { - const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim"); + const encounterExclaim = globalScene.add.sprite(0, 0, "encounter_exclaim"); encounterExclaim.setPosition(34, 26); encounterExclaim.setScale(0.65); - const subSprite = this.scene.add.sprite(56, -106, "pkmn__sub"); + const subSprite = globalScene.add.sprite(56, -106, "pkmn__sub"); subSprite.setScale(0.65); subSprite.setPosition(34, 46); - const mysteryEncounterTitle = i18next.t(this.scene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title"); - const descContainer = this.scene.add.container(0, 0); - const textBox = addTextObject(this.scene, 0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }}); + const mysteryEncounterTitle = i18next.t(globalScene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title"); + const descContainer = globalScene.add.container(0, 0); + const textBox = addTextObject(0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }}); descContainer.add(textBox); descContainer.setPosition(47, 37); this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]); @@ -306,10 +306,10 @@ export default class RunInfoUiHandler extends UiHandler { const windowCenterX = runResultWindow.getTopCenter().x; const windowBottomY = runResultWindow.getBottomCenter().y; - const runStatusText = addTextObject(this.scene, windowCenterX, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, TextStyle.WINDOW, { fontSize : "60px", lineSpacing: 0.1 }); + const runStatusText = addTextObject(windowCenterX, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, TextStyle.WINDOW, { fontSize : "60px", lineSpacing: 0.1 }); runStatusText.setOrigin(0.5, 0); - const currentBiomeText = addTextObject(this.scene, windowCenterX, windowBottomY - 5, `${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize: "60px" }); + const currentBiomeText = addTextObject(windowCenterX, windowBottomY - 5, `${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize: "60px" }); currentBiomeText.setOrigin(0.5, 1); this.runResultContainer.add([ runStatusText, currentBiomeText ]); @@ -321,16 +321,16 @@ export default class RunInfoUiHandler extends UiHandler { * @param enemyContainer - container holding enemy visual and level information */ private parseWildSingleDefeat(enemyContainer: Phaser.GameObjects.Container) { - const enemyIconContainer = this.scene.add.container(0, 0); + const enemyIconContainer = globalScene.add.container(0, 0); const enemyData = this.runInfo.enemyParty[0]; const bossStatus = enemyData.boss; enemyData.boss = false; enemyData["player"] = true; //addPokemonIcon() throws an error if the Pokemon used is a boss - const enemy = enemyData.toPokemon(this.scene); - const enemyIcon = this.scene.addPokemonIcon(enemy, 0, 0, 0, 0); + const enemy = enemyData.toPokemon(); + const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); const enemyLevelStyle = bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY; - const enemyLevel = addTextObject(this.scene, 36, 26, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, enemyLevelStyle, { fontSize: "44px", color: "#f8f8f8" }); + const enemyLevel = addTextObject(36, 26, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, enemyLevelStyle, { fontSize: "44px", color: "#f8f8f8" }); enemyLevel.setShadow(0, 0, undefined); enemyLevel.setStroke("#424242", 14); enemyLevel.setOrigin(1, 0); @@ -348,13 +348,13 @@ export default class RunInfoUiHandler extends UiHandler { */ private parseWildDoubleDefeat(enemyContainer: Phaser.GameObjects.Container) { this.runInfo.enemyParty.forEach((enemyData, e) => { - const enemyIconContainer = this.scene.add.container(0, 0); + const enemyIconContainer = globalScene.add.container(0, 0); const bossStatus = enemyData.boss; enemyData.boss = false; enemyData["player"] = true; - const enemy = enemyData.toPokemon(this.scene); - const enemyIcon = this.scene.addPokemonIcon(enemy, 0, 0, 0, 0); - const enemyLevel = addTextObject(this.scene, 36, 26, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "44px", color: "#f8f8f8" }); + const enemy = enemyData.toPokemon(); + const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); + const enemyLevel = addTextObject(36, 26, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, bossStatus ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "44px", color: "#f8f8f8" }); enemyLevel.setShadow(0, 0, undefined); enemyLevel.setStroke("#424242", 14); enemyLevel.setOrigin(1, 0); @@ -374,16 +374,16 @@ export default class RunInfoUiHandler extends UiHandler { */ private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) { // Creating the trainer sprite and adding it to enemyContainer - const tObj = this.runInfo.trainer.toTrainer(this.scene); + const tObj = this.runInfo.trainer.toTrainer(); // Loads trainer assets on demand, as they are not loaded by default in the scene - tObj.config.loadAssets(this.scene, this.runInfo.trainer.variant).then(() => { + tObj.config.loadAssets(this.runInfo.trainer.variant).then(() => { const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false); - const tObjSprite = this.scene.add.sprite(0, 5, tObjSpriteKey); + const tObjSprite = globalScene.add.sprite(0, 5, tObjSpriteKey); if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) { - const doubleContainer = this.scene.add.container(5, 8); + const doubleContainer = globalScene.add.container(5, 8); tObjSprite.setPosition(-3, -3); const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true); - const tObjPartnerSprite = this.scene.add.sprite(5, -3, tObjPartnerSpriteKey); + const tObjPartnerSprite = globalScene.add.sprite(5, -3, tObjPartnerSpriteKey); // Double Trainers have smaller sprites than Single Trainers if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { tObjPartnerSprite.setScale(0.20); @@ -424,7 +424,7 @@ export default class RunInfoUiHandler extends UiHandler { // Creates a dictionary {PokemonId: TeraShardType} const teraPokemon = {}; this.runInfo.enemyModifiers.forEach((m) => { - const modifier = m.toModifier(this.scene, this.modifiersModule[m.className]); + const modifier = m.toModifier(this.modifiersModule[m.className]); if (modifier instanceof Modifier.TerastallizeModifier) { const teraDetails = modifier?.getArgs(); const pkmnId = teraDetails[0]; @@ -434,16 +434,16 @@ export default class RunInfoUiHandler extends UiHandler { // Creates the Pokemon icons + level information and adds it to enemyContainer // 2 Rows x 3 Columns - const enemyPartyContainer = this.scene.add.container(0, 0); + const enemyPartyContainer = globalScene.add.container(0, 0); this.runInfo.enemyParty.forEach((enemyData, e) => { const pokemonRowHeight = Math.floor(e / 3); - const enemyIconContainer = this.scene.add.container(0, 0); + const enemyIconContainer = globalScene.add.container(0, 0); enemyIconContainer.setScale(0.6); const isBoss = enemyData.boss; enemyData.boss = false; enemyData["player"] = true; - const enemy = enemyData.toPokemon(this.scene); - const enemyIcon = this.scene.addPokemonIcon(enemy, 0, 0, 0, 0); + const enemy = enemyData.toPokemon(); + const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); // Applying Terastallizing Type tint to Pokemon icon // If the Pokemon is a fusion, it has two sprites and so, the tint has to be applied to each icon separately const enemySprite1 = enemyIcon.list[0] as Phaser.GameObjects.Sprite; @@ -457,7 +457,7 @@ export default class RunInfoUiHandler extends UiHandler { } } enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight)); - const enemyLevel = addTextObject(this.scene, 43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }); + const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }); enemyLevel.setShadow(0, 0, undefined); enemyLevel.setStroke("#424242", 14); enemyLevel.setOrigin(0, 0); @@ -480,7 +480,7 @@ export default class RunInfoUiHandler extends UiHandler { private async parseRunInfo(windowX: number, windowY: number) { // Parsing and displaying the mode. // In the future, parsing Challenges + Challenge Rules may have to be reworked as PokeRogue adds additional challenges and users can stack these challenges in various ways. - const modeText = addBBCodeTextObject(this.scene, 7, 0, "", TextStyle.WINDOW, { fontSize : "50px", lineSpacing:3 }); + const modeText = addBBCodeTextObject(7, 0, "", TextStyle.WINDOW, { fontSize : "50px", lineSpacing:3 }); modeText.setPosition(7, 5); modeText.appendText(i18next.t("runHistory:mode") + ": ", false); switch (this.runInfo.gameMode) { @@ -514,27 +514,27 @@ export default class RunInfoUiHandler extends UiHandler { } // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. - if ((this.runInfo.gameMode === GameModes.ENDLESS || this.runInfo.gameMode === GameModes.SPLICED_ENDLESS) && this.runInfo.waveIndex === this.scene.gameData.gameStats.highestEndlessWave) { + if ((this.runInfo.gameMode === GameModes.ENDLESS || this.runInfo.gameMode === GameModes.SPLICED_ENDLESS) && this.runInfo.waveIndex === globalScene.gameData.gameStats.highestEndlessWave) { modeText.appendText(` [${i18next.t("runHistory:personalBest")}]`); modeText.setTint(0xffef5c, 0x47ff69, 0x6b6bff, 0xff6969); } // Duration + Money - const runInfoTextContainer = this.scene.add.container(0, 0); + const runInfoTextContainer = globalScene.add.container(0, 0); // Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12. const lineSpacing = (i18next.resolvedLanguage === "ja") ? 12 : 3; - const runInfoText = addBBCodeTextObject(this.scene, 7, 0, "", TextStyle.WINDOW, { fontSize: "50px", lineSpacing: lineSpacing }); + const runInfoText = addBBCodeTextObject(7, 0, "", TextStyle.WINDOW, { fontSize: "50px", lineSpacing: lineSpacing }); const runTime = Utils.getPlayTimeString(this.runInfo.playTime); runInfoText.appendText(`${i18next.t("runHistory:runLength")}: ${runTime}`, false); - const runMoney = Utils.formatMoney(this.scene.moneyFormat, this.runInfo.money); - const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, this.scene.uiTheme); + const runMoney = Utils.formatMoney(globalScene.moneyFormat, this.runInfo.money); + const moneyTextColor = getTextColor(TextStyle.MONEY_WINDOW, false, globalScene.uiTheme); runInfoText.appendText(`[color=${moneyTextColor}]${i18next.t("battleScene:moneyOwned", { formattedMoney : runMoney })}[/color]`); runInfoText.setPosition(7, 70); runInfoTextContainer.add(runInfoText); // Luck // Uses the parameters windowX and windowY to dynamically position the luck value neatly into the bottom right corner - const luckText = addBBCodeTextObject(this.scene, 0, 0, "", TextStyle.WINDOW, { fontSize: "55px" }); - const luckValue = Phaser.Math.Clamp(this.runInfo.party.map(p => p.toPokemon(this.scene).getLuck()).reduce((total: integer, value: integer) => total += value, 0), 0, 14); + const luckText = addBBCodeTextObject(0, 0, "", TextStyle.WINDOW, { fontSize: "55px" }); + const luckValue = Phaser.Math.Clamp(this.runInfo.party.map(p => p.toPokemon().getLuck()).reduce((total: integer, value: integer) => total += value, 0), 0, 14); let luckInfo = i18next.t("runHistory:luck") + ": " + getLuckString(luckValue); if (luckValue < 14) { luckInfo = "[color=#" + (getLuckTextTint(luckValue)).toString(16) + "]" + luckInfo + "[/color]"; @@ -550,14 +550,14 @@ export default class RunInfoUiHandler extends UiHandler { if (this.runInfo.modifiers.length) { let visibleModifierIndex = 0; - const modifierIconsContainer = this.scene.add.container(8, (this.runInfo.gameMode === GameModes.CHALLENGE) ? 20 : 15); + const modifierIconsContainer = globalScene.add.container(8, (this.runInfo.gameMode === GameModes.CHALLENGE) ? 20 : 15); modifierIconsContainer.setScale(0.45); for (const m of this.runInfo.modifiers) { - const modifier = m.toModifier(this.scene, this.modifiersModule[m.className]); + const modifier = m.toModifier(this.modifiersModule[m.className]); if (modifier instanceof Modifier.PokemonHeldItemModifier) { continue; } - const icon = modifier?.getIcon(this.scene, false); + const icon = modifier?.getIcon(false); if (icon) { const rowHeightModifier = Math.floor(visibleModifierIndex / 7); icon.setPosition(24 * (visibleModifierIndex % 7), 20 + (35 * rowHeightModifier)); @@ -565,7 +565,7 @@ export default class RunInfoUiHandler extends UiHandler { } if (++visibleModifierIndex === 20) { - const maxItems = addTextObject(this.scene, 45, 90, "+", TextStyle.WINDOW); + const maxItems = addTextObject(45, 90, "+", TextStyle.WINDOW); maxItems.setPositionRelative(modifierIconsContainer, 70, 45); this.runInfoContainer.add(maxItems); break; @@ -619,13 +619,13 @@ export default class RunInfoUiHandler extends UiHandler { private parsePartyInfo(): void { const party = this.runInfo.party; const currentLanguage = i18next.resolvedLanguage ?? "en"; - const windowHeight = ((this.scene.game.canvas.height / 6) - 23) / 6; + const windowHeight = ((globalScene.game.canvas.height / 6) - 23) / 6; party.forEach((p: PokemonData, i: integer) => { - const pokemonInfoWindow = new RoundRectangle(this.scene, 0, 14, (this.statsBgWidth * 2) + 10, windowHeight - 2, 3); + const pokemonInfoWindow = new RoundRectangle(globalScene, 0, 14, (this.statsBgWidth * 2) + 10, windowHeight - 2, 3); - const pokemon = p.toPokemon(this.scene); - const pokemonInfoContainer = this.scene.add.container(this.statsBgWidth + 5, (windowHeight - 0.5) * i); + const pokemon = p.toPokemon(); + const pokemonInfoContainer = globalScene.add.container(this.statsBgWidth + 5, (windowHeight - 0.5) * i); const types = pokemon.getTypes(); const type1 = getTypeRgb(types[0]); @@ -634,8 +634,8 @@ export default class RunInfoUiHandler extends UiHandler { const bgColor = type1Color.clone().darken(45); pokemonInfoWindow.setFillStyle(bgColor.color); - const iconContainer = this.scene.add.container(0, 0); - const icon = this.scene.addPokemonIcon(pokemon, 0, 0, 0, 0); + const iconContainer = globalScene.add.container(0, 0); + const icon = globalScene.addPokemonIcon(pokemon, 0, 0, 0, 0); icon.setScale(0.75); icon.setPosition(-99, 1); const type2 = types[1] ? getTypeRgb(types[1]) : undefined; @@ -645,7 +645,7 @@ export default class RunInfoUiHandler extends UiHandler { this.getUi().bringToTop(icon); // Contains Name, Level + Nature, Ability, Passive - const pokeInfoTextContainer = this.scene.add.container(-85, 3.5); + const pokeInfoTextContainer = globalScene.add.container(-85, 3.5); const textContainerFontSize = "34px"; // This checks if the Pokemon's nature has been overwritten during the run and displays the change accurately const pNature = pokemon.getNature(); @@ -664,7 +664,7 @@ export default class RunInfoUiHandler extends UiHandler { const pAbilityInfo = abilityLabel + ": " + pokemon.getAbility().name; // Japanese is set to a greater line spacing of 35px in addBBCodeTextObject() if lineSpacing < 12. const lineSpacing = (i18next.resolvedLanguage === "ja") ? 12 : 3; - const pokeInfoText = addBBCodeTextObject(this.scene, 0, 0, pName, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); + const pokeInfoText = addBBCodeTextObject(0, 0, pName, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); pokeInfoText.appendText(`${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatFancyLargeNumber(pokemon.level, 1)} - ${pNatureName}`); pokeInfoText.appendText(pAbilityInfo); pokeInfoText.appendText(pPassiveInfo); @@ -672,7 +672,7 @@ export default class RunInfoUiHandler extends UiHandler { // Pokemon Stats // Colored Arrows (Red/Blue) are placed by stats that are boosted from natures - const pokeStatTextContainer = this.scene.add.container(-35, 6); + const pokeStatTextContainer = globalScene.add.container(-35, 6); const pStats : string[] = []; pokemon.stats.forEach((element) => pStats.push(Utils.formatFancyLargeNumber(element, 1))); for (let i = 0; i < pStats.length; i++) { @@ -688,20 +688,20 @@ export default class RunInfoUiHandler extends UiHandler { const speedLabel = (currentLanguage === "es-ES" || currentLanguage === "pt_BR") ? i18next.t("runHistory:SPDshortened") : i18next.t("pokemonInfo:Stat.SPDshortened"); const speed = speedLabel + ": " + pStats[5]; // Column 1: HP Atk Def - const pokeStatText1 = addBBCodeTextObject(this.scene, -5, 0, hp, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); + const pokeStatText1 = addBBCodeTextObject(-5, 0, hp, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); pokeStatText1.appendText(atk); pokeStatText1.appendText(def); pokeStatTextContainer.add(pokeStatText1); // Column 2: SpAtk SpDef Speed - const pokeStatText2 = addBBCodeTextObject(this.scene, 25, 0, spatk, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); + const pokeStatText2 = addBBCodeTextObject(25, 0, spatk, TextStyle.SUMMARY, { fontSize: textContainerFontSize, lineSpacing: lineSpacing }); pokeStatText2.appendText(spdef); pokeStatText2.appendText(speed); pokeStatTextContainer.add(pokeStatText2); // Shiny + Fusion Status - const marksContainer = this.scene.add.container(0, 0); + const marksContainer = globalScene.add.container(0, 0); if (pokemon.fusionSpecies) { - const splicedIcon = this.scene.add.image(0, 0, "icon_spliced"); + const splicedIcon = globalScene.add.image(0, 0, "icon_spliced"); splicedIcon.setScale(0.35); splicedIcon.setOrigin(0, 0); pokemon.isShiny() ? splicedIcon.setPositionRelative(pokeInfoTextContainer, 35, 0) : splicedIcon.setPositionRelative(pokeInfoTextContainer, 28, 0); @@ -710,7 +710,7 @@ export default class RunInfoUiHandler extends UiHandler { } if (pokemon.isShiny()) { const doubleShiny = pokemon.isFusion() && pokemon.shiny && pokemon.fusionShiny; - const shinyStar = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); + const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`); shinyStar.setOrigin(0, 0); shinyStar.setScale(0.65); shinyStar.setPositionRelative(pokeInfoTextContainer, 28, 0); @@ -718,7 +718,7 @@ export default class RunInfoUiHandler extends UiHandler { marksContainer.add(shinyStar); this.getUi().bringToTop(shinyStar); if (doubleShiny) { - const fusionShinyStar = this.scene.add.image(0, 0, "shiny_star_small_2"); + const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2"); fusionShinyStar.setOrigin(0, 0); fusionShinyStar.setScale(0.5); fusionShinyStar.setPosition(shinyStar.x + 1, shinyStar.y + 1); @@ -731,16 +731,16 @@ export default class RunInfoUiHandler extends UiHandler { // Pokemon Moveset // Need to check if dynamically typed moves const pokemonMoveset = pokemon.getMoveset(); - const movesetContainer = this.scene.add.container(70, -29); + const movesetContainer = globalScene.add.container(70, -29); const pokemonMoveBgs : Phaser.GameObjects.NineSlice[] = []; const pokemonMoveLabels : Phaser.GameObjects.Text[] = []; const movePos = [[ -6.5, 35.5 ], [ 37, 35.5 ], [ -6.5, 43.5 ], [ 37, 43.5 ]]; for (let m = 0; m < pokemonMoveset?.length; m++) { - const moveContainer = this.scene.add.container(movePos[m][0], movePos[m][1]); + const moveContainer = globalScene.add.container(movePos[m][0], movePos[m][1]); moveContainer.setScale(0.5); - const moveBg = this.scene.add.nineslice(0, 0, "type_bgs", "unknown", 85, 15, 2, 2, 2, 2); + const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 85, 15, 2, 2, 2, 2); moveBg.setOrigin(1, 0); - const moveLabel = addTextObject(this.scene, -moveBg.width / 2, 2, "-", TextStyle.PARTY); + const moveLabel = addTextObject(-moveBg.width / 2, 2, "-", TextStyle.PARTY); moveLabel.setOrigin(0.5, 0); moveLabel.setName("text-move-label"); pokemonMoveBgs.push(moveBg); @@ -756,11 +756,11 @@ export default class RunInfoUiHandler extends UiHandler { // Pokemon Held Items - not displayed by default // Endless/Endless Spliced have a different scale because Pokemon tend to accumulate more items in these runs. const heldItemsScale = (this.runInfo.gameMode === GameModes.SPLICED_ENDLESS || this.runInfo.gameMode === GameModes.ENDLESS) ? 0.25 : 0.5; - const heldItemsContainer = this.scene.add.container(-82, 2); + const heldItemsContainer = globalScene.add.container(-82, 2); const heldItemsList : Modifier.PokemonHeldItemModifier[] = []; if (this.runInfo.modifiers.length) { for (const m of this.runInfo.modifiers) { - const modifier = m.toModifier(this.scene, this.modifiersModule[m.className]); + const modifier = m.toModifier(this.modifiersModule[m.className]); if (modifier instanceof Modifier.PokemonHeldItemModifier && modifier.pokemonId === pokemon.id) { modifier.stackCount = m["stackCount"]; heldItemsList.push(modifier); @@ -771,11 +771,11 @@ export default class RunInfoUiHandler extends UiHandler { let row = 0; for (const [ index, item ] of heldItemsList.entries()) { if ( index > 36 ) { - const overflowIcon = addTextObject(this.scene, 182, 4, "+", TextStyle.WINDOW); + const overflowIcon = addTextObject(182, 4, "+", TextStyle.WINDOW); heldItemsContainer.add(overflowIcon); break; } - const itemIcon = item?.getIcon(this.scene, true); + const itemIcon = item?.getIcon(true); if (item?.stackCount < item?.getMaxHeldItemCount(pokemon) && itemIcon.list[1] instanceof Phaser.GameObjects.BitmapText) { itemIcon.list[1].clearTint(); } @@ -833,13 +833,13 @@ export default class RunInfoUiHandler extends UiHandler { * Shows the ending art. */ private createVictorySplash(): void { - this.endCardContainer = this.scene.add.container(0, 0); - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + this.endCardContainer = globalScene.add.container(0, 0); + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const isFemale = genderIndex === PlayerGender.FEMALE; - const endCard = this.scene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`); + const endCard = globalScene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`); endCard.setOrigin(0); endCard.setScale(0.5); - const text = addTextObject(this.scene, this.scene.game.canvas.width / 12, (this.scene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); + const text = addTextObject(globalScene.game.canvas.width / 12, (globalScene.game.canvas.height / 6) - 16, i18next.t("battle:congratulations"), TextStyle.SUMMARY, { fontSize: "128px" }); text.setOrigin(0.5); this.endCardContainer.add(endCard); this.endCardContainer.add(text); @@ -850,45 +850,45 @@ export default class RunInfoUiHandler extends UiHandler { * This could be adapted into a public-facing method for victory screens. Perhaps. */ private createHallofFame(): void { - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const isFemale = genderIndex === PlayerGender.FEMALE; const genderStr = PlayerGender[genderIndex].toLowerCase(); // Issue Note (08-05-2024): It seems as if fused pokemon do not appear with the averaged color b/c pokemonData's loadAsset requires there to be some active battle? // As an alternative, the icons of the second/bottom fused Pokemon have been placed next to their fellow fused Pokemon in Hall of Fame - this.hallofFameContainer = this.scene.add.container(0, 0); + this.hallofFameContainer = globalScene.add.container(0, 0); // Thank you Hayuna for the code - const endCard = this.scene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`); + const endCard = globalScene.add.image(0, 0, `end_${isFemale ? "f" : "m"}`); endCard.setOrigin(0); endCard.setPosition(-1, -1); endCard.setScale(0.5); const endCardCoords = endCard.getBottomCenter(); const overlayColor = isFemale ? "red" : "blue"; - const hallofFameBg = this.scene.add.image(0, 0, "hall_of_fame_" + overlayColor); + const hallofFameBg = globalScene.add.image(0, 0, "hall_of_fame_" + overlayColor); hallofFameBg.setPosition(159, 89); - hallofFameBg.setSize(this.scene.game.canvas.width, this.scene.game.canvas.height + 10); + hallofFameBg.setSize(globalScene.game.canvas.width, globalScene.game.canvas.height + 10); hallofFameBg.setAlpha(0.8); this.hallofFameContainer.add(endCard); this.hallofFameContainer.add(hallofFameBg); - const hallofFameText = addTextObject(this.scene, 0, 0, i18next.t("runHistory:hallofFameText", { context: genderStr }), TextStyle.WINDOW); + const hallofFameText = addTextObject(0, 0, i18next.t("runHistory:hallofFameText", { context: genderStr }), TextStyle.WINDOW); hallofFameText.setPosition(endCardCoords.x - (hallofFameText.displayWidth / 2), 164); this.hallofFameContainer.add(hallofFameText); this.runInfo.party.forEach((p, i) => { - const pkmn = p.toPokemon(this.scene); + const pkmn = p.toPokemon(); const row = i % 2; const id = pkmn.id; const shiny = pkmn.shiny; const formIndex = pkmn.formIndex; const variant = pkmn.variant; const species = pkmn.getSpeciesForm(); - const pokemonSprite: Phaser.GameObjects.Sprite = this.scene.add.sprite(60 + 40 * i, 40 + row * 80, "pkmn__sub"); - pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); + const pokemonSprite: Phaser.GameObjects.Sprite = globalScene.add.sprite(60 + 40 * i, 40 + row * 80, "pkmn__sub"); + pokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.hallofFameContainer.add(pokemonSprite); const speciesLoaded: Map = new Map(); speciesLoaded.set(id, false); const female = pkmn.gender === 1; - species.loadAssets(this.scene, female, formIndex, shiny, variant, true).then(() => { + species.loadAssets(female, formIndex, shiny, variant, true).then(() => { speciesLoaded.set(id, true); pokemonSprite.play(species.getSpriteKey(female, formIndex, shiny, variant)); pokemonSprite.setPipelineData("shiny", shiny); @@ -897,7 +897,7 @@ export default class RunInfoUiHandler extends UiHandler { pokemonSprite.setVisible(true); }); if (pkmn.isFusion()) { - const fusionIcon = this.scene.add.sprite(80 + 40 * i, 50 + row * 80, pkmn.getFusionIconAtlasKey()); + const fusionIcon = globalScene.add.sprite(80 + 40 * i, 50 + row * 80, pkmn.getFusionIconAtlasKey()); fusionIcon.setName("sprite-fusion-icon"); fusionIcon.setOrigin(0.5, 0); fusionIcon.setFrame(pkmn.getFusionIconId(true)); diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index b36c0af6ec8..13f5020e5ad 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -1,10 +1,10 @@ import i18next from "i18next"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { Button } from "#enums/buttons"; import { GameMode } from "../game-mode"; import * as Modifier from "../modifier/modifier"; -import { SessionSaveData } from "../system/game-data"; -import PokemonData from "../system/pokemon-data"; +import type { SessionSaveData } from "../system/game-data"; +import type PokemonData from "../system/pokemon-data"; import * as Utils from "../utils"; import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; @@ -39,35 +39,35 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { private sessionSlotsContainerInitialY: number; - constructor(scene: BattleScene) { - super(scene, Mode.SAVE_SLOT); + constructor() { + super(Mode.SAVE_SLOT); } setup() { const ui = this.getUi(); - this.saveSlotSelectContainer = this.scene.add.container(0, 0); + this.saveSlotSelectContainer = globalScene.add.container(0, 0); this.saveSlotSelectContainer.setVisible(false); ui.add(this.saveSlotSelectContainer); - const loadSessionBg = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, -this.scene.game.canvas.height / 6, 0x006860); + const loadSessionBg = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, -globalScene.game.canvas.height / 6, 0x006860); loadSessionBg.setOrigin(0, 0); this.saveSlotSelectContainer.add(loadSessionBg); - this.sessionSlotsContainerInitialY = -this.scene.game.canvas.height / 6 + 8; + this.sessionSlotsContainerInitialY = -globalScene.game.canvas.height / 6 + 8; - this.sessionSlotsContainer = this.scene.add.container(8, this.sessionSlotsContainerInitialY); + this.sessionSlotsContainer = globalScene.add.container(8, this.sessionSlotsContainerInitialY); this.saveSlotSelectContainer.add(this.sessionSlotsContainer); - this.saveSlotSelectMessageBoxContainer = this.scene.add.container(0, 0); + this.saveSlotSelectMessageBoxContainer = globalScene.add.container(0, 0); this.saveSlotSelectMessageBoxContainer.setVisible(false); this.saveSlotSelectContainer.add(this.saveSlotSelectMessageBoxContainer); - this.saveSlotSelectMessageBox = addWindow(this.scene, 1, -1, 318, 28); + this.saveSlotSelectMessageBox = addWindow(1, -1, 318, 28); this.saveSlotSelectMessageBox.setOrigin(0, 1); this.saveSlotSelectMessageBoxContainer.add(this.saveSlotSelectMessageBox); - this.message = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); this.message.setOrigin(0, 0); this.saveSlotSelectMessageBoxContainer.add(this.message); @@ -122,9 +122,9 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { if (this.sessionSlots[cursor].hasData) { ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.gameData.deleteSession(cursor).then(response => { + globalScene.gameData.deleteSession(cursor).then(response => { if (response === false) { - this.scene.reset(true); + globalScene.reset(true); } else { saveAndCallback(); } @@ -168,7 +168,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { break; case Button.RIGHT: if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + globalScene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); success = true; } } @@ -185,8 +185,8 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { populateSessionSlots() { for (let s = 0; s < SESSION_SLOTS_COUNT; s++) { - const sessionSlot = new SessionSlot(this.scene, s); - this.scene.add.existing(sessionSlot); + const sessionSlot = new SessionSlot(s); + globalScene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); sessionSlot.load().then((success) => { @@ -223,9 +223,9 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { const changed = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.container(0, 0); - const cursorBox = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); - const rightArrow = this.scene.add.image(0, 0, "cursor"); + this.cursorObj = globalScene.add.container(0, 0); + const cursorBox = globalScene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); + const rightArrow = globalScene.add.image(0, 0, "cursor"); rightArrow.setPosition(160, 0); rightArrow.setName("rightArrow"); this.cursorObj.add([ cursorBox, rightArrow ]); @@ -286,7 +286,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { if (changed) { this.scrollCursor = scrollCursor; this.setCursor(this.cursor, prevSlotIndex); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, duration: Utils.fixedInt(325), @@ -326,8 +326,8 @@ class SessionSlot extends Phaser.GameObjects.Container { public saveData: SessionSaveData; - constructor(scene: BattleScene, slotId: integer) { - super(scene, 0, slotId * 56); + constructor(slotId: integer) { + super(globalScene, 0, slotId * 56); this.slotId = slotId; @@ -335,10 +335,10 @@ class SessionSlot extends Phaser.GameObjects.Container { } setup() { - const slotWindow = addWindow(this.scene, 0, 0, 304, 52); + const slotWindow = addWindow(0, 0, 304, 52); this.add(slotWindow); - this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); + this.loadingLabel = addTextObject(152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); this.add(this.loadingLabel); } @@ -346,24 +346,24 @@ class SessionSlot extends Phaser.GameObjects.Container { async setupWithData(data: SessionSaveData) { this.remove(this.loadingLabel, true); - const gameModeLabel = addTextObject(this.scene, 8, 5, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW); + const gameModeLabel = addTextObject(8, 5, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW); this.add(gameModeLabel); - const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); + const timestampLabel = addTextObject(8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); this.add(timestampLabel); - const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); this.add(playTimeLabel); - const pokemonIconsContainer = this.scene.add.container(144, 4); + const pokemonIconsContainer = globalScene.add.container(144, 4); data.party.forEach((p: PokemonData, i: integer) => { - const iconContainer = this.scene.add.container(26 * i, 0); + const iconContainer = globalScene.add.container(26 * i, 0); iconContainer.setScale(0.75); - const pokemon = p.toPokemon(this.scene); - const icon = this.scene.addPokemonIcon(pokemon, 0, 0, 0, 0); + const pokemon = p.toPokemon(); + const icon = globalScene.addPokemonIcon(pokemon, 0, 0, 0, 0); - const text = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + const text = addTextObject(32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(pokemon.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); text.setShadow(0, 0, undefined); text.setStroke("#424242", 14); text.setOrigin(1, 0); @@ -378,15 +378,15 @@ class SessionSlot extends Phaser.GameObjects.Container { this.add(pokemonIconsContainer); - const modifierIconsContainer = this.scene.add.container(148, 30); + const modifierIconsContainer = globalScene.add.container(148, 30); modifierIconsContainer.setScale(0.5); let visibleModifierIndex = 0; for (const m of data.modifiers) { - const modifier = m.toModifier(this.scene, Modifier[m.className]); + const modifier = m.toModifier(Modifier[m.className]); if (modifier instanceof Modifier.PokemonHeldItemModifier) { continue; } - const icon = modifier?.getIcon(this.scene, false); + const icon = modifier?.getIcon(false); if (icon) { icon.setPosition(24 * visibleModifierIndex, 0); modifierIconsContainer.add(icon); @@ -401,7 +401,7 @@ class SessionSlot extends Phaser.GameObjects.Container { load(): Promise { return new Promise(resolve => { - this.scene.gameData.getSession(this.slotId).then(async sessionData => { + globalScene.gameData.getSession(this.slotId).then(async sessionData => { // Ignore the results if the view was exited if (!this.active) { return; @@ -420,7 +420,3 @@ class SessionSlot extends Phaser.GameObjects.Container { }); } } - -interface SessionSlot { - scene: BattleScene; -} diff --git a/src/ui/saving-icon-handler.ts b/src/ui/saving-icon-handler.ts index f62b0dc6162..ca7b1fb1aa8 100644 --- a/src/ui/saving-icon-handler.ts +++ b/src/ui/saving-icon-handler.ts @@ -1,4 +1,4 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import * as Utils from "../utils"; export default class SavingIconHandler extends Phaser.GameObjects.Container { @@ -7,12 +7,12 @@ export default class SavingIconHandler extends Phaser.GameObjects.Container { private animActive: boolean; private shown: boolean; - constructor(scene: BattleScene) { - super(scene, scene.game.canvas.width / 6 - 4, scene.game.canvas.height / 6 - 4); + constructor() { + super(globalScene, globalScene.game.canvas.width / 6 - 4, globalScene.game.canvas.height / 6 - 4); } setup(): void { - this.icon = this.scene.add.sprite(0, 0, "saving_icon"); + this.icon = globalScene.add.sprite(0, 0, "saving_icon"); this.icon.setOrigin(1, 1); this.add(this.icon); @@ -33,13 +33,13 @@ export default class SavingIconHandler extends Phaser.GameObjects.Container { this.animActive = true; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, alpha: 1, duration: Utils.fixedInt(250), ease: "Sine.easeInOut", onComplete: () => { - this.scene.time.delayedCall(Utils.fixedInt(500), () => { + globalScene.time.delayedCall(Utils.fixedInt(500), () => { this.animActive = false; if (!this.shown) { this.hide(); @@ -61,7 +61,7 @@ export default class SavingIconHandler extends Phaser.GameObjects.Container { this.animActive = true; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this, alpha: 0, duration: Utils.fixedInt(250), diff --git a/src/ui/scroll-bar.ts b/src/ui/scroll-bar.ts index 9874be0f73a..eb74bfddfde 100644 --- a/src/ui/scroll-bar.ts +++ b/src/ui/scroll-bar.ts @@ -1,3 +1,5 @@ +import { globalScene } from "#app/global-scene"; + /** * A vertical scrollbar element that resizes dynamically based on the current scrolling * and number of elements that can be shown on screen @@ -11,15 +13,14 @@ export class ScrollBar extends Phaser.GameObjects.Container { private maxRows: number; /** - * @param scene the current scene * @param x the scrollbar's x position (origin: top left) * @param y the scrollbar's y position (origin: top left) * @param width the scrollbar's width * @param height the scrollbar's height * @param maxRows the maximum number of rows that can be shown at once */ - constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number, maxRows: number) { - super(scene, x, y); + constructor(x: number, y: number, width: number, height: number, maxRows: number) { + super(globalScene, x, y); this.maxRows = maxRows; this.totalRows = maxRows; @@ -28,15 +29,15 @@ export class ScrollBar extends Phaser.GameObjects.Container { const borderSize = 2; width = Math.max(width, 4); - this.bg = scene.add.nineslice(0, 0, "scroll_bar", undefined, width, height, borderSize, borderSize, borderSize, borderSize); + this.bg = globalScene.add.nineslice(0, 0, "scroll_bar", undefined, width, height, borderSize, borderSize, borderSize, borderSize); this.bg.setOrigin(0, 0); this.add(this.bg); - this.handleBody = scene.add.rectangle(1, 1, width - 2, 4, 0xaaaaaa); + this.handleBody = globalScene.add.rectangle(1, 1, width - 2, 4, 0xaaaaaa); this.handleBody.setOrigin(0, 0); this.add(this.handleBody); - this.handleBottom = scene.add.nineslice(1, 1, "scroll_bar_handle", undefined, width - 2, 2, 2, 0, 0, 0); + this.handleBottom = globalScene.add.nineslice(1, 1, "scroll_bar_handle", undefined, width - 2, 2, 2, 0, 0, 0); this.handleBottom.setOrigin(0, 0); this.add(this.handleBottom); } diff --git a/src/ui/scrollable-grid-handler.ts b/src/ui/scrollable-grid-handler.ts index cced92a2083..04851ec002f 100644 --- a/src/ui/scrollable-grid-handler.ts +++ b/src/ui/scrollable-grid-handler.ts @@ -1,6 +1,6 @@ import { Button } from "#enums/buttons"; -import UiHandler from "#app/ui/ui-handler"; -import { ScrollBar } from "#app/ui/scroll-bar"; +import type UiHandler from "#app/ui/ui-handler"; +import type { ScrollBar } from "#app/ui/scroll-bar"; type UpdateGridCallbackFunction = () => void; type UpdateDetailsCallbackFunction = (index: number) => void; @@ -24,15 +24,14 @@ export default class ScrollableGridUiHandler { private cursor: number; private scrollCursor: number; private scrollBar?: ScrollBar; + /** Optional function that will get called if the whole grid needs to get updated */ private updateGridCallback?: UpdateGridCallbackFunction; + /** Optional function that will get called if a single element's information needs to get updated */ private updateDetailsCallback?: UpdateDetailsCallbackFunction; /** - * @param scene the {@linkcode UiHandler} that needs its cursor updated based on the scrolling * @param rows the maximum number of rows shown at once * @param columns the maximum number of columns shown at once - * @param updateGridCallback optional function that will get called if the whole grid needs to get updated - * @param updateDetailsCallback optional function that will get called if a single element's information needs to get updated */ constructor(handler: UiHandler, rows: number, columns: number) { this.handler = handler; diff --git a/src/ui/session-reload-modal-ui-handler.ts b/src/ui/session-reload-modal-ui-handler.ts index 147634b19d2..640268f0480 100644 --- a/src/ui/session-reload-modal-ui-handler.ts +++ b/src/ui/session-reload-modal-ui-handler.ts @@ -1,11 +1,11 @@ -import BattleScene from "../battle-scene"; -import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; +import { ModalUiHandler } from "./modal-ui-handler"; import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; export default class SessionReloadModalUiHandler extends ModalUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } getModalTitle(): string { @@ -31,7 +31,7 @@ export default class SessionReloadModalUiHandler extends ModalUiHandler { setup(): void { super.setup(); - const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your session is out of date.\nYour data will be reloaded…", TextStyle.WINDOW, { fontSize: "48px", align: "center" }); + const label = addTextObject(this.getWidth() / 2, this.getHeight() / 2, "Your session is out of date.\nYour data will be reloaded…", TextStyle.WINDOW, { fontSize: "48px", align: "center" }); label.setOrigin(0.5, 0.5); this.modalContainer.add(label); diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index 9ebc3c493a4..d0f9a5c8a90 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -1,11 +1,11 @@ import UiHandler from "../ui-handler"; -import BattleScene from "../../battle-scene"; -import { Mode } from "../ui"; +import type { Mode } from "../ui"; import { addWindow } from "../ui-theme"; import { addTextObject, TextStyle } from "../text"; import { Button } from "#enums/buttons"; import { NavigationManager } from "#app/ui/settings/navigationMenu"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; type CancelFn = (succes?: boolean) => boolean; @@ -49,11 +49,10 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { /** * Constructor for the AbstractBindingUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); } /** @@ -61,8 +60,8 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { */ setup() { const ui = this.getUi(); - this.optionSelectContainer = this.scene.add.container(0, 0); - this.actionsContainer = this.scene.add.container(0, 0); + this.optionSelectContainer = globalScene.add.container(0, 0); + this.actionsContainer = globalScene.add.container(0, 0); // Initially, containers are not visible. this.optionSelectContainer.setVisible(false); this.actionsContainer.setVisible(false); @@ -72,30 +71,30 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { ui.add(this.actionsContainer); // Setup backgrounds and text objects for UI. - this.titleBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + 28 + 21, this.getWindowWidth(), 24); + this.titleBg = addWindow((globalScene.game.canvas.width / 6) - this.getWindowWidth(), -(globalScene.game.canvas.height / 6) + 28 + 21, this.getWindowWidth(), 24); this.titleBg.setOrigin(0.5); this.optionSelectContainer.add(this.titleBg); - this.actionBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28 + 21 + 21, this.getWindowWidth(), 24); + this.actionBg = addWindow((globalScene.game.canvas.width / 6) - this.getWindowWidth(), -(globalScene.game.canvas.height / 6) + this.getWindowHeight() + 28 + 21 + 21, this.getWindowWidth(), 24); this.actionBg.setOrigin(0.5); this.actionsContainer.add(this.actionBg); // Text prompts and instructions for the user. - this.unlockText = addTextObject(this.scene, 0, 0, i18next.t("settings:pressButton"), TextStyle.WINDOW); + this.unlockText = addTextObject(0, 0, i18next.t("settings:pressButton"), TextStyle.WINDOW); this.unlockText.setOrigin(0, 0); this.unlockText.setPositionRelative(this.titleBg, 36, 4); this.optionSelectContainer.add(this.unlockText); - this.timerText = addTextObject(this.scene, 0, 0, "(5)", TextStyle.WINDOW); + this.timerText = addTextObject(0, 0, "(5)", TextStyle.WINDOW); this.timerText.setOrigin(0, 0); this.timerText.setPositionRelative(this.unlockText, (this.unlockText.width / 6) + 5, 0); this.optionSelectContainer.add(this.timerText); - this.optionSelectBg = addWindow(this.scene, (this.scene.game.canvas.width / 6) - this.getWindowWidth(), -(this.scene.game.canvas.height / 6) + this.getWindowHeight() + 28, this.getWindowWidth(), this.getWindowHeight()); + this.optionSelectBg = addWindow((globalScene.game.canvas.width / 6) - this.getWindowWidth(), -(globalScene.game.canvas.height / 6) + this.getWindowHeight() + 28, this.getWindowWidth(), this.getWindowHeight()); this.optionSelectBg.setOrigin(0.5); this.optionSelectContainer.add(this.optionSelectBg); - this.cancelLabel = addTextObject(this.scene, 0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); + this.cancelLabel = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); this.cancelLabel.setOrigin(0, 0.5); this.cancelLabel.setPositionRelative(this.actionBg, 10, this.actionBg.height / 2); this.actionsContainer.add(this.cancelLabel); diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 69f8eb241d3..59af1abba2a 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -1,15 +1,15 @@ import UiHandler from "#app/ui/ui-handler"; -import BattleScene from "#app/battle-scene"; -import { Mode } from "#app/ui/ui"; -import { InterfaceConfig } from "#app/inputs-controller"; +import type { Mode } from "#app/ui/ui"; +import type { InterfaceConfig } from "#app/inputs-controller"; import { addWindow } from "#app/ui/ui-theme"; import { addTextObject, TextStyle } from "#app/ui/text"; import { ScrollBar } from "#app/ui/scroll-bar"; import { getIconWithSettingName } from "#app/configs/inputs/configHandler"; import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu"; -import { Device } from "#enums/devices"; +import type { Device } from "#enums/devices"; import { Button } from "#enums/buttons"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; export interface InputsIcons { [key: string]: Phaser.GameObjects.Sprite; @@ -67,16 +67,15 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler protected device: Device; abstract saveSettingToLocalStorage(setting, cursor): void; - abstract setSetting(scene: BattleScene, setting, value: number): boolean; + abstract setSetting(setting, value: number): boolean; /** * Constructor for the AbstractSettingsUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.rowsToDisplay = 8; } @@ -99,44 +98,44 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const ui = this.getUi(); this.navigationIcons = {}; - this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.settingsContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); this.settingsContainer.setName(`settings-${this.titleSelected}`); - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); - this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + this.navigationContainer = new NavigationMenu(0, 0); - this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); + this.optionsBg = addWindow(0, this.navigationContainer.height, (globalScene.game.canvas.width / 6) - 2, (globalScene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); this.optionsBg.setOrigin(0, 0); - this.actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + this.actionsBg = addWindow(0, (globalScene.game.canvas.height / 6) - this.navigationContainer.height, (globalScene.game.canvas.width / 6) - 2, 22); this.actionsBg.setOrigin(0, 0); - const iconAction = this.scene.add.sprite(0, 0, "keyboard"); + const iconAction = globalScene.add.sprite(0, 0, "keyboard"); iconAction.setOrigin(0, -0.1); iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); this.navigationIcons["BUTTON_ACTION"] = iconAction; - const actionText = addTextObject(this.scene, 0, 0, i18next.t("settings:action"), TextStyle.SETTINGS_LABEL); + const actionText = addTextObject(0, 0, i18next.t("settings:action"), TextStyle.SETTINGS_LABEL); actionText.setOrigin(0, 0.15); actionText.setPositionRelative(iconAction, -actionText.width / 6 - 2, 0); - const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); + const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); iconCancel.setOrigin(0, -0.1); iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); this.navigationIcons["BUTTON_CANCEL"] = iconCancel; - const cancelText = addTextObject(this.scene, 0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); + const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); cancelText.setOrigin(0, 0.15); cancelText.setPositionRelative(iconCancel, -cancelText.width / 6 - 2, 0); - const iconReset = this.scene.add.sprite(0, 0, "keyboard"); + const iconReset = globalScene.add.sprite(0, 0, "keyboard"); iconReset.setOrigin(0, -0.1); iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); this.navigationIcons["BUTTON_HOME"] = iconReset; - const resetText = addTextObject(this.scene, 0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL); + const resetText = addTextObject(0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL); resetText.setOrigin(0, 0.15); resetText.setPositionRelative(iconReset, -resetText.width / 6 - 2, 0); @@ -156,7 +155,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler this.layout[config.padType] = new Map(); // Create a container for gamepad options in the scene, initially hidden. - const optionsContainer = this.scene.add.container(0, 0); + const optionsContainer = globalScene.add.container(0, 0); optionsContainer.setVisible(false); // Gather all binding settings from the configuration. @@ -195,7 +194,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler } else { labelText = i18next.t(`settings:${i18nKey}`); } - settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, labelText, labelStyle); + settingLabels[s] = addTextObject(8, 28 + s * 16, labelText, labelStyle); settingLabels[s].setOrigin(0, 0); optionsContainer.add(settingLabels[s]); @@ -208,14 +207,14 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler if (bindingSettings.includes(this.setting[setting])) { // Create a label for non-null options, typically indicating actionable options like 'change'. if (o) { - const valueLabel = addTextObject(this.scene, 0, 0, isLock ? "" : option, TextStyle.WINDOW); + const valueLabel = addTextObject(0, 0, isLock ? "" : option, TextStyle.WINDOW); valueLabel.setOrigin(0, 0); optionsContainer.add(valueLabel); valueLabels.push(valueLabel); continue; } // For null options, add an icon for the key. - const icon = this.scene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); + const icon = globalScene.add.sprite(0, 0, this.textureOverride ? this.textureOverride : config.padType); icon.setOrigin(0, -0.15); inputsIcons[this.setting[setting]] = icon; optionsContainer.add(icon); @@ -223,7 +222,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler continue; } // For regular settings like 'Gamepad support', create a label and determine if it is selected. - const valueLabel = addTextObject(this.scene, 0, 0, option, this.settingDeviceDefaults[this.setting[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); + const valueLabel = addTextObject(0, 0, option, this.settingDeviceDefaults[this.setting[setting]] === o ? TextStyle.SETTINGS_SELECTED : TextStyle.WINDOW); valueLabel.setOrigin(0, 0); optionsContainer.add(valueLabel); @@ -273,7 +272,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler } // Add vertical scrollbar - this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); + this.scrollBar = new ScrollBar(this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); this.settingsContainer.add(this.scrollBar); // Add the settings container to the UI. @@ -289,7 +288,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler * @returns The active configuration for current device */ getActiveConfig(): InterfaceConfig { - return this.scene.inputController.getActiveConfig(this.device); + return globalScene.inputController.getActiveConfig(this.device); } /** @@ -349,9 +348,9 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler this.navigationIcons[settingName].alpha = 1; continue; } - const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + const icon = globalScene.inputController?.getIconForLatestInputRecorded(settingName); if (icon) { - const type = this.scene.inputController?.getLastSourceType(); + const type = globalScene.inputController?.getLastSourceType(); this.navigationIcons[settingName].setTexture(type); this.navigationIcons[settingName].setFrame(icon); this.navigationIcons[settingName].alpha = 1; @@ -444,7 +443,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Handle cancel button press, reverting UI mode to previous state. success = true; NavigationManager.getInstance().reset(); - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); } else { const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. const setting = this.setting[Object.keys(this.setting)[cursor]]; @@ -456,7 +455,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { success = false; } else { - success = this.setSetting(this.scene, setting, 1); + success = this.setSetting(setting, 1); } break; case Button.UP: // Move up in the menu. @@ -556,8 +555,8 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler // Check if the cursor object exists, if not, create it. if (!this.cursorObj) { - const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible ? 16 : 10); - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); + const cursorWidth = (globalScene.game.canvas.width / 6) - (this.scrollBar.visible ? 16 : 10); + this.cursorObj = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); // Set the origin to the top-left corner. this.optionsContainer.add(this.cursorObj); // Add the cursor to the options container. } diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index cbc93887810..a0287f80f79 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -1,14 +1,15 @@ -import BattleScene from "#app/battle-scene"; import { TextStyle, addTextObject } from "#app/ui/text"; import { Mode } from "#app/ui/ui"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { addWindow } from "#app/ui/ui-theme"; import { ScrollBar } from "#app/ui/scroll-bar"; import { Button } from "#enums/buttons"; -import { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler"; +import type { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler"; import NavigationMenu, { NavigationManager } from "#app/ui/settings/navigationMenu"; -import { Setting, SettingKeys, SettingType } from "#app/system/settings/settings"; +import type { SettingType } from "#app/system/settings/settings"; +import { Setting, SettingKeys } from "#app/system/settings/settings"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; /** @@ -42,8 +43,8 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { protected settings: Array; protected localStorageKey: string; - constructor(scene: BattleScene, type: SettingType, mode: Mode | null = null) { - super(scene, mode); + constructor(type: SettingType, mode: Mode | null = null) { + super(mode); this.settings = Setting.filter(s => s.type === type && !s?.isHidden?.()); this.reloadRequired = false; this.rowsToDisplay = 8; @@ -55,40 +56,40 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { setup() { const ui = this.getUi(); - this.settingsContainer = this.scene.add.container(1, -(this.scene.game.canvas.height / 6) + 1); + this.settingsContainer = globalScene.add.container(1, -(globalScene.game.canvas.height / 6) + 1); this.settingsContainer.setName(`settings-${this.title}`); - this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains); + this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6 - 20), Phaser.Geom.Rectangle.Contains); this.navigationIcons = {}; - this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + this.navigationContainer = new NavigationMenu(0, 0); - this.optionsBg = addWindow(this.scene, 0, this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); + this.optionsBg = addWindow(0, this.navigationContainer.height, (globalScene.game.canvas.width / 6) - 2, (globalScene.game.canvas.height / 6) - 16 - this.navigationContainer.height - 2); this.optionsBg.setName("window-options-bg"); this.optionsBg.setOrigin(0, 0); - const actionsBg = addWindow(this.scene, 0, (this.scene.game.canvas.height / 6) - this.navigationContainer.height, (this.scene.game.canvas.width / 6) - 2, 22); + const actionsBg = addWindow(0, (globalScene.game.canvas.height / 6) - this.navigationContainer.height, (globalScene.game.canvas.width / 6) - 2, 22); actionsBg.setOrigin(0, 0); - const iconAction = this.scene.add.sprite(0, 0, "keyboard"); + const iconAction = globalScene.add.sprite(0, 0, "keyboard"); iconAction.setOrigin(0, -0.1); iconAction.setPositionRelative(actionsBg, this.navigationContainer.width - 32, 4); this.navigationIcons["BUTTON_ACTION"] = iconAction; - const actionText = addTextObject(this.scene, 0, 0, i18next.t("settings:action"), TextStyle.SETTINGS_LABEL); + const actionText = addTextObject(0, 0, i18next.t("settings:action"), TextStyle.SETTINGS_LABEL); actionText.setOrigin(0, 0.15); actionText.setPositionRelative(iconAction, -actionText.width / 6 - 2, 0); - const iconCancel = this.scene.add.sprite(0, 0, "keyboard"); + const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); iconCancel.setOrigin(0, -0.1); iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); this.navigationIcons["BUTTON_CANCEL"] = iconCancel; - const cancelText = addTextObject(this.scene, 0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); + const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); cancelText.setOrigin(0, 0.15); cancelText.setPositionRelative(iconCancel, -cancelText.width / 6 - 2, 0); - this.optionsContainer = this.scene.add.container(0, 0); + this.optionsContainer = globalScene.add.container(0, 0); this.settingLabels = []; this.optionValueLabels = []; @@ -102,12 +103,12 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { settingName += ` (${i18next.t("settings:requireReload")})`; } - this.settingLabels[s] = addTextObject(this.scene, 8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); + this.settingLabels[s] = addTextObject(8, 28 + s * 16, settingName, TextStyle.SETTINGS_LABEL); this.settingLabels[s].setOrigin(0, 0); this.optionsContainer.add(this.settingLabels[s]); this.optionValueLabels.push(setting.options.map((option, o) => { - const valueLabel = addTextObject(this.scene, 0, 0, option.label, setting.default === o ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_VALUE); + const valueLabel = addTextObject(0, 0, option.label, setting.default === o ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_VALUE); valueLabel.setOrigin(0, 0); this.optionsContainer.add(valueLabel); @@ -132,20 +133,20 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.optionCursors = this.settings.map(setting => setting.default); - this.scrollBar = new ScrollBar(this.scene, this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); + this.scrollBar = new ScrollBar(this.optionsBg.width - 9, this.optionsBg.y + 5, 4, this.optionsBg.height - 11, this.rowsToDisplay); this.scrollBar.setTotalRows(this.settings.length); // Two-lines message box - this.messageBoxContainer = this.scene.add.container(0, this.scene.scaledCanvas.height); + this.messageBoxContainer = globalScene.add.container(0, globalScene.scaledCanvas.height); this.messageBoxContainer.setName("settings-message-box"); this.messageBoxContainer.setVisible(false); - const settingsMessageBox = addWindow(this.scene, 0, -1, this.scene.scaledCanvas.width - 2, 48); + const settingsMessageBox = addWindow(0, -1, globalScene.scaledCanvas.width - 2, 48); settingsMessageBox.setOrigin(0, 1); this.messageBoxContainer.add(settingsMessageBox); - const messageText = addTextObject(this.scene, 8, -40, "", TextStyle.WINDOW, { maxLines: 2 }); - messageText.setWordWrapWidth(this.scene.game.canvas.width - 60); + const messageText = addTextObject(8, -40, "", TextStyle.WINDOW, { maxLines: 2 }); + messageText.setWordWrapWidth(globalScene.game.canvas.width - 60); messageText.setName("settings-message"); messageText.setOrigin(0, 0); @@ -181,9 +182,9 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.navigationIcons[settingName].alpha = 1; continue; } - const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + const icon = globalScene.inputController?.getIconForLatestInputRecorded(settingName); if (icon) { - const type = this.scene.inputController?.getLastSourceType(); + const type = globalScene.inputController?.getLastSourceType(); this.navigationIcons[settingName].setTexture(type); this.navigationIcons[settingName].setFrame(icon); this.navigationIcons[settingName].alpha = 1; @@ -238,7 +239,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { success = true; NavigationManager.getInstance().reset(); // Reverts UI to its previous state on cancel. - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); } else { const cursor = this.cursor + this.scrollCursor; switch (button) { @@ -314,7 +315,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { activateSetting(setting: Setting): boolean { switch (setting.key) { case SettingKeys.Move_Touch_Controls: - this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); + globalScene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi()); return true; } return false; @@ -330,8 +331,8 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { const ret = super.setCursor(cursor); if (!this.cursorObj) { - const cursorWidth = (this.scene.game.canvas.width / 6) - (this.scrollBar.visible ? 16 : 10); - this.cursorObj = this.scene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); + const cursorWidth = (globalScene.game.canvas.width / 6) - (this.scrollBar.visible ? 16 : 10); + this.cursorObj = globalScene.add.nineslice(0, 0, "summary_moves_cursor", undefined, cursorWidth, 16, 1, 1, 1, 1); this.cursorObj.setOrigin(0, 0); this.optionsContainer.add(this.cursorObj); } @@ -369,7 +370,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { if (save) { const saveSetting = () => { - this.scene.gameData.saveSetting(setting.key, cursor); + globalScene.gameData.saveSetting(setting.key, cursor); if (setting.requireReload) { this.reloadRequired = true; } @@ -378,20 +379,20 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { // For settings that ask for confirmation, display confirmation message and a Yes/No prompt before saving the setting if (setting.options[cursor].needConfirmation) { const confirmUpdateSetting = () => { - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); this.showText(""); saveSetting(); }; const cancelUpdateSetting = () => { - this.scene.ui.revertMode(); + globalScene.ui.revertMode(); this.showText(""); // Put the cursor back to its previous position without saving or asking for confirmation again this.setOptionCursor(settingIndex, lastCursor, false); }; const confirmationMessage = setting.options[cursor].confirmationMessage ?? i18next.t("settings:defaultConfirmMessage"); - this.scene.ui.showText(confirmationMessage, null, () => { - this.scene.ui.setOverlayMode(Mode.CONFIRM, confirmUpdateSetting, cancelUpdateSetting, null, null, 1, 750); + globalScene.ui.showText(confirmationMessage, null, () => { + globalScene.ui.setOverlayMode(Mode.CONFIRM, confirmUpdateSetting, cancelUpdateSetting, null, null, 1, 750); }); } else { saveSetting(); @@ -445,10 +446,10 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler { this.settingsContainer.setVisible(false); this.setScrollCursor(0); this.eraseCursor(); - this.getUi().bgmBar.toggleBgmBar(this.scene.showBgmBar); + this.getUi().bgmBar.toggleBgmBar(globalScene.showBgmBar); if (this.reloadRequired) { this.reloadRequired = false; - this.scene.reset(true, false, true); + globalScene.reset(true, false, true); } } diff --git a/src/ui/settings/gamepad-binding-ui-handler.ts b/src/ui/settings/gamepad-binding-ui-handler.ts index e89529f6453..b69c2f34e06 100644 --- a/src/ui/settings/gamepad-binding-ui-handler.ts +++ b/src/ui/settings/gamepad-binding-ui-handler.ts @@ -1,37 +1,37 @@ -import BattleScene from "../../battle-scene"; import AbstractBindingUiHandler from "./abstract-binding-ui-handler"; -import { Mode } from "../ui"; +import type { Mode } from "../ui"; import { Device } from "#enums/devices"; import { getIconWithSettingName, getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { addTextObject, TextStyle } from "#app/ui/text"; +import { globalScene } from "#app/global-scene"; export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); - this.scene.input.gamepad?.on("down", this.gamepadButtonDown, this); + constructor(mode: Mode | null = null) { + super(mode); + globalScene.input.gamepad?.on("down", this.gamepadButtonDown, this); } setup() { super.setup(); // New button icon setup. - this.newButtonIcon = this.scene.add.sprite(0, 0, "xbox"); + this.newButtonIcon = globalScene.add.sprite(0, 0, "xbox"); this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 16); this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.swapText = addTextObject(this.scene, 0, 0, "will swap with", TextStyle.WINDOW); + this.swapText = addTextObject(0, 0, "will swap with", TextStyle.WINDOW); this.swapText.setOrigin(0.5); this.swapText.setPositionRelative(this.optionSelectBg, this.optionSelectBg.width / 2 - 2, this.optionSelectBg.height / 2 - 2); this.swapText.setVisible(false); - this.targetButtonIcon = this.scene.add.sprite(0, 0, "xbox"); + this.targetButtonIcon = globalScene.add.sprite(0, 0, "xbox"); this.targetButtonIcon.setPositionRelative(this.optionSelectBg, 78, 48); this.targetButtonIcon.setOrigin(0.5); this.targetButtonIcon.setVisible(false); - this.actionLabel = addTextObject(this.scene, 0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, "Confirm swap", TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 75, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); @@ -42,7 +42,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { } getSelectedDevice() { - return this.scene.inputController?.selectedDevice[Device.GAMEPAD]; + return globalScene.inputController?.selectedDevice[Device.GAMEPAD]; } gamepadButtonDown(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { @@ -51,7 +51,7 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { if (!this.listening || pad.id.toLowerCase() !== this.getSelectedDevice() || blacklist.includes(button.index) || this.buttonPressed !== null) { return; } - const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); + const activeConfig = globalScene.inputController.getActiveConfig(Device.GAMEPAD); const type = activeConfig.padType; const key = getKeyWithKeycode(activeConfig, button.index); const buttonIcon = activeConfig.icons[key]; @@ -64,9 +64,9 @@ export default class GamepadBindingUiHandler extends AbstractBindingUiHandler { } swapAction(): boolean { - const activeConfig = this.scene.inputController.getActiveConfig(Device.GAMEPAD); - if (this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { - this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + const activeConfig = globalScene.inputController.getActiveConfig(Device.GAMEPAD); + if (globalScene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + globalScene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); return true; } return false; diff --git a/src/ui/settings/keyboard-binding-ui-handler.ts b/src/ui/settings/keyboard-binding-ui-handler.ts index 52b1a0e385f..ddc60a15631 100644 --- a/src/ui/settings/keyboard-binding-ui-handler.ts +++ b/src/ui/settings/keyboard-binding-ui-handler.ts @@ -1,29 +1,29 @@ -import BattleScene from "../../battle-scene"; import AbstractBindingUiHandler from "./abstract-binding-ui-handler"; -import { Mode } from "../ui"; +import type { Mode } from "../ui"; import { getKeyWithKeycode } from "#app/configs/inputs/configHandler"; import { Device } from "#enums/devices"; import { addTextObject, TextStyle } from "#app/ui/text"; +import { globalScene } from "#app/global-scene"; export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); // Listen to gamepad button down events to initiate binding. - scene.input.keyboard?.on("keydown", this.onKeyDown, this); + globalScene.input.keyboard?.on("keydown", this.onKeyDown, this); } setup() { super.setup(); // New button icon setup. - this.newButtonIcon = this.scene.add.sprite(0, 0, "keyboard"); + this.newButtonIcon = globalScene.add.sprite(0, 0, "keyboard"); this.newButtonIcon.setPositionRelative(this.optionSelectBg, 78, 32); this.newButtonIcon.setOrigin(0.5); this.newButtonIcon.setVisible(false); - this.actionLabel = addTextObject(this.scene, 0, 0, "Assign button", TextStyle.SETTINGS_LABEL); + this.actionLabel = addTextObject(0, 0, "Assign button", TextStyle.SETTINGS_LABEL); this.actionLabel.setOrigin(0, 0.5); this.actionLabel.setPositionRelative(this.actionBg, this.actionBg.width - 80, this.actionBg.height / 2); this.actionsContainer.add(this.actionLabel); @@ -32,7 +32,7 @@ export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { } getSelectedDevice() { - return this.scene.inputController?.selectedDevice[Device.KEYBOARD]; + return globalScene.inputController?.selectedDevice[Device.KEYBOARD]; } onKeyDown(event): void { @@ -51,7 +51,7 @@ export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { if (!this.listening || this.buttonPressed !== null || blacklist.includes(key)) { return; } - const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); + const activeConfig = globalScene.inputController.getActiveConfig(Device.KEYBOARD); const _key = getKeyWithKeycode(activeConfig, key); const buttonIcon = activeConfig.icons[_key]; if (!buttonIcon) { @@ -63,9 +63,9 @@ export default class KeyboardBindingUiHandler extends AbstractBindingUiHandler { } swapAction(): boolean { - const activeConfig = this.scene.inputController.getActiveConfig(Device.KEYBOARD); - if (this.scene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { - this.scene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); + const activeConfig = globalScene.inputController.getActiveConfig(Device.KEYBOARD); + if (globalScene.inputController.assignBinding(activeConfig, this.target, this.buttonPressed)) { + globalScene.gameData.saveMappingConfigs(this.getSelectedDevice(), activeConfig); return true; } return false; diff --git a/src/ui/settings/move-touch-controls-handler.ts b/src/ui/settings/move-touch-controls-handler.ts index eda75a54a63..6128f5954e8 100644 --- a/src/ui/settings/move-touch-controls-handler.ts +++ b/src/ui/settings/move-touch-controls-handler.ts @@ -1,6 +1,6 @@ -import TouchControl from "#app/touch-controls"; -import UI from "#app/ui/ui"; -import { Scene } from "phaser"; +import { globalScene } from "#app/global-scene"; +import type TouchControl from "#app/touch-controls"; +import type UI from "#app/ui/ui"; export const TOUCH_CONTROL_POSITIONS_LANDSCAPE = "touchControlPositionsLandscape"; export const TOUCH_CONTROL_POSITIONS_PORTRAIT = "touchControlPositionsPortrait"; @@ -319,11 +319,10 @@ export default class MoveTouchControlsHandler { * Creates an overlay that covers the screen and allows the user to drag the touch controls around. * Also enables the toolbar for saving, resetting, and canceling the changes. * @param ui The UI of the game. - * @param scene The scene of the game. */ - private createOverlay(ui: UI, scene: Scene) { - const container = new Phaser.GameObjects.Container(scene, 0, 0); - const overlay = new Phaser.GameObjects.Rectangle(scene, 0, 0, scene.game.canvas.width, scene.game.canvas.height, 0x000000, 0.5); + private createOverlay(ui: UI) { + const container = new Phaser.GameObjects.Container(globalScene, 0, 0); + const overlay = new Phaser.GameObjects.Rectangle(globalScene, 0, 0, globalScene.game.canvas.width, globalScene.game.canvas.height, 0x000000, 0.5); overlay.setInteractive(); container.add(overlay); ui.add(container); @@ -336,15 +335,14 @@ export default class MoveTouchControlsHandler { /** * Allows the user to configure the touch controls by dragging buttons around the screen. * @param ui The UI of the game. - * @param scene The scene of the game. */ - public enableConfigurationMode(ui: UI, scene: Scene) { + public enableConfigurationMode(ui: UI) { if (this.inConfigurationMode) { return; } this.inConfigurationMode = true; this.touchControls.disable(); - this.createOverlay(ui, scene); + this.createOverlay(ui); this.createToolbar(); // Create event listeners with a delay to prevent the touchstart event from being triggered immediately. setTimeout(() => { diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index ab86fa1569a..eeb6da319ef 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -1,6 +1,6 @@ -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Mode } from "#app/ui/ui"; -import { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler"; +import type { InputsIcons } from "#app/ui/settings/abstract-control-settings-ui-handler"; import { addTextObject, setTextStyle, TextStyle } from "#app/ui/text"; import { addWindow } from "#app/ui/ui-theme"; import { Button } from "#enums/buttons"; @@ -23,7 +23,7 @@ export class NavigationManager { * Creates an instance of NavigationManager. * To create a new tab in the menu, add the mode to the modes array and the label to the labels array. * and instantiate a new NavigationMenu instance in your handler - * like: this.navigationContainer = new NavigationMenu(this.scene, 0, 0); + * like: this.navigationContainer = new NavigationMenu(0, 0); */ constructor() { this.modes = [ @@ -54,10 +54,9 @@ export class NavigationManager { /** * Navigates modes based on given direction - * @param scene The current BattleScene instance * @param direction LEFT or RIGHT */ - public navigate(scene, direction) { + public navigate(direction) { const pos = this.modes.indexOf(this.selectedMode); const maxPos = this.modes.length - 1; const increment = direction === LEFT ? -1 : 1; @@ -68,7 +67,7 @@ export class NavigationManager { } else { this.selectedMode = this.modes[pos + increment]; } - scene.ui.setMode(this.selectedMode); + globalScene.ui.setMode(this.selectedMode); this.updateNavigationMenus(); } @@ -94,18 +93,15 @@ export class NavigationManager { export default class NavigationMenu extends Phaser.GameObjects.Container { private navigationIcons: InputsIcons; - public scene: BattleScene; protected headerTitles: Phaser.GameObjects.Text[] = new Array(); /** * Creates an instance of NavigationMenu. - * @param scene The current BattleScene instance. * @param x The x position of the NavigationMenu. * @param y The y position of the NavigationMenu. */ - constructor(scene: BattleScene, x: number, y: number) { - super(scene, x, y); - this.scene = scene; + constructor(x: number, y: number) { + super(globalScene, x, y); this.setup(); } @@ -115,7 +111,7 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { */ setup() { const navigationManager = NavigationManager.getInstance(); - const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); + const headerBg = addWindow(0, 0, (globalScene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); this.add(headerBg); this.width = headerBg.width; @@ -123,12 +119,12 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { this.navigationIcons = {}; - const iconPreviousTab = this.scene.add.sprite(8, 4, "keyboard"); + const iconPreviousTab = globalScene.add.sprite(8, 4, "keyboard"); iconPreviousTab.setOrigin(0, -0.1); iconPreviousTab.setPositionRelative(headerBg, 8, 4); this.navigationIcons["BUTTON_CYCLE_FORM"] = iconPreviousTab; - const iconNextTab = this.scene.add.sprite(0, 0, "keyboard"); + const iconNextTab = globalScene.add.sprite(0, 0, "keyboard"); iconNextTab.setOrigin(0, -0.1); iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4); this.navigationIcons["BUTTON_CYCLE_SHINY"] = iconNextTab; @@ -136,7 +132,7 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { let relative: Phaser.GameObjects.Sprite | Phaser.GameObjects.Text = iconPreviousTab; let relativeWidth: number = iconPreviousTab.width * 6; for (const label of navigationManager.labels) { - const labelText = addTextObject(this.scene, 0, 0, label, TextStyle.SETTINGS_LABEL); + const labelText = addTextObject(0, 0, label, TextStyle.SETTINGS_LABEL); labelText.setOrigin(0, 0); labelText.setPositionRelative(relative, 6 + relativeWidth / 6, 0); this.add(labelText); @@ -159,7 +155,7 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { const posSelected = navigationManager.modes.indexOf(navigationManager.selectedMode); for (const [ index, title ] of this.headerTitles.entries()) { - setTextStyle(title, this.scene, index === posSelected ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); + setTextStyle(title, index === posSelected ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); } } @@ -178,9 +174,9 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { this.navigationIcons[settingName].alpha = 1; continue; } - const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + const icon = globalScene.inputController?.getIconForLatestInputRecorded(settingName); if (icon) { - const type = this.scene.inputController?.getLastSourceType(); + const type = globalScene.inputController?.getLastSourceType(); this.navigationIcons[settingName].setTexture(type); this.navigationIcons[settingName].setFrame(icon); this.navigationIcons[settingName].alpha = 1; @@ -199,10 +195,10 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { const navigationManager = NavigationManager.getInstance(); switch (button) { case Button.CYCLE_FORM: - navigationManager.navigate(this.scene, LEFT); + navigationManager.navigate(LEFT); return true; case Button.CYCLE_SHINY: - navigationManager.navigate(this.scene, RIGHT); + navigationManager.navigate(RIGHT); return true; } return false; diff --git a/src/ui/settings/option-select-ui-handler.ts b/src/ui/settings/option-select-ui-handler.ts index 8d2c534476a..5deaba5cfc5 100644 --- a/src/ui/settings/option-select-ui-handler.ts +++ b/src/ui/settings/option-select-ui-handler.ts @@ -1,10 +1,9 @@ -import BattleScene from "../../battle-scene"; import AbstractOptionSelectUiHandler from "../abstact-option-select-ui-handler"; import { Mode } from "../ui"; export default class OptionSelectUiHandler extends AbstractOptionSelectUiHandler { - constructor(scene: BattleScene, mode: Mode = Mode.OPTION_SELECT) { - super(scene, mode); + constructor(mode: Mode = Mode.OPTION_SELECT) { + super(mode); } getWindowWidth(): integer { diff --git a/src/ui/settings/settings-audio-ui-handler.ts b/src/ui/settings/settings-audio-ui-handler.ts index 86c6a9bce40..3b591eab4b9 100644 --- a/src/ui/settings/settings-audio-ui-handler.ts +++ b/src/ui/settings/settings-audio-ui-handler.ts @@ -1,18 +1,16 @@ -import BattleScene from "../../battle-scene"; -import { Mode } from "../ui"; -"#app/inputs-controller"; +import type { Mode } from "../ui"; import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; import { SettingType } from "#app/system/settings/settings"; +"#app/inputs-controller"; export default class SettingsAudioUiHandler extends AbstractSettingsUiHandler { /** * Creates an instance of SettingsAudioUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode, optional. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, SettingType.AUDIO, mode); + constructor(mode: Mode | null = null) { + super(SettingType.AUDIO, mode); this.title = "Audio"; this.localStorageKey = "settings"; this.rowsToDisplay = 6; diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index c4cbb0dfe58..98fc16e2f96 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -1,18 +1,16 @@ -import BattleScene from "../../battle-scene"; -import { Mode } from "../ui"; -"#app/inputs-controller"; +import type { Mode } from "../ui"; import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; import { SettingKeys, SettingType } from "#app/system/settings/settings"; +"#app/inputs-controller"; export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler { /** * Creates an instance of SettingsGamepadUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode, optional. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, SettingType.DISPLAY, mode); + constructor(mode: Mode | null = null) { + super(SettingType.DISPLAY, mode); this.title = "Display"; /** diff --git a/src/ui/settings/settings-gamepad-ui-handler.ts b/src/ui/settings/settings-gamepad-ui-handler.ts index 864142e055b..3d23b7e99bb 100644 --- a/src/ui/settings/settings-gamepad-ui-handler.ts +++ b/src/ui/settings/settings-gamepad-ui-handler.ts @@ -1,6 +1,5 @@ -import BattleScene from "../../battle-scene"; import { addTextObject, TextStyle } from "../text"; -import { Mode } from "../ui"; +import type { Mode } from "../ui"; import { setSettingGamepad, SettingGamepad, @@ -11,11 +10,12 @@ import { import pad_xbox360 from "#app/configs/inputs/pad_xbox360"; import pad_dualshock from "#app/configs/inputs/pad_dualshock"; import pad_unlicensedSNES from "#app/configs/inputs/pad_unlicensedSNES"; -import { InterfaceConfig } from "#app/inputs-controller"; +import type { InterfaceConfig } from "#app/inputs-controller"; import AbstractControlSettingsUiHandler from "#app/ui/settings/abstract-control-settings-ui-handler"; import { Device } from "#enums/devices"; import { truncateString } from "#app/utils"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; /** * Class representing the settings UI handler for gamepads. @@ -28,11 +28,10 @@ export default class SettingsGamepadUiHandler extends AbstractControlSettingsUiH /** * Creates an instance of SettingsGamepadUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode, optional. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.titleSelected = "Gamepad"; this.setting = SettingGamepad; this.settingDeviceDefaults = settingGamepadDefaults; @@ -53,9 +52,9 @@ export default class SettingsGamepadUiHandler extends AbstractControlSettingsUiH super.setup(); // If no gamepads are detected, set up a default UI prompt in the settings container. this.layout["noGamepads"] = new Map(); - const optionsContainer = this.scene.add.container(0, 0); + const optionsContainer = globalScene.add.container(0, 0); optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. - const label = addTextObject(this.scene, 8, 28, i18next.t("settings:gamepadPleasePlug"), TextStyle.SETTINGS_LABEL); + const label = addTextObject(8, 28, i18next.t("settings:gamepadPleasePlug"), TextStyle.SETTINGS_LABEL); label.setOrigin(0, 0); optionsContainer.add(label); this.settingsContainer.add(optionsContainer); @@ -107,7 +106,7 @@ export default class SettingsGamepadUiHandler extends AbstractControlSettingsUiH // Update the text of the first option label under the current setting to the name of the chosen gamepad, // truncating the name to 30 characters if necessary. - this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.GAMEPAD], 20)); + this.layout[_key].optionValueLabels[index][0].setText(truncateString(globalScene.inputController.selectedDevice[Device.GAMEPAD], 20)); } } } @@ -121,7 +120,7 @@ export default class SettingsGamepadUiHandler extends AbstractControlSettingsUiH */ saveSettingToLocalStorage(settingName, cursor): void { if (this.setting[settingName] !== this.setting.Controller) { - this.scene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); + globalScene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); } } } diff --git a/src/ui/settings/settings-keyboard-ui-handler.ts b/src/ui/settings/settings-keyboard-ui-handler.ts index 17d91b27c57..ad9f23cc0d9 100644 --- a/src/ui/settings/settings-keyboard-ui-handler.ts +++ b/src/ui/settings/settings-keyboard-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "../../battle-scene"; import { Mode } from "../ui"; import cfg_keyboard_qwerty from "#app/configs/inputs/cfg_keyboard_qwerty"; import { @@ -10,12 +9,13 @@ import { } from "#app/system/settings/settings-keyboard"; import { reverseValueToKeySetting, truncateString } from "#app/utils"; import AbstractControlSettingsUiHandler from "#app/ui/settings/abstract-control-settings-ui-handler"; -import { InterfaceConfig } from "#app/inputs-controller"; +import type { InterfaceConfig } from "#app/inputs-controller"; import { addTextObject, TextStyle } from "#app/ui/text"; import { deleteBind } from "#app/configs/inputs/configHandler"; import { Device } from "#enums/devices"; import { NavigationManager } from "#app/ui/settings/navigationMenu"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; /** * Class representing the settings UI handler for keyboards. @@ -26,11 +26,10 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi /** * Creates an instance of SettingsKeyboardUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode, optional. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.titleSelected = "Keyboard"; this.setting = SettingKeyboard; this.settingDeviceDefaults = settingKeyboardDefaults; @@ -42,8 +41,8 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi this.settingBlacklisted = settingKeyboardBlackList; this.device = Device.KEYBOARD; - const deleteEvent = scene.input.keyboard?.addKey(Phaser.Input.Keyboard.KeyCodes.DELETE); - const restoreDefaultEvent = scene.input.keyboard?.addKey(Phaser.Input.Keyboard.KeyCodes.HOME); + const deleteEvent = globalScene.input.keyboard?.addKey(Phaser.Input.Keyboard.KeyCodes.DELETE); + const restoreDefaultEvent = globalScene.input.keyboard?.addKey(Phaser.Input.Keyboard.KeyCodes.HOME); deleteEvent && deleteEvent.on("up", this.onDeleteDown, this); restoreDefaultEvent && restoreDefaultEvent.on("up", this.onHomeDown, this); } @@ -57,19 +56,19 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi super.setup(); // If no gamepads are detected, set up a default UI prompt in the settings container. this.layout["noKeyboard"] = new Map(); - const optionsContainer = this.scene.add.container(0, 0); + const optionsContainer = globalScene.add.container(0, 0); optionsContainer.setVisible(false); // Initially hide the container as no gamepads are connected. - const label = addTextObject(this.scene, 8, 28, i18next.t("settings:keyboardPleasePress"), TextStyle.SETTINGS_LABEL); + const label = addTextObject(8, 28, i18next.t("settings:keyboardPleasePress"), TextStyle.SETTINGS_LABEL); label.setOrigin(0, 0); optionsContainer.add(label); this.settingsContainer.add(optionsContainer); - const iconDelete = this.scene.add.sprite(0, 0, "keyboard"); + const iconDelete = globalScene.add.sprite(0, 0, "keyboard"); iconDelete.setOrigin(0, -0.1); iconDelete.setPositionRelative(this.actionsBg, this.navigationContainer.width - 260, 4); this.navigationIcons["BUTTON_DELETE"] = iconDelete; - const deleteText = addTextObject(this.scene, 0, 0, i18next.t("settings:delete"), TextStyle.SETTINGS_LABEL); + const deleteText = addTextObject(0, 0, i18next.t("settings:delete"), TextStyle.SETTINGS_LABEL); deleteText.setOrigin(0, 0.15); deleteText.setPositionRelative(iconDelete, -deleteText.width / 6 - 2, 0); @@ -86,10 +85,10 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi * Handle the home key press event. */ onHomeDown(): void { - if (![ Mode.SETTINGS_KEYBOARD, Mode.SETTINGS_GAMEPAD ].includes(this.scene.ui.getMode())) { + if (![ Mode.SETTINGS_KEYBOARD, Mode.SETTINGS_GAMEPAD ].includes(globalScene.ui.getMode())) { return; } - this.scene.gameData.resetMappingToFactory(); + globalScene.gameData.resetMappingToFactory(); NavigationManager.getInstance().updateIcons(); } @@ -97,7 +96,7 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi * Handle the delete key press event. */ onDeleteDown(): void { - if (this.scene.ui.getMode() !== Mode.SETTINGS_KEYBOARD) { + if (globalScene.ui.getMode() !== Mode.SETTINGS_KEYBOARD) { return; } const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. @@ -153,7 +152,7 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi } // Skip updating the no gamepad layout. // Update the text of the first option label under the current setting to the name of the chosen gamepad, // truncating the name to 30 characters if necessary. - this.layout[_key].optionValueLabels[index][0].setText(truncateString(this.scene.inputController.selectedDevice[Device.KEYBOARD], 22)); + this.layout[_key].optionValueLabels[index][0].setText(truncateString(globalScene.inputController.selectedDevice[Device.KEYBOARD], 22)); } } } @@ -166,7 +165,7 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi * @param config - The configuration to save. */ saveCustomKeyboardMappingToLocalStorage(config): void { - this.scene.gameData.saveMappingConfigs(this.scene.inputController?.selectedDevice[Device.KEYBOARD], config); + globalScene.gameData.saveMappingConfigs(globalScene.inputController?.selectedDevice[Device.KEYBOARD], config); } /** @@ -177,7 +176,7 @@ export default class SettingsKeyboardUiHandler extends AbstractControlSettingsUi */ saveSettingToLocalStorage(settingName, cursor): void { if (this.setting[settingName] !== this.setting.Default_Layout) { - this.scene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); + globalScene.gameData.saveControlSetting(this.device, this.localStoragePropertyName, settingName, this.settingDeviceDefaults, cursor); } } } diff --git a/src/ui/settings/settings-ui-handler.ts b/src/ui/settings/settings-ui-handler.ts index 3c5a7dd2c90..22ea76d798b 100644 --- a/src/ui/settings/settings-ui-handler.ts +++ b/src/ui/settings/settings-ui-handler.ts @@ -1,17 +1,15 @@ -import BattleScene from "../../battle-scene"; import { SettingType } from "../../system/settings/settings"; -import { Mode } from "../ui"; +import type { Mode } from "../ui"; import AbstractSettingsUiHandler from "./abstract-settings-ui-handler"; export default class SettingsUiHandler extends AbstractSettingsUiHandler { /** * Creates an instance of SettingsGamepadUiHandler. * - * @param scene - The BattleScene instance. * @param mode - The UI mode, optional. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, SettingType.GENERAL, mode); + constructor(mode: Mode | null = null) { + super(SettingType.GENERAL, mode); this.title = "General"; this.localStorageKey = "settings"; } diff --git a/src/ui/starter-container.ts b/src/ui/starter-container.ts index ce21d13add8..792ce97e103 100644 --- a/src/ui/starter-container.ts +++ b/src/ui/starter-container.ts @@ -1,9 +1,8 @@ -import BattleScene from "../battle-scene"; -import PokemonSpecies from "../data/pokemon-species"; +import { globalScene } from "#app/global-scene"; +import type 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[] = []; @@ -16,16 +15,16 @@ export class StarterContainer extends Phaser.GameObjects.Container { public candyUpgradeOverlayIcon: Phaser.GameObjects.Image; public cost: number = 0; - constructor(scene: BattleScene, species: PokemonSpecies) { - super(scene, 0, 0); + constructor(species: PokemonSpecies) { + super(globalScene, 0, 0); this.species = species; - const defaultDexAttr = scene.gameData.getSpeciesDefaultDexAttr(species, false, true); - const defaultProps = scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true); + const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); // starter passive bg - const starterPassiveBg = this.scene.add.image(2, 5, "passive_bg"); + const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg"); starterPassiveBg.setOrigin(0, 0); starterPassiveBg.setScale(0.75); starterPassiveBg.setVisible(false); @@ -33,7 +32,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.starterPassiveBgs = starterPassiveBg; // icon - this.icon = this.scene.add.sprite(-2, 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + this.icon = globalScene.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)); @@ -43,7 +42,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { // shiny icons for (let i = 0; i < 3; i++) { - const shinyIcon = this.scene.add.image(i * -3 + 12, 2, "shiny_star_small"); + const shinyIcon = globalScene.add.image(i * -3 + 12, 2, "shiny_star_small"); shinyIcon.setScale(0.5); shinyIcon.setOrigin(0, 0); shinyIcon.setVisible(false); @@ -52,7 +51,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.add(this.shinyIcons); // value label - const label = addTextObject(this.scene, 1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" }); + const label = addTextObject(1, 2, "0", TextStyle.WINDOW, { fontSize: "32px" }); label.setShadowOffset(2, 2); label.setOrigin(0, 0); label.setVisible(false); @@ -60,7 +59,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.label = label; // hidden ability icon - const abilityIcon = this.scene.add.image(12, 7, "ha_capsule"); + const abilityIcon = globalScene.add.image(12, 7, "ha_capsule"); abilityIcon.setOrigin(0, 0); abilityIcon.setScale(0.5); abilityIcon.setVisible(false); @@ -68,7 +67,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.hiddenAbilityIcon = abilityIcon; // favorite icon - const favoriteIcon = this.scene.add.image(0, 7, "favorite"); + const favoriteIcon = globalScene.add.image(0, 7, "favorite"); favoriteIcon.setOrigin(0, 0); favoriteIcon.setScale(0.5); favoriteIcon.setVisible(false); @@ -76,7 +75,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.favoriteIcon = favoriteIcon; // classic win icon - const classicWinIcon = this.scene.add.image(0, 12, "champion_ribbon"); + const classicWinIcon = globalScene.add.image(0, 12, "champion_ribbon"); classicWinIcon.setOrigin(0, 0); classicWinIcon.setScale(0.5); classicWinIcon.setVisible(false); @@ -84,7 +83,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.classicWinIcon = classicWinIcon; // candy upgrade icon - const candyUpgradeIcon = this.scene.add.image(12, 12, "candy"); + const candyUpgradeIcon = globalScene.add.image(12, 12, "candy"); candyUpgradeIcon.setOrigin(0, 0); candyUpgradeIcon.setScale(0.25); candyUpgradeIcon.setVisible(false); @@ -92,7 +91,7 @@ export class StarterContainer extends Phaser.GameObjects.Container { this.candyUpgradeIcon = candyUpgradeIcon; // candy upgrade overlay icon - const candyUpgradeOverlayIcon = this.scene.add.image(12, 12, "candy_overlay"); + const candyUpgradeOverlayIcon = globalScene.add.image(12, 12, "candy_overlay"); candyUpgradeOverlayIcon.setOrigin(0, 0); candyUpgradeOverlayIcon.setScale(0.25); candyUpgradeOverlayIcon.setVisible(false); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 38a2bb85de6..29c58d7087e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,10 +1,13 @@ -import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; +import type { CandyUpgradeNotificationChangedEvent } from "#app/events/battle-scene"; +import { BattleSceneEventType } from "#app/events/battle-scene"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; -import { Variant, getVariantTint, getVariantIcon } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; +import { getVariantTint, getVariantIcon } from "#app/data/variant"; import { argbFromRgba } from "@material/material-color-utilities"; import i18next from "i18next"; -import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; -import BattleScene, { starterColors } from "#app/battle-scene"; +import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; +import { starterColors } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { allAbilities } from "#app/data/ability"; import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { GrowthRate, getGrowthRateColor } from "#app/data/exp"; @@ -12,15 +15,18 @@ import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender"; import { allMoves } from "#app/data/move"; import { getNatureName } from "#app/data/nature"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; -import { LevelMoves, pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; -import PokemonSpecies, { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; +import type { LevelMoves } from "#app/data/balance/pokemon-level-moves"; +import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves"; +import type PokemonSpecies from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data/pokemon-species"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { Type } from "#enums/type"; import { GameModes } from "#app/game-mode"; -import { AbilityAttr, DexAttr, DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences, StarterPrefs } from "#app/system/game-data"; +import type { DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences } from "#app/system/game-data"; +import { AbilityAttr, DexAttr, StarterPrefs } from "#app/system/game-data"; import { Tutorial, handleTutorial } from "#app/tutorial"; -import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { StatsContainer } from "#app/ui/stats-container"; @@ -35,7 +41,7 @@ import * as Challenge from "#app/data/challenge"; import MoveInfoOverlay from "#app/ui/move-info-overlay"; import { getEggTierForSpecies } from "#app/data/egg"; import { Device } from "#enums/devices"; -import { Moves } from "#enums/moves"; +import type { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Button } from "#enums/buttons"; import { EggSourceType } from "#enums/egg-source-types"; @@ -336,8 +342,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { protected blockInput: boolean = false; - constructor(scene: BattleScene) { - super(scene, Mode.STARTER_SELECT); + constructor() { + super(Mode.STARTER_SELECT); } setup() { @@ -346,50 +352,50 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en"; const textSettings = languageSettings[langSettingKey]; - this.starterSelectContainer = this.scene.add.container(0, -this.scene.game.canvas.height / 6); + this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6); this.starterSelectContainer.setVisible(false); ui.add(this.starterSelectContainer); - const bgColor = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0x006860); + const bgColor = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860); bgColor.setOrigin(0, 0); this.starterSelectContainer.add(bgColor); - const starterSelectBg = this.scene.add.image(0, 0, "starter_select_bg"); + const starterSelectBg = globalScene.add.image(0, 0, "starter_select_bg"); starterSelectBg.setOrigin(0, 0); this.starterSelectContainer.add(starterSelectBg); - this.shinyOverlay = this.scene.add.image(6, 6, "summary_overlay_shiny"); + this.shinyOverlay = globalScene.add.image(6, 6, "summary_overlay_shiny"); this.shinyOverlay.setOrigin(0, 0); this.shinyOverlay.setVisible(false); this.starterSelectContainer.add(this.shinyOverlay); - const starterContainerWindow = addWindow(this.scene, speciesContainerX, filterBarHeight + 1, 175, 161); - const starterContainerBg = this.scene.add.image(speciesContainerX + 1, filterBarHeight + 2, "starter_container_bg"); + const starterContainerWindow = addWindow(speciesContainerX, filterBarHeight + 1, 175, 161); + const starterContainerBg = globalScene.add.image(speciesContainerX + 1, filterBarHeight + 2, "starter_container_bg"); starterContainerBg.setOrigin(0, 0); this.starterSelectContainer.add(starterContainerBg); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY - randomSelectionWindowHeight, teamWindowWidth, randomSelectionWindowHeight, true)); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight )); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY + teamWindowHeight, teamWindowWidth, teamWindowWidth, true)); + this.starterSelectContainer.add(addWindow(teamWindowX, teamWindowY - randomSelectionWindowHeight, teamWindowWidth, randomSelectionWindowHeight, true)); + this.starterSelectContainer.add(addWindow(teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight )); + this.starterSelectContainer.add(addWindow(teamWindowX, teamWindowY + teamWindowHeight, teamWindowWidth, teamWindowWidth, true)); this.starterSelectContainer.add(starterContainerWindow); // Create and initialise filter bar - this.filterBarContainer = this.scene.add.container(0, 0); - this.filterBar = new FilterBar(this.scene, Math.min(speciesContainerX, teamWindowX), 1, 210, filterBarHeight); + this.filterBarContainer = globalScene.add.container(0, 0); + this.filterBar = new FilterBar(Math.min(speciesContainerX, teamWindowX), 1, 210, filterBarHeight); // gen filter const genOptions: DropDownOption[] = [ - new DropDownOption(this.scene, 1, new DropDownLabel(i18next.t("starterSelectUiHandler:gen1"))), - new DropDownOption(this.scene, 2, new DropDownLabel(i18next.t("starterSelectUiHandler:gen2"))), - new DropDownOption(this.scene, 3, new DropDownLabel(i18next.t("starterSelectUiHandler:gen3"))), - new DropDownOption(this.scene, 4, new DropDownLabel(i18next.t("starterSelectUiHandler:gen4"))), - new DropDownOption(this.scene, 5, new DropDownLabel(i18next.t("starterSelectUiHandler:gen5"))), - new DropDownOption(this.scene, 6, new DropDownLabel(i18next.t("starterSelectUiHandler:gen6"))), - new DropDownOption(this.scene, 7, new DropDownLabel(i18next.t("starterSelectUiHandler:gen7"))), - new DropDownOption(this.scene, 8, new DropDownLabel(i18next.t("starterSelectUiHandler:gen8"))), - new DropDownOption(this.scene, 9, new DropDownLabel(i18next.t("starterSelectUiHandler:gen9"))), + new DropDownOption(1, new DropDownLabel(i18next.t("starterSelectUiHandler:gen1"))), + new DropDownOption(2, new DropDownLabel(i18next.t("starterSelectUiHandler:gen2"))), + new DropDownOption(3, new DropDownLabel(i18next.t("starterSelectUiHandler:gen3"))), + new DropDownOption(4, new DropDownLabel(i18next.t("starterSelectUiHandler:gen4"))), + new DropDownOption(5, new DropDownLabel(i18next.t("starterSelectUiHandler:gen5"))), + new DropDownOption(6, new DropDownLabel(i18next.t("starterSelectUiHandler:gen6"))), + new DropDownOption(7, new DropDownLabel(i18next.t("starterSelectUiHandler:gen7"))), + new DropDownOption(8, new DropDownLabel(i18next.t("starterSelectUiHandler:gen8"))), + new DropDownOption(9, new DropDownLabel(i18next.t("starterSelectUiHandler:gen9"))), ]; - const genDropDown: DropDown = new DropDown(this.scene, 0, 0, genOptions, this.updateStarters, DropDownType.HYBRID); + const genDropDown: DropDown = new DropDown(0, 0, genOptions, this.updateStarters, DropDownType.HYBRID); this.filterBar.addFilter(DropDownColumn.GEN, i18next.t("filterBar:genFilter"), genDropDown); // type filter @@ -399,39 +405,39 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (index === 0 || index === 19) { return; } - const typeSprite = this.scene.add.sprite(0, 0, getLocalizedSpriteKey("types")); + const typeSprite = globalScene.add.sprite(0, 0, getLocalizedSpriteKey("types")); typeSprite.setScale(0.5); typeSprite.setFrame(type.toLowerCase()); - typeOptions.push(new DropDownOption(this.scene, index, new DropDownLabel("", typeSprite))); + typeOptions.push(new DropDownOption(index, new DropDownLabel("", typeSprite))); }); - this.filterBar.addFilter(DropDownColumn.TYPES, i18next.t("filterBar:typeFilter"), new DropDown(this.scene, 0, 0, typeOptions, this.updateStarters, DropDownType.HYBRID, 0.5)); + this.filterBar.addFilter(DropDownColumn.TYPES, i18next.t("filterBar:typeFilter"), new DropDown(0, 0, typeOptions, this.updateStarters, DropDownType.HYBRID, 0.5)); // caught filter - const shiny1Sprite = this.scene.add.sprite(0, 0, "shiny_icons"); + const shiny1Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); shiny1Sprite.setOrigin(0.15, 0.2); shiny1Sprite.setScale(0.6); shiny1Sprite.setFrame(getVariantIcon(0)); shiny1Sprite.setTint(getVariantTint(0)); - const shiny2Sprite = this.scene.add.sprite(0, 0, "shiny_icons"); + const shiny2Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); shiny2Sprite.setOrigin(0.15, 0.2); shiny2Sprite.setScale(0.6); shiny2Sprite.setFrame(getVariantIcon(1)); shiny2Sprite.setTint(getVariantTint(1)); - const shiny3Sprite = this.scene.add.sprite(0, 0, "shiny_icons"); + const shiny3Sprite = globalScene.add.sprite(0, 0, "shiny_icons"); shiny3Sprite.setOrigin(0.15, 0.2); shiny3Sprite.setScale(0.6); shiny3Sprite.setFrame(getVariantIcon(2)); shiny3Sprite.setTint(getVariantTint(2)); const caughtOptions = [ - new DropDownOption(this.scene, "SHINY3", new DropDownLabel("", shiny3Sprite)), - new DropDownOption(this.scene, "SHINY2", new DropDownLabel("", shiny2Sprite)), - new DropDownOption(this.scene, "SHINY", new DropDownLabel("", shiny1Sprite)), - new DropDownOption(this.scene, "NORMAL", new DropDownLabel(i18next.t("filterBar:normal"))), - new DropDownOption(this.scene, "UNCAUGHT", new DropDownLabel(i18next.t("filterBar:uncaught"))) + new DropDownOption("SHINY3", new DropDownLabel("", shiny3Sprite)), + new DropDownOption("SHINY2", new DropDownLabel("", shiny2Sprite)), + new DropDownOption("SHINY", new DropDownLabel("", shiny1Sprite)), + new DropDownOption("NORMAL", new DropDownLabel(i18next.t("filterBar:normal"))), + new DropDownOption("UNCAUGHT", new DropDownLabel(i18next.t("filterBar:uncaught"))) ]; - this.filterBar.addFilter(DropDownColumn.CAUGHT, i18next.t("filterBar:caughtFilter"), new DropDown(this.scene, 0, 0, caughtOptions, this.updateStarters, DropDownType.HYBRID)); + this.filterBar.addFilter(DropDownColumn.CAUGHT, i18next.t("filterBar:caughtFilter"), new DropDown(0, 0, caughtOptions, this.updateStarters, DropDownType.HYBRID)); // unlocks filter const passiveLabels = [ @@ -449,11 +455,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ]; const unlocksOptions = [ - new DropDownOption(this.scene, "PASSIVE", passiveLabels), - new DropDownOption(this.scene, "COST_REDUCTION", costReductionLabels), + new DropDownOption("PASSIVE", passiveLabels), + new DropDownOption("COST_REDUCTION", costReductionLabels), ]; - this.filterBar.addFilter(DropDownColumn.UNLOCKS, i18next.t("filterBar:unlocksFilter"), new DropDown(this.scene, 0, 0, unlocksOptions, this.updateStarters, DropDownType.RADIAL)); + this.filterBar.addFilter(DropDownColumn.UNLOCKS, i18next.t("filterBar:unlocksFilter"), new DropDown(0, 0, unlocksOptions, this.updateStarters, DropDownType.RADIAL)); // misc filter const favoriteLabels = [ @@ -480,23 +486,23 @@ export default class StarterSelectUiHandler extends MessageUiHandler { new DropDownLabel(i18next.t("filterBar:hasPokerus"), undefined, DropDownState.ON), ]; const miscOptions = [ - new DropDownOption(this.scene, "FAVORITE", favoriteLabels), - new DropDownOption(this.scene, "WIN", winLabels), - new DropDownOption(this.scene, "HIDDEN_ABILITY", hiddenAbilityLabels), - new DropDownOption(this.scene, "EGG", eggLabels), - new DropDownOption(this.scene, "POKERUS", pokerusLabels), + new DropDownOption("FAVORITE", favoriteLabels), + new DropDownOption("WIN", winLabels), + new DropDownOption("HIDDEN_ABILITY", hiddenAbilityLabels), + new DropDownOption("EGG", eggLabels), + new DropDownOption("POKERUS", pokerusLabels), ]; - this.filterBar.addFilter(DropDownColumn.MISC, i18next.t("filterBar:miscFilter"), new DropDown(this.scene, 0, 0, miscOptions, this.updateStarters, DropDownType.RADIAL)); + this.filterBar.addFilter(DropDownColumn.MISC, i18next.t("filterBar:miscFilter"), new DropDown(0, 0, miscOptions, this.updateStarters, DropDownType.RADIAL)); // sort filter const sortOptions = [ - new DropDownOption(this.scene, SortCriteria.NUMBER, new DropDownLabel(i18next.t("filterBar:sortByNumber"), undefined, DropDownState.ON)), - new DropDownOption(this.scene, SortCriteria.COST, new DropDownLabel(i18next.t("filterBar:sortByCost"))), - new DropDownOption(this.scene, SortCriteria.CANDY, new DropDownLabel(i18next.t("filterBar:sortByCandies"))), - new DropDownOption(this.scene, SortCriteria.IV, new DropDownLabel(i18next.t("filterBar:sortByIVs"))), - new DropDownOption(this.scene, SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName"))) + new DropDownOption(SortCriteria.NUMBER, new DropDownLabel(i18next.t("filterBar:sortByNumber"), undefined, DropDownState.ON)), + new DropDownOption(SortCriteria.COST, new DropDownLabel(i18next.t("filterBar:sortByCost"))), + new DropDownOption(SortCriteria.CANDY, new DropDownLabel(i18next.t("filterBar:sortByCandies"))), + new DropDownOption(SortCriteria.IV, new DropDownLabel(i18next.t("filterBar:sortByIVs"))), + new DropDownOption(SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName"))) ]; - this.filterBar.addFilter(DropDownColumn.SORT, i18next.t("filterBar:sortFilter"), new DropDown(this.scene, 0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE)); + this.filterBar.addFilter(DropDownColumn.SORT, i18next.t("filterBar:sortFilter"), new DropDown(0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE)); this.filterBarContainer.add(this.filterBar); this.starterSelectContainer.add(this.filterBarContainer); @@ -504,35 +510,35 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Offset the generation filter dropdown to avoid covering the filtered pokemon this.filterBar.offsetHybridFilters(); - if (!this.scene.uiTheme) { + if (!globalScene.uiTheme) { starterContainerWindow.setVisible(false); } this.iconAnimHandler = new PokemonIconAnimHandler(); - this.iconAnimHandler.setup(this.scene); + this.iconAnimHandler.setup(); - this.pokemonNumberText = addTextObject(this.scene, 17, 1, "0000", TextStyle.SUMMARY); + this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY); this.pokemonNumberText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonNumberText); - this.pokemonNameText = addTextObject(this.scene, 6, 112, "", TextStyle.SUMMARY); + this.pokemonNameText = addTextObject(6, 112, "", TextStyle.SUMMARY); this.pokemonNameText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonNameText); - this.pokemonGrowthRateLabelText = addTextObject(this.scene, 8, 106, i18next.t("starterSelectUiHandler:growthRate"), TextStyle.SUMMARY_ALT, { fontSize: "36px" }); + this.pokemonGrowthRateLabelText = addTextObject(8, 106, i18next.t("starterSelectUiHandler:growthRate"), TextStyle.SUMMARY_ALT, { fontSize: "36px" }); this.pokemonGrowthRateLabelText.setOrigin(0, 0); this.pokemonGrowthRateLabelText.setVisible(false); this.starterSelectContainer.add(this.pokemonGrowthRateLabelText); - this.pokemonGrowthRateText = addTextObject(this.scene, 34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" }); + this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" }); this.pokemonGrowthRateText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonGrowthRateText); - this.pokemonGenderText = addTextObject(this.scene, 96, 112, "", TextStyle.SUMMARY_ALT); + this.pokemonGenderText = addTextObject(96, 112, "", TextStyle.SUMMARY_ALT); this.pokemonGenderText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonGenderText); - this.pokemonUncaughtText = addTextObject(this.scene, 6, 127, i18next.t("starterSelectUiHandler:uncaught"), TextStyle.SUMMARY_ALT, { fontSize: "56px" }); + this.pokemonUncaughtText = addTextObject(6, 127, i18next.t("starterSelectUiHandler:uncaught"), TextStyle.SUMMARY_ALT, { fontSize: "56px" }); this.pokemonUncaughtText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonUncaughtText); @@ -544,46 +550,46 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // The font size should be set per language const starterInfoTextSize = textSettings?.starterInfoTextSize || 56; - this.pokemonAbilityLabelText = addTextObject(this.scene, 6, 127 + starterInfoYOffset, i18next.t("starterSelectUiHandler:ability"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonAbilityLabelText = addTextObject(6, 127 + starterInfoYOffset, i18next.t("starterSelectUiHandler:ability"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonAbilityLabelText.setOrigin(0, 0); this.pokemonAbilityLabelText.setVisible(false); this.starterSelectContainer.add(this.pokemonAbilityLabelText); - this.pokemonAbilityText = addTextObject(this.scene, starterInfoXPos, 127 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonAbilityText = addTextObject(starterInfoXPos, 127 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonAbilityText.setOrigin(0, 0); this.pokemonAbilityText.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains); this.starterSelectContainer.add(this.pokemonAbilityText); - this.pokemonPassiveLabelText = addTextObject(this.scene, 6, 136 + starterInfoYOffset, i18next.t("starterSelectUiHandler:passive"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonPassiveLabelText = addTextObject(6, 136 + starterInfoYOffset, i18next.t("starterSelectUiHandler:passive"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonPassiveLabelText.setOrigin(0, 0); this.pokemonPassiveLabelText.setVisible(false); this.starterSelectContainer.add(this.pokemonPassiveLabelText); - this.pokemonPassiveText = addTextObject(this.scene, starterInfoXPos, 136 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonPassiveText = addTextObject(starterInfoXPos, 136 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonPassiveText.setOrigin(0, 0); this.pokemonPassiveText.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains); this.starterSelectContainer.add(this.pokemonPassiveText); - this.pokemonPassiveDisabledIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop"); + this.pokemonPassiveDisabledIcon = globalScene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop"); this.pokemonPassiveDisabledIcon.setOrigin(0, 0.5); this.pokemonPassiveDisabledIcon.setScale(0.35); this.pokemonPassiveDisabledIcon.setVisible(false); this.starterSelectContainer.add(this.pokemonPassiveDisabledIcon); - this.pokemonPassiveLockedIcon = this.scene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_lock"); + this.pokemonPassiveLockedIcon = globalScene.add.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_lock"); this.pokemonPassiveLockedIcon.setOrigin(0, 0.5); this.pokemonPassiveLockedIcon.setScale(0.42, 0.38); this.pokemonPassiveLockedIcon.setVisible(false); this.starterSelectContainer.add(this.pokemonPassiveLockedIcon); - this.pokemonNatureLabelText = addTextObject(this.scene, 6, 145 + starterInfoYOffset, i18next.t("starterSelectUiHandler:nature"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonNatureLabelText = addTextObject(6, 145 + starterInfoYOffset, i18next.t("starterSelectUiHandler:nature"), TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonNatureLabelText.setOrigin(0, 0); this.pokemonNatureLabelText.setVisible(false); this.starterSelectContainer.add(this.pokemonNatureLabelText); - this.pokemonNatureText = addBBCodeTextObject(this.scene, starterInfoXPos, 145 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); + this.pokemonNatureText = addBBCodeTextObject(starterInfoXPos, 145 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, { fontSize: starterInfoTextSize }); this.pokemonNatureText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonNatureText); @@ -595,38 +601,38 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonEggMoveBgs = []; this.pokemonEggMoveLabels = []; - this.valueLimitLabel = addTextObject(this.scene, teamWindowX + 17, 150, "0/10", TextStyle.TOOLTIP_CONTENT); + this.valueLimitLabel = addTextObject(teamWindowX + 17, 150, "0/10", TextStyle.TOOLTIP_CONTENT); this.valueLimitLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(this.valueLimitLabel); - const startLabel = addTextObject(this.scene, teamWindowX + 17, 162, i18next.t("common:start"), TextStyle.TOOLTIP_CONTENT); + const startLabel = addTextObject(teamWindowX + 17, 162, i18next.t("common:start"), TextStyle.TOOLTIP_CONTENT); startLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(startLabel); - this.startCursorObj = this.scene.add.nineslice(teamWindowX + 4, 160, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); + this.startCursorObj = globalScene.add.nineslice(teamWindowX + 4, 160, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); this.startCursorObj.setVisible(false); this.startCursorObj.setOrigin(0, 0); this.starterSelectContainer.add(this.startCursorObj); - const randomSelectLabel = addTextObject(this.scene, teamWindowX + 17, 23, i18next.t("starterSelectUiHandler:randomize"), TextStyle.TOOLTIP_CONTENT); + const randomSelectLabel = addTextObject(teamWindowX + 17, 23, i18next.t("starterSelectUiHandler:randomize"), TextStyle.TOOLTIP_CONTENT); randomSelectLabel.setOrigin(0.5, 0); this.starterSelectContainer.add(randomSelectLabel); - this.randomCursorObj = this.scene.add.nineslice(teamWindowX + 4, 21, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); + this.randomCursorObj = globalScene.add.nineslice(teamWindowX + 4, 21, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); this.randomCursorObj.setVisible(false); this.randomCursorObj.setOrigin(0, 0); this.starterSelectContainer.add(this.randomCursorObj); const starterSpecies: Species[] = []; - const starterBoxContainer = this.scene.add.container(speciesContainerX + 6, 9); //115 + const starterBoxContainer = globalScene.add.container(speciesContainerX + 6, 9); //115 - this.starterSelectScrollBar = new ScrollBar(this.scene, 161, 12, 5, starterContainerWindow.height - 6, 9); + this.starterSelectScrollBar = new ScrollBar(161, 12, 5, starterContainerWindow.height - 6, 9); starterBoxContainer.add(this.starterSelectScrollBar); this.pokerusCursorObjs = new Array(POKERUS_STARTER_COUNT).fill(null).map(() => { - const cursorObj = this.scene.add.image(0, 0, "select_cursor_pokerus"); + const cursorObj = globalScene.add.image(0, 0, "select_cursor_pokerus"); cursorObj.setVisible(false); cursorObj.setOrigin(0, 0); starterBoxContainer.add(cursorObj); @@ -634,16 +640,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); this.starterCursorObjs = new Array(6).fill(null).map(() => { - const cursorObj = this.scene.add.image(0, 0, "select_cursor_highlight"); + const cursorObj = globalScene.add.image(0, 0, "select_cursor_highlight"); cursorObj.setVisible(false); cursorObj.setOrigin(0, 0); starterBoxContainer.add(cursorObj); return cursorObj; }); - this.cursorObj = this.scene.add.image(0, 0, "select_cursor"); + this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); this.cursorObj.setOrigin(0, 0); - this.starterIconsCursorObj = this.scene.add.image(289, 64, "select_gen_cursor"); + this.starterIconsCursorObj = globalScene.add.image(289, 64, "select_gen_cursor"); this.starterIconsCursorObj.setName("starter-icons-cursor"); this.starterIconsCursorObj.setVisible(false); this.starterIconsCursorObj.setOrigin(0, 0); @@ -660,7 +666,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.speciesLoaded.set(species.speciesId, false); this.allSpecies.push(species); - const starterContainer = new StarterContainer(this.scene, species).setVisible(false); + const starterContainer = new StarterContainer(species).setVisible(false); this.iconAnimHandler.addOrUpdate(starterContainer.icon, PokemonIconAnimMode.NONE); this.starterContainers.push(starterContainer); starterBoxContainer.add(starterContainer); @@ -669,7 +675,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.add(starterBoxContainer); this.starterIcons = new Array(6).fill(null).map((_, i) => { - const icon = this.scene.add.sprite(teamWindowX + 7, calcStarterIconY(i), "pokemon_icons_0"); + const icon = globalScene.add.sprite(teamWindowX + 7, calcStarterIconY(i), "pokemon_icons_0"); icon.setScale(0.5); icon.setOrigin(0, 0); icon.setFrame("unknown"); @@ -678,96 +684,96 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return icon; }); - 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.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub"); + this.pokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.starterSelectContainer.add(this.pokemonSprite); - this.type1Icon = this.scene.add.sprite(8, 98, getLocalizedSpriteKey("types")); + this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")); this.type1Icon.setScale(0.5); this.type1Icon.setOrigin(0, 0); this.starterSelectContainer.add(this.type1Icon); - this.type2Icon = this.scene.add.sprite(26, 98, getLocalizedSpriteKey("types")); + this.type2Icon = globalScene.add.sprite(26, 98, getLocalizedSpriteKey("types")); this.type2Icon.setScale(0.5); this.type2Icon.setOrigin(0, 0); this.starterSelectContainer.add(this.type2Icon); - this.pokemonLuckLabelText = addTextObject(this.scene, 8, 89, i18next.t("common:luckIndicator"), TextStyle.WINDOW_ALT, { fontSize: "56px" }); + this.pokemonLuckLabelText = addTextObject(8, 89, i18next.t("common:luckIndicator"), TextStyle.WINDOW_ALT, { fontSize: "56px" }); this.pokemonLuckLabelText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonLuckLabelText); - this.pokemonLuckText = addTextObject(this.scene, 8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, { fontSize: "56px" }); + this.pokemonLuckText = addTextObject(8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, { fontSize: "56px" }); this.pokemonLuckText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonLuckText); // Candy icon and count - this.pokemonCandyContainer = this.scene.add.container(4.5, 18); + this.pokemonCandyContainer = globalScene.add.container(4.5, 18); - this.pokemonCandyIcon = this.scene.add.sprite(0, 0, "candy"); + this.pokemonCandyIcon = globalScene.add.sprite(0, 0, "candy"); this.pokemonCandyIcon.setScale(0.5); this.pokemonCandyIcon.setOrigin(0, 0); this.pokemonCandyContainer.add(this.pokemonCandyIcon); - this.pokemonCandyOverlayIcon = this.scene.add.sprite(0, 0, "candy_overlay"); + this.pokemonCandyOverlayIcon = globalScene.add.sprite(0, 0, "candy_overlay"); this.pokemonCandyOverlayIcon.setScale(0.5); this.pokemonCandyOverlayIcon.setOrigin(0, 0); this.pokemonCandyContainer.add(this.pokemonCandyOverlayIcon); - this.pokemonCandyDarknessOverlay = this.scene.add.sprite(0, 0, "candy"); + this.pokemonCandyDarknessOverlay = globalScene.add.sprite(0, 0, "candy"); this.pokemonCandyDarknessOverlay.setScale(0.5); this.pokemonCandyDarknessOverlay.setOrigin(0, 0); this.pokemonCandyDarknessOverlay.setTint(0x000000); this.pokemonCandyDarknessOverlay.setAlpha(0.50); this.pokemonCandyContainer.add(this.pokemonCandyDarknessOverlay); - this.pokemonCandyCountText = addTextObject(this.scene, 9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }); + this.pokemonCandyCountText = addTextObject(9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }); this.pokemonCandyCountText.setOrigin(0, 0); this.pokemonCandyContainer.add(this.pokemonCandyCountText); this.pokemonCandyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 20), Phaser.Geom.Rectangle.Contains); this.starterSelectContainer.add(this.pokemonCandyContainer); - this.pokemonFormText = addTextObject(this.scene, 6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" }); + this.pokemonFormText = addTextObject(6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" }); this.pokemonFormText.setOrigin(0, 0); this.starterSelectContainer.add(this.pokemonFormText); - this.pokemonCaughtHatchedContainer = this.scene.add.container(2, 25); + this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25); this.pokemonCaughtHatchedContainer.setScale(0.5); this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer); - const pokemonCaughtIcon = this.scene.add.sprite(1, 0, "items", "pb"); + const pokemonCaughtIcon = globalScene.add.sprite(1, 0, "items", "pb"); pokemonCaughtIcon.setOrigin(0, 0); pokemonCaughtIcon.setScale(0.75); this.pokemonCaughtHatchedContainer.add(pokemonCaughtIcon); - this.pokemonCaughtCountText = addTextObject(this.scene, 24, 4, "0", TextStyle.SUMMARY_ALT); + this.pokemonCaughtCountText = addTextObject(24, 4, "0", TextStyle.SUMMARY_ALT); this.pokemonCaughtCountText.setOrigin(0, 0); this.pokemonCaughtHatchedContainer.add(this.pokemonCaughtCountText); - this.pokemonHatchedIcon = this.scene.add.sprite(1, 14, "egg_icons"); + this.pokemonHatchedIcon = globalScene.add.sprite(1, 14, "egg_icons"); this.pokemonHatchedIcon.setOrigin(0.15, 0.2); this.pokemonHatchedIcon.setScale(0.8); this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon); - this.pokemonShinyIcon = this.scene.add.sprite(14, 76, "shiny_icons"); + this.pokemonShinyIcon = globalScene.add.sprite(14, 76, "shiny_icons"); this.pokemonShinyIcon.setOrigin(0.15, 0.2); this.pokemonShinyIcon.setScale(1); this.pokemonCaughtHatchedContainer.add(this.pokemonShinyIcon); - this.pokemonHatchedCountText = addTextObject(this.scene, 24, 19, "0", TextStyle.SUMMARY_ALT); + this.pokemonHatchedCountText = addTextObject(24, 19, "0", TextStyle.SUMMARY_ALT); this.pokemonHatchedCountText.setOrigin(0, 0); this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText); - this.pokemonMovesContainer = this.scene.add.container(102, 16); + this.pokemonMovesContainer = globalScene.add.container(102, 16); this.pokemonMovesContainer.setScale(0.375); for (let m = 0; m < 4; m++) { - const moveContainer = this.scene.add.container(0, 14 * m); + const moveContainer = globalScene.add.container(0, 14 * m); - const moveBg = this.scene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); + const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); moveBg.setOrigin(1, 0); - const moveLabel = addTextObject(this.scene, -moveBg.width / 2, 0, "-", TextStyle.PARTY); + const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.PARTY); moveLabel.setOrigin(0.5, 0); this.pokemonMoveBgs.push(moveBg); @@ -780,28 +786,28 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonMovesContainer.add(moveContainer); } - this.pokemonAdditionalMoveCountLabel = addTextObject(this.scene, -this.pokemonMoveBgs[0].width / 2, 56, "(+0)", TextStyle.PARTY); + this.pokemonAdditionalMoveCountLabel = addTextObject(-this.pokemonMoveBgs[0].width / 2, 56, "(+0)", TextStyle.PARTY); this.pokemonAdditionalMoveCountLabel.setOrigin(0.5, 0); this.pokemonMovesContainer.add(this.pokemonAdditionalMoveCountLabel); this.starterSelectContainer.add(this.pokemonMovesContainer); - this.pokemonEggMovesContainer = this.scene.add.container(102, 85); + this.pokemonEggMovesContainer = globalScene.add.container(102, 85); this.pokemonEggMovesContainer.setScale(0.375); - const eggMovesLabel = addTextObject(this.scene, -46, 0, i18next.t("starterSelectUiHandler:eggMoves"), TextStyle.WINDOW_ALT); + const eggMovesLabel = addTextObject(-46, 0, i18next.t("starterSelectUiHandler:eggMoves"), TextStyle.WINDOW_ALT); eggMovesLabel.setOrigin(0.5, 0); this.pokemonEggMovesContainer.add(eggMovesLabel); for (let m = 0; m < 4; m++) { - const eggMoveContainer = this.scene.add.container(0, 16 + 14 * m); + const eggMoveContainer = globalScene.add.container(0, 16 + 14 * m); - const eggMoveBg = this.scene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); + const eggMoveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2); eggMoveBg.setOrigin(1, 0); - const eggMoveLabel = addTextObject(this.scene, -eggMoveBg.width / 2, 0, "???", TextStyle.PARTY); + const eggMoveLabel = addTextObject(-eggMoveBg.width / 2, 0, "???", TextStyle.PARTY); eggMoveLabel.setOrigin(0.5, 0); this.pokemonEggMoveBgs.push(eggMoveBg); @@ -820,85 +826,85 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // The font size should be set per language const instructionTextSize = textSettings.instructionTextSize; - this.instructionsContainer = this.scene.add.container(4, 156); + this.instructionsContainer = globalScene.add.container(4, 156); this.instructionsContainer.setVisible(true); this.starterSelectContainer.add(this.instructionsContainer); // instruction rows that will be pushed into the container dynamically based on need // creating new sprites since they will be added to the scene later - this.shinyIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "R.png"); + this.shinyIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "R.png"); this.shinyIconElement.setName("sprite-shiny-icon-element"); this.shinyIconElement.setScale(0.675); this.shinyIconElement.setOrigin(0.0, 0.0); - this.shinyLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleShiny"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.shinyLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleShiny"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.shinyLabel.setName("text-shiny-label"); - this.formIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "F.png"); + this.formIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "F.png"); this.formIconElement.setName("sprite-form-icon-element"); this.formIconElement.setScale(0.675); this.formIconElement.setOrigin(0.0, 0.0); - this.formLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleForm"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.formLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleForm"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.formLabel.setName("text-form-label"); - this.genderIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "G.png"); + this.genderIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "G.png"); this.genderIconElement.setName("sprite-gender-icon-element"); this.genderIconElement.setScale(0.675); this.genderIconElement.setOrigin(0.0, 0.0); - this.genderLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleGender"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.genderLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleGender"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.genderLabel.setName("text-gender-label"); - this.abilityIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "E.png"); + this.abilityIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "E.png"); this.abilityIconElement.setName("sprite-ability-icon-element"); this.abilityIconElement.setScale(0.675); this.abilityIconElement.setOrigin(0.0, 0.0); - this.abilityLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleAbility"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.abilityLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleAbility"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.abilityLabel.setName("text-ability-label"); - this.natureIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "N.png"); + this.natureIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "N.png"); this.natureIconElement.setName("sprite-nature-icon-element"); this.natureIconElement.setScale(0.675); this.natureIconElement.setOrigin(0.0, 0.0); - this.natureLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleNature"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.natureLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleNature"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.natureLabel.setName("text-nature-label"); - this.variantIconElement = new Phaser.GameObjects.Sprite(this.scene, this.instructionRowX, this.instructionRowY, "keyboard", "V.png"); + this.variantIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "V.png"); this.variantIconElement.setName("sprite-variant-icon-element"); this.variantIconElement.setScale(0.675); this.variantIconElement.setOrigin(0.0, 0.0); - this.variantLabel = addTextObject(this.scene, this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.variantLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("starterSelectUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.variantLabel.setName("text-variant-label"); - this.goFilterIconElement = new Phaser.GameObjects.Sprite(this.scene, this.filterInstructionRowX, this.filterInstructionRowY, "keyboard", "C.png"); + this.goFilterIconElement = new Phaser.GameObjects.Sprite(globalScene, this.filterInstructionRowX, this.filterInstructionRowY, "keyboard", "C.png"); this.goFilterIconElement.setName("sprite-goFilter-icon-element"); this.goFilterIconElement.setScale(0.675); this.goFilterIconElement.setOrigin(0.0, 0.0); - this.goFilterLabel = addTextObject(this.scene, this.filterInstructionRowX + this.instructionRowTextOffset, this.filterInstructionRowY, i18next.t("starterSelectUiHandler:goFilter"), TextStyle.PARTY, { fontSize: instructionTextSize }); + this.goFilterLabel = addTextObject(this.filterInstructionRowX + this.instructionRowTextOffset, this.filterInstructionRowY, i18next.t("starterSelectUiHandler:goFilter"), TextStyle.PARTY, { fontSize: instructionTextSize }); this.goFilterLabel.setName("text-goFilter-label"); this.hideInstructions(); - this.filterInstructionsContainer = this.scene.add.container(50, 5); + this.filterInstructionsContainer = globalScene.add.container(50, 5); this.filterInstructionsContainer.setVisible(true); this.starterSelectContainer.add(this.filterInstructionsContainer); - this.starterSelectMessageBoxContainer = this.scene.add.container(0, this.scene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6); this.starterSelectMessageBoxContainer.setVisible(false); this.starterSelectContainer.add(this.starterSelectMessageBoxContainer); - this.starterSelectMessageBox = addWindow(this.scene, 1, -1, 318, 28); + this.starterSelectMessageBox = addWindow(1, -1, 318, 28); this.starterSelectMessageBox.setOrigin(0, 1); this.starterSelectMessageBoxContainer.add(this.starterSelectMessageBox); - this.message = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); this.message.setOrigin(0, 0); this.starterSelectMessageBoxContainer.add(this.message); // arrow icon for the message box this.initPromptSprite(this.starterSelectMessageBoxContainer); - this.statsContainer = new StatsContainer(this.scene, 6, 16); + this.statsContainer = new StatsContainer(6, 16); - this.scene.add.existing(this.statsContainer); + globalScene.add.existing(this.statsContainer); this.statsContainer.setVisible(false); @@ -906,11 +912,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // add the info overlay last to be the top most ui element and prevent the IVs from overlaying this const overlayScale = 1; - this.moveInfoOverlay = new MoveInfoOverlay(this.scene, { + this.moveInfoOverlay = new MoveInfoOverlay({ scale: overlayScale, top: true, x: 1, - y: this.scene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, + y: globalScene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29, }); this.starterSelectContainer.add(this.moveInfoOverlay); @@ -919,7 +925,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.initTutorialOverlay(this.starterSelectContainer); this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer); - this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); + globalScene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); this.updateInstructions(); } @@ -930,7 +936,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterPreferences = StarterPrefs.load(); } this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers - this.pokerusSpecies = getPokerusStarters(this.scene); + this.pokerusSpecies = getPokerusStarters(); if (args.length >= 1 && args[0] instanceof Function) { super.show(args); @@ -940,7 +946,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.allSpecies.forEach((species, s) => { const icon = this.starterContainers[s].icon; - const dexEntry = this.scene.gameData.dexData[species.speciesId]; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; // Initialize the StarterAttributes for this species this.starterPreferences[species.speciesId] = this.initStarterPrefs(species); @@ -962,7 +968,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setCursor(0); this.tryUpdateValue(0); - handleTutorial(this.scene, Tutorial.Starter_Select); + handleTutorial(Tutorial.Starter_Select); return true; } @@ -980,8 +986,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ initStarterPrefs(species: PokemonSpecies): StarterAttributes { const starterAttributes = this.starterPreferences[species.speciesId]; - const dexEntry = this.scene.gameData.dexData[species.speciesId]; - const starterData = this.scene.gameData.starterData[species.speciesId]; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + const starterData = globalScene.gameData.starterData[species.speciesId]; // no preferences or Pokemon wasn't caught, return empty attribute if (!starterAttributes || !dexEntry.caughtAttr) { @@ -1040,13 +1046,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } const selectedForm = starterAttributes.form; - if (selectedForm !== undefined && (!species.forms[selectedForm]?.isStarterSelectable || !(caughtAttr & this.scene.gameData.getFormAttr(selectedForm)))) { + if (selectedForm !== undefined && (!species.forms[selectedForm]?.isStarterSelectable || !(caughtAttr & globalScene.gameData.getFormAttr(selectedForm)))) { // requested form wasn't unlocked/isn't a starter form, purging setting delete starterAttributes.form; } if (starterAttributes.nature !== undefined) { - const unlockedNatures = this.scene.gameData.getNaturesForAttr(dexEntry.natureAttr); + const unlockedNatures = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr); if (unlockedNatures.indexOf(starterAttributes.nature as unknown as Nature) < 0) { // requested nature wasn't unlocked, purging setting delete starterAttributes.nature; @@ -1085,7 +1091,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectMessageBoxContainer.setY(0); this.message.setY(4); } else { - this.starterSelectMessageBoxContainer.setY(this.scene.game.canvas.height / 6); + this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6); this.starterSelectMessageBox.setOrigin(0, 1); this.message.setY(singleLine ? -22 : -37); } @@ -1098,14 +1104,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * @returns true if upgrade notifications are enabled and set to display an 'Icon' */ isUpgradeIconEnabled(): boolean { - return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 0; + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 0; } /** * Determines if 'Animation' based upgrade notifications should be shown * @returns true if upgrade notifications are enabled and set to display an 'Animation' */ isUpgradeAnimationEnabled(): boolean { - return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 1; + return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 1; } /** @@ -1115,7 +1121,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ isPassiveAvailable(speciesId: number): boolean { // Get this species ID's starter data - const starterData = this.scene.gameData.starterData[speciesId]; + const starterData = globalScene.gameData.starterData[speciesId]; return starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[speciesId]) && !(starterData.passiveAttr & PassiveAttr.UNLOCKED); @@ -1128,7 +1134,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ isValueReductionAvailable(speciesId: number): boolean { // Get this species ID's starter data - const starterData = this.scene.gameData.starterData[speciesId]; + const starterData = globalScene.gameData.starterData[speciesId]; return starterData.candyCount >= getValueReductionCandyCounts(speciesStarterCosts[speciesId])[starterData.valueReduction] && starterData.valueReduction < valueReductionMax; @@ -1141,7 +1147,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ isSameSpeciesEggAvailable(speciesId: number): boolean { // Get this species ID's starter data - const starterData = this.scene.gameData.starterData[speciesId]; + const starterData = globalScene.gameData.starterData[speciesId]; return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[speciesId]); } @@ -1153,9 +1159,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * @param startPaused Should this animation be paused after it is added? */ setUpgradeAnimation(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, startPaused: boolean = false): void { - this.scene.tweens.killTweensOf(icon); + globalScene.tweens.killTweensOf(icon); // Skip animations if they are disabled - if (this.scene.candyUpgradeDisplay === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + if (globalScene.candyUpgradeDisplay === 0 || species.speciesId !== species.getRootSpeciesId(false)) { return; } @@ -1189,14 +1195,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isSameSpeciesEggAvailable = this.isSameSpeciesEggAvailable(species.speciesId); // 'Passives Only' mode - if (this.scene.candyUpgradeNotification === 1) { + if (globalScene.candyUpgradeNotification === 1) { if (isPassiveAvailable) { - this.scene.tweens.chain(tweenChain).paused = startPaused; + globalScene.tweens.chain(tweenChain).paused = startPaused; } // 'On' mode - } else if (this.scene.candyUpgradeNotification === 2) { + } else if (globalScene.candyUpgradeNotification === 2) { if (isPassiveAvailable || isValueReductionAvailable || isSameSpeciesEggAvailable) { - this.scene.tweens.chain(tweenChain).paused = startPaused; + globalScene.tweens.chain(tweenChain).paused = startPaused; } } } @@ -1208,7 +1214,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const species = starter.species; const slotVisible = !!species?.speciesId; - if (!species || this.scene.candyUpgradeNotification === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + if (!species || globalScene.candyUpgradeNotification === 0 || species.speciesId !== species.getRootSpeciesId(false)) { starter.candyUpgradeIcon.setVisible(false); starter.candyUpgradeOverlayIcon.setVisible(false); return; @@ -1219,12 +1225,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isSameSpeciesEggAvailable = this.isSameSpeciesEggAvailable(species.speciesId); // 'Passive Only' mode - if (this.scene.candyUpgradeNotification === 1) { + if (globalScene.candyUpgradeNotification === 1) { starter.candyUpgradeIcon.setVisible(slotVisible && isPassiveAvailable); starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); // 'On' mode - } else if (this.scene.candyUpgradeNotification === 2) { + } else if (globalScene.candyUpgradeNotification === 2) { starter.candyUpgradeIcon.setVisible( slotVisible && ( isPassiveAvailable || isValueReductionAvailable || isSameSpeciesEggAvailable )); starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible); @@ -1255,7 +1261,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } // Loop through all visible candy icons when set to 'Icon' mode - if (this.scene.candyUpgradeDisplay === 0) { + if (globalScene.candyUpgradeDisplay === 0) { this.filteredStarterContainers.forEach((starter) => { this.setUpgradeIcon(starter); }); @@ -1459,25 +1465,25 @@ export default class StarterSelectUiHandler extends MessageUiHandler { error = true; break; } - const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number ) => total + this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number ) => total + globalScene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); // Filter valid starters const validStarters = this.filteredStarterContainers.filter(starter => { const species = starter.species; const [ isDupe ] = this.isInParty(species); - const starterCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); + const starterCost = globalScene.gameData.getSpeciesStarterValue(species.speciesId); const isValidForChallenge = new BooleanHolder(true); Challenge.applyChallenges( - this.scene.gameMode, + globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, - this.scene.gameData.getSpeciesDexAttrProps( + globalScene.gameData.getSpeciesDexAttrProps( species, this.getCurrentDexProps(species.speciesId) ), this.isPartyValid() ); - const isCaught = this.scene.gameData.dexData[species.speciesId].caughtAttr; + const isCaught = globalScene.gameData.dexData[species.speciesId].caughtAttr; return ( !isDupe && isValidForChallenge.value && @@ -1495,15 +1501,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Set species and prepare attributes this.setSpecies(randomSpecies); const dexAttr = this.getCurrentDexProps(randomSpecies.speciesId); - const props = this.scene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr); + const props = globalScene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr); const abilityIndex = this.abilityCursor; const nature = this.natureCursor as unknown as Nature; const moveset = this.starterMoveset?.slice(0) as StarterMoveset; - const starterCost = this.scene.gameData.getSpeciesStarterValue(randomSpecies.speciesId); + const starterCost = globalScene.gameData.getSpeciesStarterValue(randomSpecies.speciesId); const speciesForm = getPokemonSpeciesForm(randomSpecies.speciesId, props.formIndex); // Load assets and add to party speciesForm - .loadAssets(this.scene, props.female, props.formIndex, props.shiny, props.variant, true) + .loadAssets(props.female, props.formIndex, props.shiny, props.variant, true) .then(() => { if (this.tryUpdateValue(starterCost, true)) { this.addToParty(randomSpecies, dexAttr, abilityIndex, nature, moveset, true); @@ -1547,7 +1553,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else { let starterContainer; - const starterData = this.scene.gameData.starterData[this.lastSpecies.speciesId]; + const starterData = globalScene.gameData.starterData[this.lastSpecies.speciesId]; // prepare persistent starter data to store changes let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]; @@ -1571,17 +1577,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isPartyValid = this.isPartyValid(); const isValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.lastSpecies, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)), isPartyValid); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.lastSpecies, isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)), isPartyValid); - const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); - const newCost = this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number) => total += globalScene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + const newCost = globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE) { // 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); - const isOverValueLimit = this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId), true); + const isOverValueLimit = this.tryUpdateValue(globalScene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId), true); if (!isDupe && isValidForChallenge.value && isOverValueLimit) { const cursorObj = this.starterCursorObjs[this.starterSpecies.length]; cursorObj.setVisible(true); @@ -1710,11 +1716,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.setMode(Mode.STARTER_SELECT).then(() => { ui.showText(i18next.t("starterSelectUiHandler:selectNature"), null, () => { - const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + const natures = globalScene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); ui.setModeWithoutClear(Mode.OPTION_SELECT, { options: natures.map((n: Nature, _i: number) => { const option: OptionSelectItem = { - label: getNatureName(n, true, true, true, this.scene.uiTheme), + label: getNatureName(n, true, true, true, globalScene.uiTheme), handler: () => { // update default nature in starter save data if (!starterAttributes) { @@ -1852,19 +1858,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterData.candyCount -= passiveCost; } this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); - this.scene.gameData.saveSystem().then(success => { + globalScene.gameData.saveSystem().then(success => { if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } }); ui.setMode(Mode.STARTER_SELECT); this.setSpeciesDetails(this.lastSpecies); - this.scene.playSound("se/buy"); + globalScene.playSound("se/buy"); // update the passive background and icon/animation for available upgrade if (starterContainer) { this.updateCandyUpgradeDisplay(starterContainer); - starterContainer.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[this.lastSpecies.speciesId].passiveAttr); + starterContainer.starterPassiveBgs.setVisible(!!globalScene.gameData.starterData[this.lastSpecies.speciesId].passiveAttr); } return true; } @@ -1888,14 +1894,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterData.candyCount -= reductionCost; } this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); - this.scene.gameData.saveSystem().then(success => { + globalScene.gameData.saveSystem().then(success => { if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } }); this.tryUpdateValue(0); ui.setMode(Mode.STARTER_SELECT); - this.scene.playSound("se/buy"); + globalScene.playSound("se/buy"); // update the value label and icon/animation for available upgrade if (starterContainer) { @@ -1917,7 +1923,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) { - if (this.scene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { // Egg list full, show error message at the top of the screen and abort this.showText(i18next.t("egg:tooManyEggs"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), 2000, false, undefined, true); return false; @@ -1927,16 +1933,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); - const egg = new Egg({ scene: this.scene, species: this.lastSpecies.speciesId, sourceType: EggSourceType.SAME_SPECIES_EGG }); - egg.addEggToGameData(this.scene); + const egg = new Egg({ species: this.lastSpecies.speciesId, sourceType: EggSourceType.SAME_SPECIES_EGG }); + egg.addEggToGameData(); - this.scene.gameData.saveSystem().then(success => { + globalScene.gameData.saveSystem().then(success => { if (!success) { - return this.scene.reset(true); + return globalScene.reset(true); } }); ui.setMode(Mode.STARTER_SELECT); - this.scene.playSound("se/buy"); + globalScene.playSound("se/buy"); // update the icon/animation for available upgrade if (starterContainer) { @@ -1985,7 +1991,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; } } else { - const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const props = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); switch (button) { case Button.CYCLE_SHINY: if (this.canCycleShiny) { @@ -1993,11 +1999,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (starterAttributes.shiny) { // Change to shiny, we need to get the proper default variant - const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const newProps = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; this.setSpeciesDetails(this.lastSpecies, { shiny: true, variant: newVariant }); - this.scene.playSound("se/sparkle"); + globalScene.playSound("se/sparkle"); // Set the variant label to the shiny tint const tint = getVariantTint(newVariant); this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); @@ -2044,7 +2050,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let newFormIndex = props.formIndex; do { newFormIndex = (newFormIndex + 1) % formCount; - if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? + if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & globalScene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? break; } } while (newFormIndex !== props.formIndex); @@ -2063,7 +2069,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.CYCLE_ABILITY: if (this.canCycleAbility) { const abilityCount = this.lastSpecies.getAbilityCount(); - const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; + const abilityAttr = globalScene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; let newAbilityIndex = this.abilityCursor; do { @@ -2086,11 +2092,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } while (newAbilityIndex !== this.abilityCursor); starterAttributes.ability = newAbilityIndex; // store the selected ability - const { visible: tooltipVisible } = this.scene.ui.getTooltip(); + const { visible: tooltipVisible } = globalScene.ui.getTooltip(); if (tooltipVisible && this.activeTooltip === "ABILITY") { const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; - this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); + globalScene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); } this.setSpeciesDetails(this.lastSpecies, { abilityIndex: newAbilityIndex }); @@ -2099,7 +2105,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { break; case Button.CYCLE_NATURE: if (this.canCycleNature) { - const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + const natures = globalScene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); const natureIndex = natures.indexOf(this.natureCursor); const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; // store cycled nature as default @@ -2273,7 +2279,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, dexAttr); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, dexAttr); 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); @@ -2284,13 +2290,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterNatures.push(nature); this.starterMovesets.push(moveset); if (this.speciesLoaded.get(species.speciesId) || randomSelection ) { - getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene); + getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(); } this.updateInstructions(); } updatePartyIcon(species: PokemonSpecies, index: number) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); this.starterIcons[index].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[index].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); this.checkIconId(this.starterIcons[index], species, props.female, props.formIndex, props.shiny, props.variant); @@ -2303,29 +2309,29 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (existingMoveIndex > -1) { this.starterMoveset![existingMoveIndex] = move; // TODO: is this bang correct? } - const props: DexAttrProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor); + const props: DexAttrProps = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.dexAttrCursor); // species has different forms if (pokemonFormLevelMoves.hasOwnProperty(speciesId)) { // starterMoveData doesn't have base form moves or is using the single form format - if (!this.scene.gameData.starterData[speciesId].moveset || Array.isArray(this.scene.gameData.starterData[speciesId].moveset)) { - this.scene.gameData.starterData[speciesId].moveset = { [props.formIndex]: this.starterMoveset?.slice(0) as StarterMoveset }; + if (!globalScene.gameData.starterData[speciesId].moveset || Array.isArray(globalScene.gameData.starterData[speciesId].moveset)) { + globalScene.gameData.starterData[speciesId].moveset = { [props.formIndex]: this.starterMoveset?.slice(0) as StarterMoveset }; } - const starterMoveData = this.scene.gameData.starterData[speciesId].moveset; + const starterMoveData = globalScene.gameData.starterData[speciesId].moveset; // starterMoveData doesn't have active form moves if (!starterMoveData.hasOwnProperty(props.formIndex)) { - this.scene.gameData.starterData[speciesId].moveset[props.formIndex] = this.starterMoveset?.slice(0) as StarterMoveset; + globalScene.gameData.starterData[speciesId].moveset[props.formIndex] = this.starterMoveset?.slice(0) as StarterMoveset; } // does the species' starter move data have its form's starter moves and has it been updated if (starterMoveData.hasOwnProperty(props.formIndex)) { // active form move hasn't been updated if (starterMoveData[props.formIndex][existingMoveIndex] !== newMove) { - this.scene.gameData.starterData[speciesId].moveset[props.formIndex] = this.starterMoveset?.slice(0) as StarterMoveset; + globalScene.gameData.starterData[speciesId].moveset[props.formIndex] = this.starterMoveset?.slice(0) as StarterMoveset; } } } else { - this.scene.gameData.starterData[speciesId].moveset = this.starterMoveset?.slice(0) as StarterMoveset; + globalScene.gameData.starterData[speciesId].moveset = this.starterMoveset?.slice(0) as StarterMoveset; } this.setSpeciesDetails(this.lastSpecies, { forSeen: false }); @@ -2372,7 +2378,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { break; } } else { - iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); + iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting); } iconElement.setTexture(gamepadType, iconPath); iconElement.setPosition(this.instructionRowX, this.instructionRowY); @@ -2395,7 +2401,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { gamepadType = "keyboard"; iconPath = "C.png"; } else { - iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); + iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting); } iconElement.setTexture(gamepadType, iconPath); iconElement.setPosition(this.filterInstructionRowX, this.filterInstructionRowY); @@ -2419,10 +2425,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.instructionsContainer.removeAll(); this.filterInstructionsContainer.removeAll(); let gamepadType; - if (this.scene.inputMethod === "gamepad") { - gamepadType = this.scene.inputController.getConfig(this.scene.inputController.selectedDevice[Device.GAMEPAD]).padType; + if (globalScene.inputMethod === "gamepad") { + gamepadType = globalScene.inputController.getConfig(globalScene.inputController.selectedDevice[Device.GAMEPAD]).padType; } else { - gamepadType = this.scene.inputMethod; + gamepadType = globalScene.inputMethod; } if (!gamepadType) { @@ -2459,7 +2465,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { getValueLimit(): number { const valueLimit = new NumberHolder(0); - switch (this.scene.gameMode.modeId) { + switch (globalScene.gameMode.modeId) { case GameModes.ENDLESS: case GameModes.SPLICED_ENDLESS: valueLimit.value = 15; @@ -2468,7 +2474,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { valueLimit.value = 10; } - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); return valueLimit.value; } @@ -2484,7 +2490,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.filterBar.updateFilterLabels(); // pre filter for challenges - if (this.scene.gameMode.modeId === GameModes.CHALLENGE) { + if (globalScene.gameMode.modeId === GameModes.CHALLENGE) { this.starterContainers.forEach(container => { const species = container.species; let allFormsValid = false; @@ -2495,12 +2501,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ const tempFormProps = BigInt(Math.pow(2, i)) * DexAttr.DEFAULT_FORM; const isValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, container.species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, tempFormProps), true); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, container.species, isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps), true); allFormsValid = allFormsValid || isValidForChallenge.value; } } else { const isValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, container.species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(container.species, false, true)), true); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, container.species, isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(species, globalScene.gameData.getSpeciesDefaultDexAttr(container.species, false, true)), true); allFormsValid = isValidForChallenge.value; } if (allFormsValid) { @@ -2519,7 +2525,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; const currentDexAttr = this.getCurrentDexProps(currentFilteredContainer.species.speciesId); - const props = this.scene.gameData.getSpeciesDexAttrProps(currentFilteredContainer.species, currentDexAttr); + const props = globalScene.gameData.getSpeciesDexAttrProps(currentFilteredContainer.species, currentDexAttr); starterSprite.setTexture(currentFilteredContainer.species.getIconAtlasKey(props.formIndex, props.shiny, props.variant), currentFilteredContainer.species.getIconId(props.female!, props.formIndex, props.shiny, props.variant)); currentFilteredContainer.checkIconId(props.female, props.formIndex, props.shiny, props.variant); @@ -2529,11 +2535,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.validStarterContainers.forEach(container => { container.setVisible(false); - container.cost = this.scene.gameData.getSpeciesStarterValue(container.species.speciesId); + container.cost = globalScene.gameData.getSpeciesStarterValue(container.species.speciesId); // First, ensure you have the caught attributes for the species else default to bigint 0 - const caughtAttr = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); - const starterData = this.scene.gameData.starterData[container.species.speciesId]; + const caughtAttr = globalScene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); + const starterData = globalScene.gameData.starterData[container.species.speciesId]; const isStarterProgressable = speciesEggMoves.hasOwnProperty(container.species.speciesId); // Gen filter @@ -2676,12 +2682,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case SortCriteria.COST: return (a.cost - b.cost) * -sort.dir; case SortCriteria.CANDY: - const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; - const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; + const candyCountA = globalScene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = globalScene.gameData.starterData[b.species.speciesId].candyCount; return (candyCountA - candyCountB) * -sort.dir; case SortCriteria.IV: - 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; + const avgIVsA = globalScene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / globalScene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = globalScene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / globalScene.gameData.dexData[b.species.speciesId].ivs.length; return (avgIVsA - avgIVsB) * -sort.dir; case SortCriteria.NAME: return a.species.name.localeCompare(b.species.name) * -sort.dir; @@ -2736,8 +2742,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { 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)) + const speciesVariants = speciesId && globalScene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY + ? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(globalScene.gameData.dexData[speciesId].caughtAttr & v)) : []; for (let v = 0; v < 3; v++) { const hasVariant = speciesVariants.length > v; @@ -2747,13 +2753,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } - 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); + container.starterPassiveBgs.setVisible(!!globalScene.gameData.starterData[speciesId].passiveAttr); + container.hiddenAbilityIcon.setVisible(!!globalScene.gameData.dexData[speciesId].caughtAttr && !!(globalScene.gameData.starterData[speciesId].abilityAttr & 4)); + container.classicWinIcon.setVisible(globalScene.gameData.starterData[speciesId].classicWinCount > 0); container.favoriteIcon.setVisible(this.starterPreferences[speciesId]?.favorite ?? false); // 'Candy Icon' mode - if (this.scene.candyUpgradeDisplay === 0) { + if (globalScene.candyUpgradeDisplay === 0) { if (!starterColors[speciesId]) { // Default to white if no colors are found @@ -2765,7 +2771,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { container.candyUpgradeOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(starterColors[speciesId][1]))); this.setUpgradeIcon(container); - } else if (this.scene.candyUpgradeDisplay === 1) { + } else if (globalScene.candyUpgradeDisplay === 1) { container.candyUpgradeIcon.setVisible(false); container.candyUpgradeOverlayIcon.setVisible(false); } @@ -2792,7 +2798,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (species) { const defaultDexAttr = this.getCurrentDexProps(species.speciesId); - const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); const variant = this.starterPreferences[species.speciesId]?.variant ? this.starterPreferences[species.speciesId].variant as Variant : defaultProps.variant; const tint = getVariantTint(variant); this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); @@ -2836,7 +2842,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } getFriendship(speciesId: number) { - let currentFriendship = this.scene.gameData.starterData[speciesId].friendship; + let currentFriendship = globalScene.gameData.starterData[speciesId].friendship; if (!currentFriendship || currentFriendship === undefined) { currentFriendship = 0; } @@ -2847,13 +2853,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } setSpecies(species: PokemonSpecies | null) { - this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null; + this.speciesStarterDexEntry = species ? globalScene.gameData.dexData[species.speciesId] : null; this.dexAttrCursor = species ? this.getCurrentDexProps(species.speciesId) : 0n; - this.abilityCursor = species ? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; - this.natureCursor = species ? this.scene.gameData.getSpeciesDefaultNature(species) : 0; + this.abilityCursor = species ? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species) : 0; + this.natureCursor = species ? globalScene.gameData.getSpeciesDefaultNature(species) : 0; - if (!species && this.scene.ui.getTooltip().visible) { - this.scene.ui.hideTooltip(); + if (!species && globalScene.ui.getTooltip().visible) { + globalScene.ui.hideTooltip(); } this.pokemonAbilityText.off("pointerover"); @@ -2883,7 +2889,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.lastSpecies) { const dexAttr = this.getCurrentDexProps(this.lastSpecies.speciesId); - const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr); + const props = globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr); const speciesIndex = this.allSpecies.indexOf(this.lastSpecies); const lastSpeciesIcon = this.starterContainers[speciesIndex].icon; this.checkIconId(lastSpeciesIcon, this.lastSpecies, props.female, props.formIndex, props.shiny, props.variant); @@ -2891,7 +2897,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { // Resume the animation for the previously selected species const icon = this.starterContainers[speciesIndex].icon; - this.scene.tweens.getTweensOf(icon).forEach(tween => tween.resume()); + globalScene.tweens.getTweensOf(icon).forEach(tween => tween.resume()); } this.lastSpecies = species!; // TODO: is this bang correct? @@ -2908,7 +2914,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.speciesStarterDexEntry?.caughtAttr) { const colorScheme = starterColors[species.speciesId]; - const luck = this.scene.gameData.getDexAttrLuck(this.speciesStarterDexEntry.caughtAttr); + const luck = globalScene.gameData.getDexAttrLuck(this.speciesStarterDexEntry.caughtAttr); this.pokemonLuckText.setVisible(!!luck); this.pokemonLuckText.setText(luck.toString()); this.pokemonLuckText.setTint(getVariantTint(Math.min(luck - 1, 2) as Variant)); @@ -2938,7 +2944,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry.hatchedCount}`); const defaultDexAttr = this.getCurrentDexProps(species.speciesId); - const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); const variant = defaultProps.variant; const tint = getVariantTint(variant); this.pokemonShinyIcon.setFrame(getVariantIcon(variant)); @@ -2962,7 +2968,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonShinyIcon.setY(117); this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0]))); this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1]))); - this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`); + this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[species.speciesId].candyCount}`); this.pokemonCandyContainer.setVisible(true); this.pokemonFormText.setY(42); this.pokemonHatchedIcon.setVisible(true); @@ -2973,11 +2979,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY); this.pokemonCandyContainer.on("pointerover", () => { - this.scene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true); + globalScene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true); this.activeTooltip = "CANDY"; }); this.pokemonCandyContainer.on("pointerout", () => { - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); this.activeTooltip = undefined; }); @@ -2989,7 +2995,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const icon = this.starterContainers[speciesIndex].icon; if (this.isUpgradeAnimationEnabled()) { - this.scene.tweens.getTweensOf(icon).forEach(tween => tween.pause()); + globalScene.tweens.getTweensOf(icon).forEach(tween => tween.pause()); // Reset the position of the icon icon.x = -2; icon.y = 2; @@ -3003,7 +3009,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { let props: DexAttrProps; if (starterIndex > -1) { - props = this.scene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]); + props = globalScene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]); this.setSpeciesDetails(species, { shiny: props.shiny, formIndex: props.formIndex, @@ -3014,10 +3020,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); } else { const defaultDexAttr = this.getCurrentDexProps(species.speciesId); - const defaultAbilityIndex = starterAttributes?.ability ?? this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultAbilityIndex = starterAttributes?.ability ?? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); // load default nature from stater save data, if set - const defaultNature = starterAttributes?.nature || this.scene.gameData.getSpeciesDefaultNature(species); - props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const defaultNature = starterAttributes?.nature || globalScene.gameData.getSpeciesDefaultNature(species); + props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); if (starterAttributes?.variant && !isNaN(starterAttributes.variant)) { if (props.shiny) { props.variant = starterAttributes.variant as Variant; @@ -3041,7 +3047,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonSprite.clearTint(); if (this.pokerusSpecies.includes(species)) { - handleTutorial(this.scene, Tutorial.Pokerus); + handleTutorial(Tutorial.Pokerus); } } else { this.pokemonGrowthRateText.setText(""); @@ -3059,10 +3065,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonCandyContainer.setVisible(false); this.pokemonFormText.setVisible(false); - const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, true, true); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); - const props = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); + const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true); + const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); this.setSpeciesDetails(species, { shiny: props.shiny, @@ -3108,9 +3114,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { let { shiny, formIndex, female, variant, abilityIndex, natureIndex } = options; const forSeen: boolean = options.forSeen ?? false; - const oldProps = species ? this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; - const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const oldNatureIndex = this.natureCursor > -1 ? this.natureCursor : this.scene.gameData.getSpeciesDefaultNature(species); + const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null; + const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const oldNatureIndex = this.natureCursor > -1 ? this.natureCursor : globalScene.gameData.getSpeciesDefaultNature(species); this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; @@ -3122,9 +3128,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.activeTooltip === "CANDY") { if (this.lastSpecies && this.pokemonCandyContainer.visible) { const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); - this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + globalScene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); } else { - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); } } @@ -3140,7 +3146,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.dexAttrCursor |= (shiny !== undefined ? !shiny : !(shiny = oldProps?.shiny)) ? DexAttr.NON_SHINY : DexAttr.SHINY; this.dexAttrCursor |= (female !== undefined ? !female : !(female = oldProps?.female)) ? DexAttr.MALE : DexAttr.FEMALE; this.dexAttrCursor |= (variant !== undefined ? !variant : !(variant = oldProps?.variant)) ? DexAttr.DEFAULT_VARIANT : variant === 1 ? DexAttr.VARIANT_2 : DexAttr.VARIANT_3; - this.dexAttrCursor |= this.scene.gameData.getFormAttr(formIndex !== undefined ? formIndex : (formIndex = oldProps!.formIndex)); // TODO: is this bang correct? + this.dexAttrCursor |= globalScene.gameData.getFormAttr(formIndex !== undefined ? formIndex : (formIndex = oldProps!.formIndex)); // TODO: is this bang correct? this.abilityCursor = abilityIndex !== undefined ? abilityIndex : (abilityIndex = oldAbilityIndex); this.natureCursor = natureIndex !== undefined ? natureIndex : (natureIndex = oldNatureIndex); const [ isInParty, partyIndex ]: [boolean, number] = this.isInParty(species); // we use this to firstly check if the pokemon is in the party, and if so, to get the party index in order to update the icon image @@ -3164,15 +3170,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves = []; if (species) { - const dexEntry = this.scene.gameData.dexData[species.speciesId]; - const abilityAttr = this.scene.gameData.starterData[species.speciesId].abilityAttr; + const dexEntry = globalScene.gameData.dexData[species.speciesId]; + const abilityAttr = globalScene.gameData.starterData[species.speciesId].abilityAttr; - const caughtAttr = this.scene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); + const caughtAttr = globalScene.gameData.dexData[species.speciesId]?.caughtAttr || BigInt(0); if (!dexEntry.caughtAttr) { - const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); - const defaultAbilityIndex = this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species); - const defaultNature = this.scene.gameData.getSpeciesDefaultNature(species); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)); + const defaultAbilityIndex = globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species); + const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species); if (shiny === undefined || shiny !== props.shiny) { shiny = props.shiny; @@ -3211,7 +3217,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.assetLoadCancelled = assetLoadCancelled; if (shouldUpdateSprite) { - species.loadAssets(this.scene, female!, formIndex, shiny, variant, true).then(() => { // TODO: is this bang correct? + species.loadAssets(female!, formIndex, shiny, variant, true).then(() => { // TODO: is this bang correct? if (assetLoadCancelled.value) { return; } @@ -3228,7 +3234,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } const isValidForChallenge = new BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterSpecies.length); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterSpecies.length); const currentFilteredContainer = this.filteredStarterContainers.find(p => p.species.speciesId === species.speciesId); if (currentFilteredContainer) { const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite; @@ -3265,8 +3271,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.canCycleAbility = [ hasAbility1, hasAbility2, hasHiddenAbility ].filter(a => a).length > 1; this.canCycleForm = species.forms.filter(f => f.isStarterSelectable || !pokemonFormChanges[species.speciesId]?.find(fc => fc.formKey)) - .map((_, f) => dexEntry.caughtAttr & this.scene.gameData.getFormAttr(f)).filter(f => f).length > 1; - this.canCycleNature = this.scene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; + .map((_, f) => dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f)).filter(f => f).length > 1; + this.canCycleNature = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr).length > 1; } @@ -3287,20 +3293,20 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonAbilityText.setColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD)); this.pokemonAbilityText.setShadowColor(this.getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD, true)); - const passiveAttr = this.scene.gameData.starterData[species.speciesId].passiveAttr; + const passiveAttr = globalScene.gameData.starterData[species.speciesId].passiveAttr; const passiveAbility = allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]]; if (this.pokemonAbilityText.visible) { if (this.activeTooltip === "ABILITY") { - this.scene.ui.editTooltip(`${ability.name}`, `${ability.description}`); + globalScene.ui.editTooltip(`${ability.name}`, `${ability.description}`); } this.pokemonAbilityText.on("pointerover", () => { - this.scene.ui.showTooltip(`${ability.name}`, `${ability.description}`, true); + globalScene.ui.showTooltip(`${ability.name}`, `${ability.description}`, true); this.activeTooltip = "ABILITY"; }); this.pokemonAbilityText.on("pointerout", () => { - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); this.activeTooltip = undefined; }); } @@ -3322,16 +3328,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonPassiveText.setShadowColor(this.getTextColor(textStyle, true)); if (this.activeTooltip === "PASSIVE") { - this.scene.ui.editTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`); + globalScene.ui.editTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`); } if (this.pokemonPassiveText.visible) { this.pokemonPassiveText.on("pointerover", () => { - this.scene.ui.showTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`, true); + globalScene.ui.showTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`, true); this.activeTooltip = "PASSIVE"; }); this.pokemonPassiveText.on("pointerout", () => { - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); this.activeTooltip = undefined; }); } @@ -3347,10 +3353,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } else if (this.activeTooltip === "PASSIVE") { // No passive and passive tooltip is active > hide it - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); } - this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true, false, this.scene.uiTheme)); + this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true, false, globalScene.uiTheme)); let levelMoves: LevelMoves; if (pokemonFormLevelMoves.hasOwnProperty(species.speciesId) && formIndex && pokemonFormLevelMoves[species.speciesId].hasOwnProperty(formIndex)) { @@ -3361,19 +3367,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.speciesStarterMoves.push(...levelMoves.filter(lm => lm[0] > 0 && lm[0] <= 5).map(lm => lm[1])); if (speciesEggMoves.hasOwnProperty(species.speciesId)) { for (let em = 0; em < 4; em++) { - if (this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) { + if (globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) { this.speciesStarterMoves.push(speciesEggMoves[species.speciesId][em]); } } } - const speciesMoveData = this.scene.gameData.starterData[species.speciesId].moveset; + const speciesMoveData = globalScene.gameData.starterData[species.speciesId].moveset; const moveData: StarterMoveset | null = speciesMoveData ? Array.isArray(speciesMoveData) ? speciesMoveData : speciesMoveData[formIndex!] // TODO: is this bang correct? : null; - const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []); + const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []); this.starterMoveset = (moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset)).filter(m => availableStarterMoves.find(sm => sm === m)) as StarterMoveset; // Consolidate move data if it contains an incompatible move if (this.starterMoveset.length < 4 && this.starterMoveset.length < availableStarterMoves.length) { @@ -3430,7 +3436,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { for (let em = 0; em < 4; em++) { const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null; - const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em); + const eggMoveUnlocked = eggMove && globalScene.gameData.starterData[species.speciesId].eggMoves & (1 << em); this.pokemonEggMoveBgs[em].setFrame(Type[eggMove ? eggMove.type : Type.UNKNOWN].toString().toLowerCase()); this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???"); } @@ -3470,7 +3476,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { for (let s = 0; s < this.starterSpecies.length; s++) { const species = this.starterSpecies[s]; const currentDexAttr = this.getCurrentDexProps(species.speciesId); - const props = this.scene.gameData.getSpeciesDexAttrProps(species, currentDexAttr); + const props = globalScene.gameData.getSpeciesDexAttrProps(species, currentDexAttr); this.starterIcons[s].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[s].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); this.checkIconId(this.starterIcons[s], species, props.female, props.formIndex, props.shiny, props.variant); @@ -3516,7 +3522,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { updateStarterValueLabel(starter: StarterContainer): void { const speciesId = starter.species.speciesId; const baseStarterValue = speciesStarterCosts[speciesId]; - const starterValue = this.scene.gameData.getSpeciesStarterValue(speciesId); + const starterValue = globalScene.gameData.getSpeciesStarterValue(speciesId); starter.cost = starterValue; let valueStr = starterValue.toString(); if (valueStr.startsWith("0.")) { @@ -3543,7 +3549,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } tryUpdateValue(add?: integer, addingToParty?: boolean): boolean { - 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 value = this.starterSpecies.map(s => s.generation).reduce((total: integer, _gen: integer, i: integer) => total += globalScene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newValue = value + (add || 0); const valueLimit = this.getValueLimit(); const overLimit = newValue > valueLimit; @@ -3555,14 +3561,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.valueLimitLabel.setColor(this.getTextColor(!overLimit ? TextStyle.TOOLTIP_CONTENT : TextStyle.SUMMARY_PINK)); this.valueLimitLabel.setShadowColor(this.getTextColor(!overLimit ? TextStyle.TOOLTIP_CONTENT : TextStyle.SUMMARY_PINK, true)); if (overLimit) { - this.scene.time.delayedCall(fixedInt(500), () => this.tryUpdateValue()); + globalScene.time.delayedCall(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 BooleanHolder(true); const species = this.filteredStarterContainers[this.cursor].species; - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isNewPokemonValid, this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isNewPokemonValid, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); isPartyValid = isPartyValid || isNewPokemonValid.value; } @@ -3572,7 +3578,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const remainValue = valueLimit - newValue; for (let s = 0; s < this.allSpecies.length; s++) { /** Cost of pokemon species */ - const speciesStarterValue = this.scene.gameData.getSpeciesStarterValue(this.allSpecies[s].speciesId); + const speciesStarterValue = globalScene.gameData.getSpeciesStarterValue(this.allSpecies[s].speciesId); /** {@linkcode Phaser.GameObjects.Sprite} object of Pokémon for setting the alpha value */ const speciesSprite = this.starterContainers[s].icon; @@ -3588,7 +3594,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { * 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 BooleanHolder(true); - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.allSpecies[s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.allSpecies[s], this.getCurrentDexProps(this.allSpecies[s].speciesId)), isPartyValid); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.allSpecies[s], isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(this.allSpecies[s], this.getCurrentDexProps(this.allSpecies[s].speciesId)), isPartyValid); const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value; @@ -3627,15 +3633,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.showText(i18next.t("starterSelectUiHandler:confirmExit"), null, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => { ui.setMode(Mode.STARTER_SELECT); - this.scene.clearPhaseQueue(); - if (this.scene.gameMode.isChallenge) { - this.scene.pushPhase(new SelectChallengePhase(this.scene)); - this.scene.pushPhase(new EncounterPhase(this.scene, false)); + globalScene.clearPhaseQueue(); + if (globalScene.gameMode.isChallenge) { + globalScene.pushPhase(new SelectChallengePhase()); + globalScene.pushPhase(new EncounterPhase()); } else { - this.scene.pushPhase(new TitlePhase(this.scene)); + globalScene.pushPhase(new TitlePhase()); } this.clearText(); - this.scene.getCurrentPhase()?.end(); + globalScene.getCurrentPhase()?.end(); }, cancel, null, null, 19); }); @@ -3663,7 +3669,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.showText(i18next.t("starterSelectUiHandler:confirmStartTeam"), null, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => { const startRun = () => { - this.scene.money = this.scene.gameMode.getStartingMoney(); + globalScene.money = globalScene.gameMode.getStartingMoney(); ui.setMode(Mode.STARTER_SELECT); const thisObj = this; const originalStarterSelectCallback = this.starterSelectCallback; @@ -3674,7 +3680,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { species: starterSpecies, dexAttr: thisObj.starterAttr[i], abilityIndex: thisObj.starterAbilityIndexes[i], - passive: !(thisObj.scene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)), + passive: !(globalScene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)), nature: thisObj.starterNatures[i] as Nature, moveset: thisObj.starterMovesets[i], pokerus: thisObj.pokerusSpecies.includes(starterSpecies), @@ -3700,7 +3706,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { for (let s = 0; s < this.starterSpecies.length; s++) { const isValidForChallenge = new BooleanHolder(true); const species = this.starterSpecies[s]; - Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); + Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)), false); canStart = canStart || isValidForChallenge.value; } return canStart; @@ -3715,7 +3721,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { */ getCurrentDexProps(speciesId: number): bigint { let props = 0n; - const caughtAttr = this.scene.gameData.dexData[speciesId].caughtAttr; + const caughtAttr = globalScene.gameData.dexData[speciesId].caughtAttr; /* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props * It then checks b) if the caughtAttr for the pokemon is female and NOT male - this means that the ONLY gender we've gotten is female, and we need to add DexAttr.FEMALE to our temp props @@ -3753,7 +3759,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { props += BigInt(Math.pow(2, this.starterPreferences[speciesId]?.form)) * DexAttr.DEFAULT_FORM; } else { // Get the first unlocked form - props += this.scene.gameData.getFormAttr(this.scene.gameData.getFormIndex(caughtAttr)); + props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr)); } return props; @@ -3815,7 +3821,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.cursor = -1; this.hideInstructions(); this.activeTooltip = undefined; - this.scene.ui.hideTooltip(); + globalScene.ui.hideTooltip(); this.starterSelectContainer.setVisible(false); this.blockInput = false; diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 7e026ede83e..8d8d3c101b2 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -1,8 +1,8 @@ -import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; -import BattleScene from "../battle-scene"; +import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; import { PERMANENT_STATS, getStatKey } from "#app/enums/stat"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; const ivChartSize = 24; @@ -21,8 +21,8 @@ export class StatsContainer extends Phaser.GameObjects.Container { private ivChart: Phaser.GameObjects.Polygon; private ivStatValueTexts: BBCodeText[]; - constructor(scene: BattleScene, x: number, y: number, showDiff?: boolean) { - super(scene, x, y); + constructor(x: number, y: number, showDiff?: boolean) { + super(globalScene, x, y); this.showDiff = !!showDiff; @@ -32,21 +32,21 @@ export class StatsContainer extends Phaser.GameObjects.Container { setup() { this.setName("stats"); const ivChartBgData = new Array(6).fill(null).map((_, i: integer) => [ ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); - const ivChartBg = this.scene.add.polygon(48, 44, ivChartBgData, 0xd8e0f0, 0.625); + const ivChartBg = globalScene.add.polygon(48, 44, ivChartBgData, 0xd8e0f0, 0.625); ivChartBg.setOrigin(0, 0); - const ivChartBorder = this.scene.add.polygon(ivChartBg.x, ivChartBg.y, ivChartBgData) + const ivChartBorder = globalScene.add.polygon(ivChartBg.x, ivChartBg.y, ivChartBgData) .setStrokeStyle(1, 0x484050); ivChartBorder.setOrigin(0, 0); const ivChartBgLines = [[ 0, -1, 0, 1 ], [ -0.825, -0.5, 0.825, 0.5 ], [ 0.825, -0.5, -0.825, 0.5 ]].map(coords => { - const line = new Phaser.GameObjects.Line(this.scene, ivChartBg.x, ivChartBg.y, ivChartSize * coords[0], ivChartSize * coords[1], ivChartSize * coords[2], ivChartSize * coords[3], 0xffffff) + const line = new Phaser.GameObjects.Line(globalScene, ivChartBg.x, ivChartBg.y, ivChartSize * coords[0], ivChartSize * coords[1], ivChartSize * coords[2], ivChartSize * coords[3], 0xffffff) .setLineWidth(0.5); line.setOrigin(0, 0); return line; }); - this.ivChart = this.scene.add.polygon(ivChartBg.x, ivChartBg.y, defaultIvChartData, 0x98d8a0, 0.75); + this.ivChart = globalScene.add.polygon(ivChartBg.x, ivChartBg.y, defaultIvChartData, 0x98d8a0, 0.75); this.ivChart.setOrigin(0, 0); this.add(ivChartBg); @@ -58,7 +58,6 @@ export class StatsContainer extends Phaser.GameObjects.Container { for (const s of PERMANENT_STATS) { const statLabel = addTextObject( - this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[s][0] * 1.325 + (this.showDiff ? 0 : ivLabelOffset[s]), ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[s][1] * 1.325 - 4 + (this.showDiff ? 0 : ivChartLabelyOffset[s]), i18next.t(getStatKey(s)), @@ -66,7 +65,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { ); statLabel.setOrigin(0.5); - this.ivStatValueTexts[s] = addBBCodeTextObject(this.scene, statLabel.x - (this.showDiff ? 0 : ivLabelOffset[s]), statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT); + this.ivStatValueTexts[s] = addBBCodeTextObject(statLabel.x - (this.showDiff ? 0 : ivLabelOffset[s]), statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT); this.ivStatValueTexts[s].setOrigin(0.5); @@ -79,7 +78,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { if (ivs) { const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const lastIvChartData = this.statsIvsCache || defaultIvChartData; - const perfectIVColor: string = getTextColor(TextStyle.SUMMARY_GOLD, false, (this.scene as BattleScene).uiTheme); + const perfectIVColor: string = getTextColor(TextStyle.SUMMARY_GOLD, false, globalScene.uiTheme); this.statsIvsCache = ivChartData.slice(0); this.ivStatValueTexts.map((t: BBCodeText, i: integer) => { @@ -93,7 +92,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { } if (this.showDiff && originalIvs) { if (originalIvs[i] < ivs[i]) { - label += ` ([color=${getTextColor(TextStyle.SUMMARY_BLUE, false, (this.scene as BattleScene).uiTheme)}][shadow=${getTextColor(TextStyle.SUMMARY_BLUE, true, (this.scene as BattleScene).uiTheme)}]+${ivs[i] - originalIvs[i]}[/shadow][/color])`; + label += ` ([color=${getTextColor(TextStyle.SUMMARY_BLUE, false, globalScene.uiTheme)}][shadow=${getTextColor(TextStyle.SUMMARY_BLUE, true, globalScene.uiTheme)}]+${ivs[i] - originalIvs[i]}[/shadow][/color])`; } else { label += " (-)"; } @@ -108,7 +107,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { Phaser.Display.Color.IntegerToColor(newColor) ] : null; - this.scene.tweens.addCounter({ + globalScene.tweens.addCounter({ from: 0, to: 1, duration: 1000, diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 63ef6155fbc..3fe6a372737 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -1,14 +1,16 @@ -import BattleScene, { starterColors } from "#app/battle-scene"; +import { starterColors } from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { Mode } from "#app/ui/ui"; import UiHandler from "#app/ui/ui-handler"; import * as Utils from "#app/utils"; -import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { argbFromRgba } from "@material/material-color-utilities"; import { getTypeRgb } from "#app/data/type"; import { Type } from "#enums/type"; import { TextStyle, addBBCodeTextObject, addTextObject, getBBCodeFrag } from "#app/ui/text"; -import Move, { MoveCategory } from "#app/data/move"; +import type Move from "#app/data/move"; +import { MoveCategory } from "#app/data/move"; import { getPokeballAtlasKey } from "#app/data/pokeball"; import { getGenderColor, getGenderSymbol } from "#app/data/gender"; import { getLevelRelExp, getLevelTotalExp } from "#app/data/exp"; @@ -17,9 +19,10 @@ import { StatusEffect } from "#enums/status-effect"; import { getBiomeName } from "#app/data/balance/biomes"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { loggedInUser } from "#app/account"; -import { Variant, getVariantTint } from "#app/data/variant"; +import type { Variant } from "#app/data/variant"; +import { getVariantTint } from "#app/data/variant"; import { Button } from "#enums/buttons"; -import { Ability } from "#app/data/ability"; +import type { Ability } from "#app/data/ability"; import i18next from "i18next"; import { modifierSortFunc } from "#app/modifier/modifier"; import { PlayerGender } from "#enums/player-gender"; @@ -111,177 +114,177 @@ export default class SummaryUiHandler extends UiHandler { private selectedMoveIndex: integer; private selectCallback: Function | null; - constructor(scene: BattleScene) { - super(scene, Mode.SUMMARY); + constructor() { + super(Mode.SUMMARY); } setup() { const ui = this.getUi(); - this.summaryContainer = this.scene.add.container(0, 0); + this.summaryContainer = globalScene.add.container(0, 0); this.summaryContainer.setVisible(false); ui.add(this.summaryContainer); - const summaryBg = this.scene.add.image(0, 0, "summary_bg"); + const summaryBg = globalScene.add.image(0, 0, "summary_bg"); summaryBg.setOrigin(0, 1); this.summaryContainer.add(summaryBg); - this.tabSprite = this.scene.add.sprite(134, (-summaryBg.displayHeight) + 16, "summary_tabs_1"); + this.tabSprite = globalScene.add.sprite(134, (-summaryBg.displayHeight) + 16, "summary_tabs_1"); this.tabSprite.setOrigin(1, 1); this.summaryContainer.add(this.tabSprite); - const summaryLabel = addTextObject(this.scene, 4, -165, i18next.t("pokemonSummary:pokemonInfo"), TextStyle.SUMMARY); + const summaryLabel = addTextObject(4, -165, i18next.t("pokemonSummary:pokemonInfo"), TextStyle.SUMMARY); summaryLabel.setOrigin(0, 1); this.summaryContainer.add(summaryLabel); - this.shinyOverlay = this.scene.add.image(6, -54, "summary_overlay_shiny"); + this.shinyOverlay = globalScene.add.image(6, -54, "summary_overlay_shiny"); this.shinyOverlay.setOrigin(0, 1); this.shinyOverlay.setVisible(false); this.summaryContainer.add(this.shinyOverlay); - this.numberText = addTextObject(this.scene, 17, -149, "0000", TextStyle.SUMMARY); + this.numberText = addTextObject(17, -149, "0000", TextStyle.SUMMARY); this.numberText.setOrigin(0, 1); this.summaryContainer.add(this.numberText); - this.pokemonSprite = this.scene.initPokemonSprite(this.scene.add.sprite(56, -106, "pkmn__sub"), undefined, false, true); + this.pokemonSprite = globalScene.initPokemonSprite(globalScene.add.sprite(56, -106, "pkmn__sub"), undefined, false, true); this.summaryContainer.add(this.pokemonSprite); - this.nameText = addTextObject(this.scene, 6, -54, "", TextStyle.SUMMARY); + this.nameText = addTextObject(6, -54, "", TextStyle.SUMMARY); this.nameText.setOrigin(0, 0); this.summaryContainer.add(this.nameText); - this.splicedIcon = this.scene.add.sprite(0, -54, "icon_spliced"); + this.splicedIcon = globalScene.add.sprite(0, -54, "icon_spliced"); this.splicedIcon.setVisible(false); this.splicedIcon.setOrigin(0, 0); this.splicedIcon.setScale(0.75); this.splicedIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.splicedIcon); - this.shinyIcon = this.scene.add.image(0, -54, "shiny_star"); + this.shinyIcon = globalScene.add.image(0, -54, "shiny_star"); this.shinyIcon.setVisible(false); this.shinyIcon.setOrigin(0, 0); this.shinyIcon.setScale(0.75); this.shinyIcon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 12, 15), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.shinyIcon); - this.fusionShinyIcon = this.scene.add.image(0, 0, "shiny_star_2"); + this.fusionShinyIcon = globalScene.add.image(0, 0, "shiny_star_2"); this.fusionShinyIcon.setVisible(false); this.fusionShinyIcon.setOrigin(0, 0); this.fusionShinyIcon.setScale(0.75); this.summaryContainer.add(this.fusionShinyIcon); - this.pokeball = this.scene.add.sprite(6, -19, "pb"); + this.pokeball = globalScene.add.sprite(6, -19, "pb"); this.pokeball.setOrigin(0, 1); this.summaryContainer.add(this.pokeball); - this.candyIcon = this.scene.add.sprite(13, -140, "candy"); + this.candyIcon = globalScene.add.sprite(13, -140, "candy"); this.candyIcon.setScale(0.8); this.summaryContainer.add(this.candyIcon); - this.candyOverlay = this.scene.add.sprite(13, -140, "candy_overlay"); + this.candyOverlay = globalScene.add.sprite(13, -140, "candy_overlay"); this.candyOverlay.setScale(0.8); this.summaryContainer.add(this.candyOverlay); - this.candyShadow = this.scene.add.sprite(13, -140, "candy"); + this.candyShadow = globalScene.add.sprite(13, -140, "candy"); this.candyShadow.setTint(0x000000); this.candyShadow.setAlpha(0.50); this.candyShadow.setScale(0.8); this.candyShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 16), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.candyShadow); - this.candyCountText = addTextObject(this.scene, 20, -146, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); + this.candyCountText = addTextObject(20, -146, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); this.candyCountText.setOrigin(0, 0); this.summaryContainer.add(this.candyCountText); - this.friendshipIcon = this.scene.add.sprite(13, -60, "friendship"); + this.friendshipIcon = globalScene.add.sprite(13, -60, "friendship"); this.friendshipIcon.setScale(0.8); this.summaryContainer.add(this.friendshipIcon); - this.friendshipOverlay = this.scene.add.sprite(13, -60, "friendship_overlay"); + this.friendshipOverlay = globalScene.add.sprite(13, -60, "friendship_overlay"); this.friendshipOverlay.setScale(0.8); this.summaryContainer.add(this.friendshipOverlay); - this.friendshipShadow = this.scene.add.sprite(13, -60, "friendship"); + this.friendshipShadow = globalScene.add.sprite(13, -60, "friendship"); this.friendshipShadow.setTint(0x000000); this.friendshipShadow.setAlpha(0.50); this.friendshipShadow.setScale(0.8); this.friendshipShadow.setInteractive(new Phaser.Geom.Rectangle(0, 0, 50, 16), Phaser.Geom.Rectangle.Contains); this.summaryContainer.add(this.friendshipShadow); - this.friendshipText = addTextObject(this.scene, 20, -66, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); + this.friendshipText = addTextObject(20, -66, "x0", TextStyle.WINDOW_ALT, { fontSize: "76px" }); this.friendshipText.setOrigin(0, 0); this.summaryContainer.add(this.friendshipText); - this.championRibbon = this.scene.add.image(88, -146, "champion_ribbon"); + this.championRibbon = globalScene.add.image(88, -146, "champion_ribbon"); this.championRibbon.setOrigin(0, 0); //this.championRibbon.setScale(0.8); this.championRibbon.setScale(1.25); this.summaryContainer.add(this.championRibbon); this.championRibbon.setVisible(false); - this.levelText = addTextObject(this.scene, 36, -17, "", TextStyle.SUMMARY_ALT); + this.levelText = addTextObject(36, -17, "", TextStyle.SUMMARY_ALT); this.levelText.setOrigin(0, 1); this.summaryContainer.add(this.levelText); - this.genderText = addTextObject(this.scene, 96, -17, "", TextStyle.SUMMARY); + this.genderText = addTextObject(96, -17, "", TextStyle.SUMMARY); this.genderText.setOrigin(0, 1); this.summaryContainer.add(this.genderText); - this.statusContainer = this.scene.add.container(-106, -16); + this.statusContainer = globalScene.add.container(-106, -16); - const statusBg = this.scene.add.image(0, 0, "summary_status"); + const statusBg = globalScene.add.image(0, 0, "summary_status"); statusBg.setOrigin(0, 0); this.statusContainer.add(statusBg); - const statusLabel = addTextObject(this.scene, 3, 0, i18next.t("pokemonSummary:status"), TextStyle.SUMMARY); + const statusLabel = addTextObject(3, 0, i18next.t("pokemonSummary:status"), TextStyle.SUMMARY); statusLabel.setOrigin(0, 0); this.statusContainer.add(statusLabel); - this.status = this.scene.add.sprite(91, 4, Utils.getLocalizedSpriteKey("statuses")); + this.status = globalScene.add.sprite(91, 4, Utils.getLocalizedSpriteKey("statuses")); this.status.setOrigin(0.5, 0); this.statusContainer.add(this.status); this.summaryContainer.add(this.statusContainer); - this.moveEffectContainer = this.scene.add.container(106, -62); + this.moveEffectContainer = globalScene.add.container(106, -62); this.summaryContainer.add(this.moveEffectContainer); - const moveEffectBg = this.scene.add.image(0, 0, "summary_moves_effect"); + const moveEffectBg = globalScene.add.image(0, 0, "summary_moves_effect"); moveEffectBg.setOrigin(0, 0); this.moveEffectContainer.add(moveEffectBg); - const moveEffectLabels = addTextObject(this.scene, 8, 12, i18next.t("pokemonSummary:powerAccuracyCategory"), TextStyle.SUMMARY); + const moveEffectLabels = addTextObject(8, 12, i18next.t("pokemonSummary:powerAccuracyCategory"), TextStyle.SUMMARY); moveEffectLabels.setLineSpacing(9); moveEffectLabels.setOrigin(0, 0); this.moveEffectContainer.add(moveEffectLabels); - this.movePowerText = addTextObject(this.scene, 99, 27, "0", TextStyle.WINDOW_ALT); + this.movePowerText = addTextObject(99, 27, "0", TextStyle.WINDOW_ALT); this.movePowerText.setOrigin(1, 1); this.moveEffectContainer.add(this.movePowerText); - this.moveAccuracyText = addTextObject(this.scene, 99, 43, "0", TextStyle.WINDOW_ALT); + this.moveAccuracyText = addTextObject(99, 43, "0", TextStyle.WINDOW_ALT); this.moveAccuracyText.setOrigin(1, 1); this.moveEffectContainer.add(this.moveAccuracyText); - this.moveCategoryIcon = this.scene.add.sprite(99, 57, "categories"); + this.moveCategoryIcon = globalScene.add.sprite(99, 57, "categories"); this.moveCategoryIcon.setOrigin(1, 1); this.moveEffectContainer.add(this.moveCategoryIcon); const getSummaryPageBg = () => { - const ret = this.scene.add.sprite(0, 0, this.getPageKey(0)); + const ret = globalScene.add.sprite(0, 0, this.getPageKey(0)); ret.setOrigin(0, 1); return ret; }; - this.summaryContainer.add((this.summaryPageContainer = this.scene.add.container(106, 0))); + this.summaryContainer.add((this.summaryPageContainer = globalScene.add.container(106, 0))); this.summaryPageContainer.add(getSummaryPageBg()); this.summaryPageContainer.setVisible(false); - this.summaryContainer.add((this.summaryPageTransitionContainer = this.scene.add.container(106, 0))); + this.summaryContainer.add((this.summaryPageTransitionContainer = globalScene.add.container(106, 0))); this.summaryPageTransitionContainer.add(getSummaryPageBg()); this.summaryPageTransitionContainer.setVisible(false); } @@ -306,7 +309,7 @@ export default class SummaryUiHandler extends UiHandler { this.pokemon = args[0] as PlayerPokemon; this.summaryUiMode = args.length > 1 ? args[1] as SummaryUiMode : SummaryUiMode.DEFAULT; this.playerParty = args[4] ?? true; - this.scene.ui.bringToTop(this.summaryContainer); + globalScene.ui.bringToTop(this.summaryContainer); this.summaryContainer.setVisible(true); this.cursor = -1; @@ -348,17 +351,17 @@ export default class SummaryUiHandler extends UiHandler { this.splicedIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + 2, 3); this.splicedIcon.setVisible(isFusion); if (this.splicedIcon.visible) { - this.splicedIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${this.pokemon?.species.getName(this.pokemon.formIndex)}/${this.pokemon?.fusionSpecies?.getName(this.pokemon?.fusionFormIndex)}`, true)); - this.splicedIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.splicedIcon.on("pointerover", () => globalScene.ui.showTooltip("", `${this.pokemon?.species.getName(this.pokemon.formIndex)}/${this.pokemon?.fusionSpecies?.getName(this.pokemon?.fusionFormIndex)}`, true)); + this.splicedIcon.on("pointerout", () => globalScene.ui.hideTooltip()); } - if (this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].classicWinCount > 0 && this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId(true)].classicWinCount > 0) { + if (globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].classicWinCount > 0 && globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId(true)].classicWinCount > 0) { this.championRibbon.setVisible(true); } else { this.championRibbon.setVisible(false); } - let currentFriendship = this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].friendship; + let currentFriendship = globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].friendship; if (!currentFriendship || currentFriendship === undefined) { currentFriendship = 0; } @@ -367,17 +370,17 @@ export default class SummaryUiHandler extends UiHandler { const candyCropY = 16 - (16 * (currentFriendship / friendshipCap)); if (this.candyShadow.visible) { - this.candyShadow.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true)); - this.candyShadow.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.candyShadow.on("pointerover", () => globalScene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true)); + this.candyShadow.on("pointerout", () => globalScene.ui.hideTooltip()); } - this.candyCountText.setText(`x${this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].candyCount}`); + this.candyCountText.setText(`x${globalScene.gameData.starterData[this.pokemon.species.getRootSpeciesId()].candyCount}`); this.candyShadow.setCrop(0, 0, 16, candyCropY); if (this.friendshipShadow.visible) { - this.friendshipShadow.on("pointerover", () => this.scene.ui.showTooltip("", `${i18next.t("pokemonSummary:friendship")}`, true)); - this.friendshipShadow.on("pointerout", () => this.scene.ui.hideTooltip()); + this.friendshipShadow.on("pointerover", () => globalScene.ui.showTooltip("", `${i18next.t("pokemonSummary:friendship")}`, true)); + this.friendshipShadow.on("pointerout", () => globalScene.ui.hideTooltip()); } this.friendshipText.setText(`${this.pokemon?.friendship || "0"} / 255`); @@ -395,8 +398,8 @@ export default class SummaryUiHandler extends UiHandler { const shinyDescriptor = doubleShiny || baseVariant ? `${baseVariant === 2 ? i18next.t("common:epicShiny") : baseVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}${doubleShiny ? `/${this.pokemon.fusionVariant === 2 ? i18next.t("common:epicShiny") : this.pokemon.fusionVariant === 1 ? i18next.t("common:rareShiny") : i18next.t("common:commonShiny")}` : ""}` : ""; - this.shinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); - this.shinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + this.shinyIcon.on("pointerover", () => globalScene.ui.showTooltip("", `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); + this.shinyIcon.on("pointerout", () => globalScene.ui.hideTooltip()); } this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y); @@ -560,7 +563,7 @@ export default class SummaryUiHandler extends UiHandler { break; } const isDown = button === Button.DOWN; - const party = this.scene.getPlayerParty(); + const party = globalScene.getPlayerParty(); const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { const page = this.cursor; @@ -621,7 +624,7 @@ export default class SummaryUiHandler extends UiHandler { } if (moveDescriptionLineCount > 3) { - this.descriptionScrollTween = this.scene.tweens.add({ + this.descriptionScrollTween = globalScene.tweens.add({ targets: this.moveDescriptionText, delay: Utils.fixedInt(2000), loop: -1, @@ -632,7 +635,7 @@ export default class SummaryUiHandler extends UiHandler { } if (!this.moveCursorObj) { - this.moveCursorObj = this.scene.add.sprite(-2, 0, "summary_moves_cursor", "highlight"); + this.moveCursorObj = globalScene.add.sprite(-2, 0, "summary_moves_cursor", "highlight"); this.moveCursorObj.setOrigin(0, 1); this.movesContainer.add(this.moveCursorObj); } @@ -643,12 +646,12 @@ export default class SummaryUiHandler extends UiHandler { this.moveCursorBlinkTimer.destroy(); } this.moveCursorObj.setVisible(true); - this.moveCursorBlinkTimer = this.scene.time.addEvent({ + this.moveCursorBlinkTimer = globalScene.time.addEvent({ loop: true, delay: Utils.fixedInt(600), callback: () => { this.moveCursorObj?.setVisible(false); - this.scene.time.delayedCall(Utils.fixedInt(100), () => { + globalScene.time.delayedCall(Utils.fixedInt(100), () => { if (!this.moveCursorObj) { return; } @@ -658,7 +661,7 @@ export default class SummaryUiHandler extends UiHandler { }); if (this.selectedMoveIndex > -1) { if (!this.selectedMoveCursorObj) { - this.selectedMoveCursorObj = this.scene.add.sprite(-2, 0, "summary_moves_cursor", "select"); + this.selectedMoveCursorObj = globalScene.add.sprite(-2, 0, "summary_moves_cursor", "select"); this.selectedMoveCursorObj.setOrigin(0, 1); this.movesContainer.add(this.selectedMoveCursorObj); this.movesContainer.moveBelow(this.selectedMoveCursorObj, this.moveCursorObj); @@ -684,7 +687,7 @@ export default class SummaryUiHandler extends UiHandler { } else { this.populatePageContainer(this.summaryPageContainer); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.summaryPageTransitionContainer, x: forward ? "-=214" : "+=214", duration: 250, @@ -737,27 +740,27 @@ export default class SummaryUiHandler extends UiHandler { switch (page) { case Page.PROFILE: - const profileContainer = this.scene.add.container(0, -pageBg.height); + const profileContainer = globalScene.add.container(0, -pageBg.height); pageContainer.add(profileContainer); // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs - const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); + const trainerText = addBBCodeTextObject(7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), globalScene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); trainerText.setOrigin(0, 0); profileContainer.add(trainerText); - const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); + const trainerIdText = addTextObject(174, 12, globalScene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); trainerIdText.setOrigin(0, 0); profileContainer.add(trainerIdText); - const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); + const typeLabel = addTextObject(7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); typeLabel.setOrigin(0, 0); profileContainer.add(typeLabel); const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; const typeIcon = !tera - ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) - : this.scene.add.sprite(xCoord, 42, "type_tera"); + ? globalScene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) + : globalScene.add.sprite(xCoord, 42, "type_tera"); if (tera) { typeIcon.setScale(0.5); const typeRgb = getTypeRgb(type); @@ -777,18 +780,18 @@ export default class SummaryUiHandler extends UiHandler { } if (this.pokemon?.getLuck()) { - const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); + const luckLabelText = addTextObject(141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); luckLabelText.setOrigin(0, 0); profileContainer.add(luckLabelText); - const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); + const luckText = addTextObject(141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); luckText.setOrigin(0, 0); luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); profileContainer.add(luckText); } this.abilityContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), + labelImage: globalScene.add.image(0, 0, "summary_profile_ability"), ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? nameText: null, descriptionText: null }; @@ -797,14 +800,14 @@ export default class SummaryUiHandler extends UiHandler { // Only add to the array and set up displaying a passive if it's unlocked if (this.pokemon?.hasPassive()) { this.passiveContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), + labelImage: globalScene.add.image(0, 0, "summary_profile_passive"), ability: this.pokemon.getPassiveAbility(), nameText: null, descriptionText: null }; allAbilityInfo.push(this.passiveContainer); // Sets up the pixel button prompt image - this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt = globalScene.add.image(0, 0, !globalScene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); this.abilityPrompt.setPosition(8, 43); this.abilityPrompt.setVisible(true); this.abilityPrompt.setOrigin(0, 0); @@ -817,16 +820,16 @@ export default class SummaryUiHandler extends UiHandler { abilityInfo.labelImage.setOrigin(0, 0); profileContainer.add(abilityInfo.labelImage); - abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? + abilityInfo.nameText = addTextObject(7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? abilityInfo.nameText.setOrigin(0, 1); profileContainer.add(abilityInfo.nameText); - abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? + abilityInfo.descriptionText = addTextObject(7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? abilityInfo.descriptionText.setOrigin(0, 0); profileContainer.add(abilityInfo.descriptionText); // Sets up the mask that hides the description text to give an illusion of scrolling - const descriptionTextMaskRect = this.scene.make.graphics({}); + const descriptionTextMaskRect = globalScene.make.graphics({}); descriptionTextMaskRect.setScale(6); descriptionTextMaskRect.fillStyle(0xFFFFFF); descriptionTextMaskRect.beginPath(); @@ -841,7 +844,7 @@ export default class SummaryUiHandler extends UiHandler { // Animates the description text moving upwards if (abilityDescriptionLineCount > 2) { abilityInfo.descriptionText.setY(69); - this.descriptionScrollTween = this.scene.tweens.add({ + this.descriptionScrollTween = globalScene.tweens.add({ targets: abilityInfo.descriptionText, delay: Utils.fixedInt(2000), loop: -1, @@ -869,12 +872,12 @@ export default class SummaryUiHandler extends UiHandler { natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) }); - const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); + const memoText = addBBCodeTextObject(7, 113, String(memoString), TextStyle.WINDOW_ALT); memoText.setOrigin(0, 0); profileContainer.add(memoText); break; case Page.STATS: - const statsContainer = this.scene.add.container(0, -pageBg.height); + const statsContainer = globalScene.add.container(0, -pageBg.height); pageContainer.add(statsContainer); PERMANENT_STATS.forEach((stat, s) => { @@ -884,7 +887,7 @@ export default class SummaryUiHandler extends UiHandler { const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? - const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + const statLabel = addTextObject(27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); statLabel.setOrigin(0.5, 0); statsContainer.add(statLabel); @@ -892,24 +895,24 @@ export default class SummaryUiHandler extends UiHandler { ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? - const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); + const statValue = addTextObject(120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); statValue.setOrigin(1, 0); statsContainer.add(statValue); }); - const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const itemModifiers = (globalScene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) .sort(modifierSortFunc); itemModifiers.forEach((item, i) => { - const icon = item.getIcon(this.scene, true); + const icon = item.getIcon(true); icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); statsContainer.add(icon); icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); - icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + icon.on("pointerover", () => globalScene.ui.showTooltip(item.type.name, item.type.getDescription(), true)); + icon.on("pointerout", () => globalScene.ui.hideTooltip()); }); const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? @@ -917,32 +920,32 @@ export default class SummaryUiHandler extends UiHandler { const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); - const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; + const expRatio = pkmLvl < globalScene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; - const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); + const expLabel = addTextObject(6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); expLabel.setOrigin(0, 0); statsContainer.add(expLabel); - const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); + const nextLvExpLabel = addTextObject(6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); nextLvExpLabel.setOrigin(0, 0); statsContainer.add(nextLvExpLabel); - const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); + const expText = addTextObject(208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); expText.setOrigin(1, 0); statsContainer.add(expText); - const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() + const nextLvExp = pkmLvl < globalScene.getMaxExpLevel() ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp : 0; - const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); + const nextLvExpText = addTextObject(208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); nextLvExpText.setOrigin(1, 0); statsContainer.add(nextLvExpText); - const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); + const expOverlay = globalScene.add.image(140, 145, "summary_stats_overlay_exp"); expOverlay.setOrigin(0, 0); statsContainer.add(expOverlay); - const expMaskRect = this.scene.make.graphics({}); + const expMaskRect = globalScene.make.graphics({}); expMaskRect.setScale(6); expMaskRect.fillStyle(0xFFFFFF); expMaskRect.beginPath(); @@ -953,18 +956,18 @@ export default class SummaryUiHandler extends UiHandler { expOverlay.setMask(expMask); break; case Page.MOVES: - this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); + this.movesContainer = globalScene.add.container(5, -pageBg.height + 26); pageContainer.add(this.movesContainer); - this.extraMoveRowContainer = this.scene.add.container(0, 64); + this.extraMoveRowContainer = globalScene.add.container(0, 64); this.extraMoveRowContainer.setVisible(false); this.movesContainer.add(this.extraMoveRowContainer); - const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); + const extraRowOverlay = globalScene.add.image(-2, 1, "summary_moves_overlay_row"); extraRowOverlay.setOrigin(0, 1); this.extraMoveRowContainer.add(extraRowOverlay); - const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), + const extraRowText = addTextObject(35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); extraRowText.setOrigin(0, 1); this.extraMoveRowContainer.add(extraRowText); @@ -975,45 +978,45 @@ export default class SummaryUiHandler extends UiHandler { if (this.newMove && this.pokemon) { const spriteKey = Utils.getLocalizedSpriteKey("types"); const moveType = this.pokemon.getMoveType(this.newMove); - const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + const newMoveTypeIcon = globalScene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); newMoveTypeIcon.setOrigin(0, 1); this.extraMoveRowContainer.add(newMoveTypeIcon); } - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + const ppOverlay = globalScene.add.image(163, -1, "summary_moves_overlay_pp"); ppOverlay.setOrigin(0, 1); this.extraMoveRowContainer.add(ppOverlay); const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? - const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); + const ppText = addTextObject(173, 1, `${pp}/${pp}`, TextStyle.WINDOW); ppText.setOrigin(0, 1); this.extraMoveRowContainer.add(ppText); } - this.moveRowsContainer = this.scene.add.container(0, 0); + this.moveRowsContainer = globalScene.add.container(0, 0); this.movesContainer.add(this.moveRowsContainer); for (let m = 0; m < 4; m++) { const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; - const moveRowContainer = this.scene.add.container(0, 16 * m); + const moveRowContainer = globalScene.add.container(0, 16 * m); this.moveRowsContainer.add(moveRowContainer); if (move && this.pokemon) { const spriteKey = Utils.getLocalizedSpriteKey("types"); const moveType = this.pokemon.getMoveType(move.getMove()); - const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + const typeIcon = globalScene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); typeIcon.setOrigin(0, 1); moveRowContainer.add(typeIcon); } - const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); + const moveText = addTextObject(35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); moveText.setOrigin(0, 1); moveRowContainer.add(moveText); - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + const ppOverlay = globalScene.add.image(163, -1, "summary_moves_overlay_pp"); ppOverlay.setOrigin(0, 1); moveRowContainer.add(ppOverlay); - const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); + const ppText = addTextObject(173, 1, "--/--", TextStyle.WINDOW); ppText.setOrigin(0, 1); if (move) { @@ -1025,10 +1028,10 @@ export default class SummaryUiHandler extends UiHandler { moveRowContainer.add(ppText); } - this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); + this.moveDescriptionText = addTextObject(2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); this.movesContainer.add(this.moveDescriptionText); - const moveDescriptionTextMaskRect = this.scene.make.graphics({}); + const moveDescriptionTextMaskRect = globalScene.make.graphics({}); moveDescriptionTextMaskRect.setScale(6); moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); moveDescriptionTextMaskRect.beginPath(); @@ -1046,7 +1049,7 @@ export default class SummaryUiHandler extends UiHandler { return; } this.statusVisible = true; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.statusContainer, x: 0, duration: instant ? 0 : 250, @@ -1059,7 +1062,7 @@ export default class SummaryUiHandler extends UiHandler { return; } this.statusVisible = false; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.statusContainer, x: -106, duration: instant ? 0 : 250, @@ -1122,7 +1125,7 @@ export default class SummaryUiHandler extends UiHandler { return; } this.moveEffectsVisible = true; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.moveEffectContainer, x: 6, duration: instant ? 0 : 250, @@ -1135,7 +1138,7 @@ export default class SummaryUiHandler extends UiHandler { return; } this.moveEffectsVisible = false; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.moveEffectContainer, x: 106, duration: instant ? 0 : 250, diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 249ae7b8b01..232ec887b0a 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -1,14 +1,14 @@ import { BattlerIndex } from "../battle"; -import BattleScene from "../battle-scene"; import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; import { getMoveTargets } from "../data/move"; import { Button } from "#enums/buttons"; -import { Moves } from "#enums/moves"; -import Pokemon from "#app/field/pokemon"; -import { ModifierBar } from "#app/modifier/modifier"; +import type { Moves } from "#enums/moves"; +import type Pokemon from "#app/field/pokemon"; +import type { ModifierBar } from "#app/modifier/modifier"; import { SubstituteTag } from "#app/data/battler-tags"; +import { globalScene } from "#app/global-scene"; export type TargetSelectCallback = (targets: BattlerIndex[]) => void; @@ -26,8 +26,8 @@ export default class TargetSelectUiHandler extends UiHandler { private enemyModifiers: ModifierBar; private targetBattleInfoMoveTween: Phaser.Tweens.Tween[] = []; - constructor(scene: BattleScene) { - super(scene, Mode.TARGET_SELECT); + constructor() { + super(Mode.TARGET_SELECT); this.cursor = -1; } @@ -44,7 +44,7 @@ export default class TargetSelectUiHandler extends UiHandler { this.fieldIndex = args[0] as integer; this.move = args[1] as Moves; this.targetSelectCallback = args[2] as TargetSelectCallback; - const user = this.scene.getPlayerField()[this.fieldIndex]; + const user = globalScene.getPlayerField()[this.fieldIndex]; const moveTargets = getMoveTargets(user, this.move); this.targets = moveTargets.targets; @@ -54,7 +54,7 @@ export default class TargetSelectUiHandler extends UiHandler { return false; } - this.enemyModifiers = this.scene.getModifierBar(true); + this.enemyModifiers = globalScene.getModifierBar(true); if (this.fieldIndex === BattlerIndex.PLAYER) { this.resetCursor(this.cursor0, user); @@ -132,8 +132,8 @@ export default class TargetSelectUiHandler extends UiHandler { } setCursor(cursor: integer): boolean { - const singleTarget = this.scene.getField()[cursor]; - const multipleTargets = this.targets.map(index => this.scene.getField()[index]); + const singleTarget = globalScene.getField()[cursor]; + const multipleTargets = this.targets.map(index => globalScene.getField()[index]); this.targetsHighlighted = this.isMultipleTargets ? multipleTargets : [ singleTarget ]; @@ -147,7 +147,7 @@ export default class TargetSelectUiHandler extends UiHandler { } } - this.targetFlashTween = this.scene.tweens.add({ + this.targetFlashTween = globalScene.tweens.add({ targets: this.targetsHighlighted, key: { start: 1, to: 0.25 }, loop: -1, @@ -173,7 +173,7 @@ export default class TargetSelectUiHandler extends UiHandler { const targetsBattleInfo = this.targetsHighlighted.map(target => target.getBattleInfo()); targetsBattleInfo.map(info => { - this.targetBattleInfoMoveTween.push(this.scene.tweens.add({ + this.targetBattleInfoMoveTween.push(globalScene.tweens.add({ targets: [ info ], y: { start: info.getBaseY(), to: info.getBaseY() + 1 }, loop: -1, diff --git a/src/ui/test-dialogue-ui-handler.ts b/src/ui/test-dialogue-ui-handler.ts index bf0e7f6723f..c7693ec954d 100644 --- a/src/ui/test-dialogue-ui-handler.ts +++ b/src/ui/test-dialogue-ui-handler.ts @@ -1,8 +1,9 @@ -import { FormModalUiHandler, InputFieldConfig } from "./form-modal-ui-handler"; -import { ModalConfig } from "./modal-ui-handler"; +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; import i18next from "i18next"; -import { PlayerPokemon } from "#app/field/pokemon"; -import { OptionSelectItem } from "./abstact-option-select-ui-handler"; +import type { PlayerPokemon } from "#app/field/pokemon"; +import type { OptionSelectItem } from "./abstact-option-select-ui-handler"; import { isNullOrUndefined } from "#app/utils"; import { Mode } from "./ui"; @@ -10,8 +11,8 @@ export default class TestDialogueUiHandler extends FormModalUiHandler { keys: string[]; - constructor(scene, mode) { - super(scene, mode); + constructor(mode) { + super(mode); } setup() { diff --git a/src/ui/text.ts b/src/ui/text.ts index 17ae02be9ef..cdd1142e7ee 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -1,9 +1,9 @@ import { EggTier } from "#enums/egg-type"; import { UiTheme } from "#enums/ui-theme"; -import Phaser from "phaser"; +import type Phaser from "phaser"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText"; import InputText from "phaser3-rex-plugins/plugins/inputtext"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; import { ModifierTier } from "../modifier/modifier-tier"; import i18next from "#app/plugins/i18n"; @@ -52,10 +52,10 @@ export interface TextStyleOptions { shadowYpos: number } -export function addTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): Phaser.GameObjects.Text { - const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); +export function addTextObject(x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): Phaser.GameObjects.Text { + const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, globalScene.uiTheme, extraStyleOptions); - const ret = scene.add.text(x, y, content, styleOptions); + const ret = globalScene.add.text(x, y, content, styleOptions); ret.setScale(scale); ret.setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) { @@ -69,8 +69,8 @@ export function addTextObject(scene: Phaser.Scene, x: number, y: number, content return ret; } -export function setTextStyle(obj: Phaser.GameObjects.Text, scene: Phaser.Scene, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle) { - const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); +export function setTextStyle(obj: Phaser.GameObjects.Text, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle) { + const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, globalScene.uiTheme, extraStyleOptions); obj.setScale(scale); obj.setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as Phaser.Types.GameObjects.Text.TextStyle).lineSpacing) { @@ -82,11 +82,11 @@ export function setTextStyle(obj: Phaser.GameObjects.Text, scene: Phaser.Scene, } } -export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): BBCodeText { - const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); +export function addBBCodeTextObject(x: number, y: number, content: string, style: TextStyle, extraStyleOptions?: Phaser.Types.GameObjects.Text.TextStyle): BBCodeText { + const { scale, styleOptions, shadowColor, shadowXpos, shadowYpos } = getTextStyleOptions(style, globalScene.uiTheme, extraStyleOptions); - const ret = new BBCodeText(scene, x, y, content, styleOptions as BBCodeText.TextStyle); - scene.add.existing(ret); + const ret = new BBCodeText(globalScene, x, y, content, styleOptions as BBCodeText.TextStyle); + globalScene.add.existing(ret); ret.setScale(scale); ret.setShadow(shadowXpos, shadowYpos, shadowColor); if (!(styleOptions as BBCodeText.TextStyle).lineSpacing) { @@ -100,11 +100,11 @@ export function addBBCodeTextObject(scene: Phaser.Scene, x: number, y: number, c return ret; } -export function addTextInputObject(scene: Phaser.Scene, x: number, y: number, width: number, height: number, style: TextStyle, extraStyleOptions?: InputText.IConfig): InputText { - const { scale, styleOptions } = getTextStyleOptions(style, (scene as BattleScene).uiTheme, extraStyleOptions); +export function addTextInputObject(x: number, y: number, width: number, height: number, style: TextStyle, extraStyleOptions?: InputText.IConfig): InputText { + const { scale, styleOptions } = getTextStyleOptions(style, globalScene.uiTheme, extraStyleOptions); - const ret = new InputText(scene, x, y, width, height, styleOptions as InputText.IConfig); - scene.add.existing(ret); + const ret = new InputText(globalScene, x, y, width, height, styleOptions as InputText.IConfig); + globalScene.add.existing(ret); ret.setScale(scale); return ret; diff --git a/src/ui/time-of-day-widget.ts b/src/ui/time-of-day-widget.ts index 66fe5cc9ac3..2a33b475385 100644 --- a/src/ui/time-of-day-widget.ts +++ b/src/ui/time-of-day-widget.ts @@ -1,13 +1,11 @@ import * as Utils from "../utils"; -import BattleScene from "#app/battle-scene"; +import { globalScene } from "#app/global-scene"; import { BattleSceneEventType } from "../events/battle-scene"; import { EaseType } from "#enums/ease-type"; import { TimeOfDay } from "#enums/time-of-day"; /** A small self contained UI element that displays the time of day as an icon */ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { - /** An alias for the scene typecast to a {@linkcode BattleScene} */ - private battleScene: BattleScene; /** The {@linkcode Phaser.GameObjects.Sprite} that represents the foreground of the current time of day */ private readonly timeOfDayIconFgs: Phaser.GameObjects.Sprite[] = new Array(2); @@ -42,19 +40,18 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { public set parentVisible(visible: boolean) { if (visible && !this._parentVisible) { // Only resume the tweens if parent is newly visible this.timeOfDayIcons?.forEach( - icon => this.scene.tweens.getTweensOf(icon).forEach( + icon => globalScene.tweens.getTweensOf(icon).forEach( tween => tween.resume())); } this._parentVisible = visible; } - constructor(scene: Phaser.Scene, x: number = 0, y: number = 0) { - super(scene, x, y); - this.battleScene = this.scene as BattleScene; + constructor(x: number = 0, y: number = 0) { + super(globalScene, x, y); - this.setVisible(this.battleScene.showTimeOfDayWidget); - if (!this.battleScene.showTimeOfDayWidget) { + this.setVisible(globalScene.showTimeOfDayWidget); + if (!globalScene.showTimeOfDayWidget) { return; } @@ -62,14 +59,14 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { this.timeOfDayIconPairs.forEach( (icons, key) => { for (let i = 0; i < icons.length; i++) { - icons[i] = this.scene.add.sprite(0, 0, "dawn_icon_" + key).setOrigin(); + icons[i] = globalScene.add.sprite(0, 0, "dawn_icon_" + key).setOrigin(); } }); // Store a flat array of all icons for later this.timeOfDayIcons = [ this.timeOfDayIconBgs, this.timeOfDayIconMgs, this.timeOfDayIconFgs ].flat(); this.add(this.timeOfDayIcons); - this.battleScene.eventTarget.addEventListener(BattleSceneEventType.ENCOUNTER_PHASE, this.onEncounterPhaseEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.ENCOUNTER_PHASE, this.onEncounterPhaseEvent); } /** @@ -136,13 +133,13 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { /** Adds the proper tween for all icons */ private tweenTimeOfDayIcon() { - this.scene.tweens.killTweensOf(this.timeOfDayIcons); + globalScene.tweens.killTweensOf(this.timeOfDayIcons); this.resetIcons(); // Tween based on the player setting - (this.battleScene.timeOfDayAnimation === EaseType.BACK ? this.getBackTween() : this.getBounceTween()) - .forEach(tween => this.scene.tweens.add(tween)); + (globalScene.timeOfDayAnimation === EaseType.BACK ? this.getBackTween() : this.getBounceTween()) + .forEach(tween => globalScene.tweens.add(tween)); // Swaps all elements of the icon arrays by shifting the first element onto the end of the array // This ensures index[0] is always the new time of day icon and index[1] is always the current one @@ -158,7 +155,7 @@ export default class TimeOfDayWidget extends Phaser.GameObjects.Container { * @param event {@linkcode Event} being sent */ private onEncounterPhase(event: Event) { - const newTime = this.battleScene.arena.getTimeOfDay(); + const newTime = globalScene.arena.getTimeOfDay(); if (this.currentTime === newTime) { return; diff --git a/src/ui/title-ui-handler.ts b/src/ui/title-ui-handler.ts index aec80f049c9..538f78e877e 100644 --- a/src/ui/title-ui-handler.ts +++ b/src/ui/title-ui-handler.ts @@ -1,4 +1,3 @@ -import BattleScene from "../battle-scene"; import OptionSelectUiHandler from "./settings/option-select-ui-handler"; import { Mode } from "./ui"; import * as Utils from "../utils"; @@ -8,6 +7,7 @@ import i18next from "i18next"; import { TimedEventDisplay } from "#app/timed-event-manager"; import { version } from "../../package.json"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { globalScene } from "#app/global-scene"; export default class TitleUiHandler extends OptionSelectUiHandler { /** If the stats can not be retrieved, use this fallback value */ @@ -22,8 +22,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler { private titleStatsTimer: NodeJS.Timeout | null; - constructor(scene: BattleScene, mode: Mode = Mode.TITLE) { - super(scene, mode); + constructor(mode: Mode = Mode.TITLE) { + super(mode); } setup() { @@ -31,25 +31,24 @@ export default class TitleUiHandler extends OptionSelectUiHandler { const ui = this.getUi(); - this.titleContainer = this.scene.add.container(0, -(this.scene.game.canvas.height / 6)); + this.titleContainer = globalScene.add.container(0, -(globalScene.game.canvas.height / 6)); this.titleContainer.setName("title"); this.titleContainer.setAlpha(0); ui.add(this.titleContainer); - const logo = this.scene.add.image((this.scene.game.canvas.width / 6) / 2, 8, "logo"); + const logo = globalScene.add.image((globalScene.game.canvas.width / 6) / 2, 8, "logo"); logo.setOrigin(0.5, 0); this.titleContainer.add(logo); - if (this.scene.eventManager.isEventActive()) { - this.eventDisplay = new TimedEventDisplay(this.scene, 0, 0, this.scene.eventManager.activeEvent()); + if (globalScene.eventManager.isEventActive()) { + this.eventDisplay = new TimedEventDisplay(0, 0, globalScene.eventManager.activeEvent()); this.eventDisplay.setup(); this.titleContainer.add(this.eventDisplay); } this.playerCountLabel = addTextObject( - this.scene, - (this.scene.game.canvas.width / 6) - 2, - (this.scene.game.canvas.height / 6) - 13 - 576 * getTextStyleOptions(TextStyle.WINDOW, this.scene.uiTheme).scale, + (globalScene.game.canvas.width / 6) - 2, + (globalScene.game.canvas.height / 6) - 13 - 576 * getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale, `? ${i18next.t("menu:playersOnline")}`, TextStyle.MESSAGE, { fontSize: "54px" } @@ -57,14 +56,14 @@ export default class TitleUiHandler extends OptionSelectUiHandler { this.playerCountLabel.setOrigin(1, 0); this.titleContainer.add(this.playerCountLabel); - this.splashMessageText = addTextObject(this.scene, logo.x + 64, logo.y + logo.displayHeight - 8, "", TextStyle.MONEY, { fontSize: "54px" }); + this.splashMessageText = addTextObject(logo.x + 64, logo.y + logo.displayHeight - 8, "", TextStyle.MONEY, { fontSize: "54px" }); this.splashMessageText.setOrigin(0.5, 0.5); this.splashMessageText.setAngle(-20); this.titleContainer.add(this.splashMessageText); const originalSplashMessageScale = this.splashMessageText.scale; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.splashMessageText, duration: Utils.fixedInt(350), scale: originalSplashMessageScale * 1.25, @@ -72,7 +71,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { yoyo: true, }); - this.appVersionText = addTextObject(this.scene, logo.x - 60, logo.y + logo.displayHeight + 4, "", TextStyle.MONEY, { fontSize: "54px" }); + this.appVersionText = addTextObject(logo.x - 60, logo.y + logo.displayHeight + 4, "", TextStyle.MONEY, { fontSize: "54px" }); this.appVersionText.setOrigin(0.5, 0.5); this.appVersionText.setAngle(0); this.titleContainer.add(this.appVersionText); @@ -104,8 +103,8 @@ export default class TitleUiHandler extends OptionSelectUiHandler { const ui = this.getUi(); - if (this.scene.eventManager.isEventActive()) { - this.eventDisplay.setWidth(this.scene.scaledCanvas.width - this.optionSelectBg.width - this.optionSelectBg.x); + if (globalScene.eventManager.isEventActive()) { + this.eventDisplay.setWidth(globalScene.scaledCanvas.width - this.optionSelectBg.width - this.optionSelectBg.x); this.eventDisplay.show(); } @@ -115,7 +114,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { this.updateTitleStats(); }, 60000); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.titleContainer, ui.getMessageHandler().bg ], duration: Utils.fixedInt(325), alpha: (target: any) => target === this.titleContainer ? 1 : 0, @@ -136,7 +135,7 @@ export default class TitleUiHandler extends OptionSelectUiHandler { this.titleStatsTimer && clearInterval(this.titleStatsTimer); this.titleStatsTimer = null; - this.scene.tweens.add({ + globalScene.tweens.add({ targets: [ this.titleContainer, ui.getMessageHandler().bg ], duration: Utils.fixedInt(325), alpha: (target: any) => target === this.titleContainer ? 0 : 1, diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts index bb7b1e038db..1f0155aef8b 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -1,23 +1,21 @@ -import BattleScene from "../battle-scene"; -import { TextStyle, getTextColor } from "./text"; -import { Mode } from "./ui"; -import { Button } from "#enums/buttons"; +import { globalScene } from "#app/global-scene"; +import type { TextStyle } from "./text"; +import { getTextColor } from "./text"; +import type { Mode } from "./ui"; +import type { Button } from "#enums/buttons"; /** * A basic abstract class to act as a holder and processor for UI elements. */ export default abstract class UiHandler { - protected scene: BattleScene; protected mode: integer | null; protected cursor: integer = 0; public active: boolean = false; /** - * @param {BattleScene} scene The same scene as everything else. - * @param {Mode} mode The mode of the UI element. These should be unique. + * @param mode The mode of the UI element. These should be unique. */ - constructor(scene: BattleScene, mode: Mode | null = null) { - this.scene = scene; + constructor(mode: Mode | null = null) { this.mode = mode; } @@ -32,11 +30,11 @@ export default abstract class UiHandler { abstract processInput(button: Button): boolean; getUi() { - return this.scene.ui; + return globalScene.ui; } getTextColor(style: TextStyle, shadow: boolean = false): string { - return getTextColor(style, shadow, this.scene.uiTheme); + return getTextColor(style, shadow, globalScene.uiTheme); } getCursor(): integer { @@ -58,7 +56,7 @@ export default abstract class UiHandler { * @param cursorStyle cursor style to apply */ protected setMouseCursorStyle(cursorStyle: "pointer" | "default") { - this.scene.input.manager.canvas.style.cursor = cursorStyle; + globalScene.input.manager.canvas.style.cursor = cursorStyle; } clear() { diff --git a/src/ui/ui-theme.ts b/src/ui/ui-theme.ts index 89c56384bd0..0d42815f842 100644 --- a/src/ui/ui-theme.ts +++ b/src/ui/ui-theme.ts @@ -1,6 +1,6 @@ import { UiTheme } from "#enums/ui-theme"; import { legacyCompatibleImages } from "#app/scene-base"; -import BattleScene from "../battle-scene"; +import { globalScene } from "#app/global-scene"; export enum WindowVariant { NORMAL, @@ -36,14 +36,14 @@ const windowTypeControlColors = { } }; -export function addWindow(scene: BattleScene, x: number, y: number, width: number, height: number, mergeMaskTop?: boolean, mergeMaskLeft?: boolean, maskOffsetX?: number, maskOffsetY?: number, windowVariant?: WindowVariant): Phaser.GameObjects.NineSlice { +export function addWindow(x: number, y: number, width: number, height: number, mergeMaskTop?: boolean, mergeMaskLeft?: boolean, maskOffsetX?: number, maskOffsetY?: number, windowVariant?: WindowVariant): Phaser.GameObjects.NineSlice { if (windowVariant === undefined) { windowVariant = WindowVariant.NORMAL; } - const borderSize = scene.uiTheme ? 6 : 8; + const borderSize = globalScene.uiTheme ? 6 : 8; - const window = scene.add.nineslice(x, y, `window_${scene.windowType}${getWindowVariantSuffix(windowVariant)}`, undefined, width, height, borderSize, borderSize, borderSize, borderSize); + const window = globalScene.add.nineslice(x, y, `window_${globalScene.windowType}${getWindowVariantSuffix(windowVariant)}`, undefined, width, height, borderSize, borderSize, borderSize, borderSize); window.setOrigin(0, 0); if (mergeMaskLeft || mergeMaskTop || maskOffsetX || maskOffsetY) { @@ -54,7 +54,7 @@ export function addWindow(scene: BattleScene, x: number, y: number, width: numbe * height: bottom */ const maskRect = new Phaser.GameObjects.Rectangle( - scene, + globalScene, 6 * (x - (mergeMaskLeft ? 2 : 0) - (maskOffsetX || 0)), 6 * (y + (mergeMaskTop ? 2 : 0) + (maskOffsetY || 0)), width - (mergeMaskLeft ? 2 : 0), @@ -70,7 +70,7 @@ export function addWindow(scene: BattleScene, x: number, y: number, width: numbe return window; } -export function updateWindowType(scene: BattleScene, windowTypeIndex: integer): void { +export function updateWindowType(windowTypeIndex: integer): void { const windowObjects: [Phaser.GameObjects.NineSlice, WindowVariant][] = []; const themedObjects: (Phaser.GameObjects.Image | Phaser.GameObjects.NineSlice)[] = []; const traverse = (object: any) => { @@ -96,12 +96,12 @@ export function updateWindowType(scene: BattleScene, windowTypeIndex: integer): } }; - traverse(scene); + traverse(globalScene); - scene.windowType = windowTypeIndex; + globalScene.windowType = windowTypeIndex; const rootStyle = document.documentElement.style; - [ "base", "light", "dark" ].map((k, i) => rootStyle.setProperty(`--color-${k}`, windowTypeControlColors[scene.uiTheme][windowTypeIndex - 1][i])); + [ "base", "light", "dark" ].map((k, i) => rootStyle.setProperty(`--color-${k}`, windowTypeControlColors[globalScene.uiTheme][windowTypeIndex - 1][i])); const windowKey = `window_${windowTypeIndex}`; @@ -114,11 +114,11 @@ export function updateWindowType(scene: BattleScene, windowTypeIndex: integer): } } -export function addUiThemeOverrides(scene: BattleScene): void { - const originalAddImage = scene.add.image; - scene.add.image = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number): Phaser.GameObjects.Image { +export function addUiThemeOverrides(): void { + const originalAddImage = globalScene.add.image; + globalScene.add.image = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number): Phaser.GameObjects.Image { let legacy = false; - if (typeof texture === "string" && scene.uiTheme && legacyCompatibleImages.includes(texture)) { + if (typeof texture === "string" && globalScene.uiTheme && legacyCompatibleImages.includes(texture)) { legacy = true; texture += "_legacy"; } @@ -133,10 +133,10 @@ export function addUiThemeOverrides(scene: BattleScene): void { return ret; }; - const originalAddSprite = scene.add.sprite; - scene.add.sprite = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number): Phaser.GameObjects.Sprite { + const originalAddSprite = globalScene.add.sprite; + globalScene.add.sprite = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number): Phaser.GameObjects.Sprite { let legacy = false; - if (typeof texture === "string" && scene.uiTheme && legacyCompatibleImages.includes(texture)) { + if (typeof texture === "string" && globalScene.uiTheme && legacyCompatibleImages.includes(texture)) { legacy = true; texture += "_legacy"; } @@ -151,10 +151,10 @@ export function addUiThemeOverrides(scene: BattleScene): void { return ret; }; - const originalAddNineslice = scene.add.nineslice; - scene.add.nineslice = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, width?: number, height?: number, leftWidth?: number, rightWidth?: number, topHeight?: number, bottomHeight?: number): Phaser.GameObjects.NineSlice { + const originalAddNineslice = globalScene.add.nineslice; + globalScene.add.nineslice = function (x: number, y: number, texture: string | Phaser.Textures.Texture, frame?: string | number, width?: number, height?: number, leftWidth?: number, rightWidth?: number, topHeight?: number, bottomHeight?: number): Phaser.GameObjects.NineSlice { let legacy = false; - if (typeof texture === "string" && scene.uiTheme && legacyCompatibleImages.includes(texture)) { + if (typeof texture === "string" && globalScene.uiTheme && legacyCompatibleImages.includes(texture)) { legacy = true; texture += "_legacy"; } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index fc8fa94c848..6d44997f649 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -1,5 +1,5 @@ -import { default as BattleScene } from "../battle-scene"; -import UiHandler from "./ui-handler"; +import { globalScene } from "#app/global-scene"; +import type UiHandler from "./ui-handler"; import BattleMessageUiHandler from "./battle-message-ui-handler"; import CommandUiHandler from "./command-ui-handler"; import PartyUiHandler from "./party-ui-handler"; @@ -35,7 +35,7 @@ import TitleUiHandler from "./title-ui-handler"; import SavingIconHandler from "./saving-icon-handler"; import UnavailableModalUiHandler from "./unavailable-modal-ui-handler"; import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler"; -import { Button } from "#enums/buttons"; +import type { Button } from "#enums/buttons"; import i18next from "i18next"; import GamepadBindingUiHandler from "./settings/gamepad-binding-ui-handler"; import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler"; @@ -43,7 +43,7 @@ import KeyboardBindingUiHandler from "#app/ui/settings/keyboard-binding-ui-handl import SettingsDisplayUiHandler from "./settings/settings-display-ui-handler"; import SettingsAudioUiHandler from "./settings/settings-audio-ui-handler"; import { PlayerGender } from "#enums/player-gender"; -import BgmBar from "#app/ui/bgm-bar"; +import type BgmBar from "#app/ui/bgm-bar"; import RenameFormUiHandler from "./rename-form-ui-handler"; import AdminUiHandler from "./admin-ui-handler"; import RunHistoryUiHandler from "./run-history-ui-handler"; @@ -156,55 +156,55 @@ export default class UI extends Phaser.GameObjects.Container { private overlayActive: boolean; - constructor(scene: BattleScene) { - super(scene, 0, scene.game.canvas.height / 6); + constructor() { + super(globalScene, 0, globalScene.game.canvas.height / 6); this.mode = Mode.MESSAGE; this.modeChain = []; this.handlers = [ - new BattleMessageUiHandler(scene), - new TitleUiHandler(scene), - new CommandUiHandler(scene), - new FightUiHandler(scene), - new BallUiHandler(scene), - new TargetSelectUiHandler(scene), - new ModifierSelectUiHandler(scene), - new SaveSlotSelectUiHandler(scene), - new PartyUiHandler(scene), - new SummaryUiHandler(scene), - new StarterSelectUiHandler(scene), - new EvolutionSceneHandler(scene), - new EggHatchSceneHandler(scene), - new EggSummaryUiHandler(scene), - new ConfirmUiHandler(scene), - new OptionSelectUiHandler(scene), - new MenuUiHandler(scene), - new OptionSelectUiHandler(scene, Mode.MENU_OPTION_SELECT), + new BattleMessageUiHandler(), + new TitleUiHandler(), + new CommandUiHandler(), + new FightUiHandler(), + new BallUiHandler(), + new TargetSelectUiHandler(), + new ModifierSelectUiHandler(), + new SaveSlotSelectUiHandler(), + new PartyUiHandler(), + new SummaryUiHandler(), + new StarterSelectUiHandler(), + new EvolutionSceneHandler(), + new EggHatchSceneHandler(), + new EggSummaryUiHandler(), + new ConfirmUiHandler(), + new OptionSelectUiHandler(), + new MenuUiHandler(), + new OptionSelectUiHandler(Mode.MENU_OPTION_SELECT), // settings - new SettingsUiHandler(scene), - new SettingsDisplayUiHandler(scene), - new SettingsAudioUiHandler(scene), - new SettingsGamepadUiHandler(scene), - new GamepadBindingUiHandler(scene), - new SettingsKeyboardUiHandler(scene), - new KeyboardBindingUiHandler(scene), - new AchvsUiHandler(scene), - new GameStatsUiHandler(scene), - new EggListUiHandler(scene), - new EggGachaUiHandler(scene), - new LoginFormUiHandler(scene), - new RegistrationFormUiHandler(scene), - new LoadingModalUiHandler(scene), - new SessionReloadModalUiHandler(scene), - new UnavailableModalUiHandler(scene), - new GameChallengesUiHandler(scene), - new RenameFormUiHandler(scene), - new RunHistoryUiHandler(scene), - new RunInfoUiHandler(scene), - new TestDialogueUiHandler(scene, Mode.TEST_DIALOGUE), - new AutoCompleteUiHandler(scene), - new AdminUiHandler(scene), - new MysteryEncounterUiHandler(scene), + new SettingsUiHandler(), + new SettingsDisplayUiHandler(), + new SettingsAudioUiHandler(), + new SettingsGamepadUiHandler(), + new GamepadBindingUiHandler(), + new SettingsKeyboardUiHandler(), + new KeyboardBindingUiHandler(), + new AchvsUiHandler(), + new GameStatsUiHandler(), + new EggListUiHandler(), + new EggGachaUiHandler(), + new LoginFormUiHandler(), + new RegistrationFormUiHandler(), + new LoadingModalUiHandler(), + new SessionReloadModalUiHandler(), + new UnavailableModalUiHandler(), + new GameChallengesUiHandler(), + new RenameFormUiHandler(), + new RunHistoryUiHandler(), + new RunInfoUiHandler(), + new TestDialogueUiHandler(Mode.TEST_DIALOGUE), + new AutoCompleteUiHandler(), + new AdminUiHandler(), + new MysteryEncounterUiHandler(), ]; } @@ -213,38 +213,38 @@ export default class UI extends Phaser.GameObjects.Container { for (const handler of this.handlers) { handler.setup(); } - this.overlay = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6, 0); + this.overlay = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0); this.overlay.setName("rect-ui-overlay"); this.overlay.setOrigin(0, 0); - (this.scene as BattleScene).uiContainer.add(this.overlay); + globalScene.uiContainer.add(this.overlay); this.overlay.setVisible(false); this.setupTooltip(); - this.achvBar = new AchvBar(this.scene as BattleScene); + this.achvBar = new AchvBar; this.achvBar.setup(); - (this.scene as BattleScene).uiContainer.add(this.achvBar); + globalScene.uiContainer.add(this.achvBar); - this.savingIcon = new SavingIconHandler(this.scene as BattleScene); + this.savingIcon = new SavingIconHandler; this.savingIcon.setup(); - (this.scene as BattleScene).uiContainer.add(this.savingIcon); + globalScene.uiContainer.add(this.savingIcon); } private setupTooltip() { - this.tooltipContainer = this.scene.add.container(0, 0); + this.tooltipContainer = globalScene.add.container(0, 0); this.tooltipContainer.setName("tooltip"); this.tooltipContainer.setVisible(false); - this.tooltipBg = addWindow(this.scene as BattleScene, 0, 0, 128, 31); + this.tooltipBg = addWindow(0, 0, 128, 31); this.tooltipBg.setName("window-tooltip-bg"); this.tooltipBg.setOrigin(0, 0); - this.tooltipTitle = addTextObject(this.scene, 64, 4, "", TextStyle.TOOLTIP_TITLE); + this.tooltipTitle = addTextObject(64, 4, "", TextStyle.TOOLTIP_TITLE); this.tooltipTitle.setName("text-tooltip-title"); this.tooltipTitle.setOrigin(0.5, 0); - this.tooltipContent = addTextObject(this.scene, 6, 16, "", TextStyle.TOOLTIP_CONTENT); + this.tooltipContent = addTextObject(6, 16, "", TextStyle.TOOLTIP_CONTENT); this.tooltipContent.setName("text-tooltip-content"); this.tooltipContent.setWordWrapWidth(850); @@ -252,7 +252,7 @@ export default class UI extends Phaser.GameObjects.Container { this.tooltipContainer.add(this.tooltipTitle); this.tooltipContainer.add(this.tooltipContent); - (this.scene as BattleScene).uiContainer.add(this.tooltipContainer); + globalScene.uiContainer.add(this.tooltipContainer); } getHandler(): H { @@ -268,12 +268,11 @@ export default class UI extends Phaser.GameObjects.Container { return false; } - const battleScene = this.scene as BattleScene; if ([ Mode.CONFIRM, Mode.COMMAND, Mode.FIGHT, Mode.MESSAGE ].includes(this.mode)) { - battleScene?.processInfoButton(pressed); + globalScene?.processInfoButton(pressed); return true; } - battleScene?.processInfoButton(false); + globalScene?.processInfoButton(false); return true; } @@ -318,11 +317,10 @@ export default class UI extends Phaser.GameObjects.Container { } showDialogue(keyOrText: string, name: string | undefined, delay: integer | null = 0, callback: Function, callbackDelay?: integer, promptDelay?: integer): void { - const battleScene = this.scene as BattleScene; // Get localized dialogue (if available) let hasi18n = false; let text = keyOrText; - const genderIndex = battleScene.gameData.gender ?? PlayerGender.UNSET; + const genderIndex = globalScene.gameData.gender ?? PlayerGender.UNSET; const genderStr = PlayerGender[genderIndex].toLowerCase(); if (i18next.exists(keyOrText) ) { @@ -339,7 +337,7 @@ export default class UI extends Phaser.GameObjects.Container { } } let showMessageAndCallback = () => { - hasi18n && battleScene.gameData.saveSeenDialogue(keyOrText); + hasi18n && globalScene.gameData.saveSeenDialogue(keyOrText); callback(); }; if (text.indexOf("$") > -1) { @@ -360,10 +358,8 @@ export default class UI extends Phaser.GameObjects.Container { } shouldSkipDialogue(i18nKey: string): boolean { - const battleScene = this.scene as BattleScene; - if (i18next.exists(i18nKey) ) { - if (battleScene.skipSeenDialogues && battleScene.gameData.getSeenDialogues()[i18nKey] === true) { + if (globalScene.skipSeenDialogues && globalScene.gameData.getSeenDialogues()[i18nKey] === true) { return true; } } @@ -378,9 +374,9 @@ export default class UI extends Phaser.GameObjects.Container { this.tooltipContainer.setVisible(true); this.editTooltip(title, content); if (overlap) { - (this.scene as BattleScene).uiContainer.moveAbove(this.tooltipContainer, this); + globalScene.uiContainer.moveAbove(this.tooltipContainer, this); } else { - (this.scene as BattleScene).uiContainer.moveBelow(this.tooltipContainer, this); + globalScene.uiContainer.moveBelow(this.tooltipContainer, this); } } @@ -401,9 +397,9 @@ export default class UI extends Phaser.GameObjects.Container { update(): void { if (this.tooltipContainer.visible) { - const isTouch = (this.scene as BattleScene).inputMethod === "touch"; - const pointerX = this.scene.game.input.activePointer.x; - const pointerY = this.scene.game.input.activePointer.y; + const isTouch = globalScene.inputMethod === "touch"; + const pointerX = globalScene.game.input.activePointer.x; + const pointerY = globalScene.game.input.activePointer.y; const tooltipWidth = this.tooltipBg.width; const tooltipHeight = this.tooltipBg.height; const padding = 2; @@ -414,16 +410,16 @@ export default class UI extends Phaser.GameObjects.Container { if (isTouch) { // If we are in the top left quadrant on mobile, move the tooltip to the top right corner - if (pointerX <= this.scene.game.canvas.width / 2 && pointerY <= this.scene.game.canvas.height / 2) { - x = this.scene.game.canvas.width / 6 - tooltipWidth - padding; + if (pointerX <= globalScene.game.canvas.width / 2 && pointerY <= globalScene.game.canvas.height / 2) { + x = globalScene.game.canvas.width / 6 - tooltipWidth - padding; } } else { // If the tooltip would go offscreen on the right, or is close to it, move to the left of the cursor - if (x + tooltipWidth + padding > this.scene.game.canvas.width / 6) { + if (x + tooltipWidth + padding > globalScene.game.canvas.width / 6) { x = Math.max(padding, pointerX / 6 - tooltipWidth - padding); } // If the tooltip would go offscreen at the bottom, or is close to it, move above the cursor - if (y + tooltipHeight + padding > this.scene.game.canvas.height / 6) { + if (y + tooltipHeight + padding > globalScene.game.canvas.height / 6) { y = Math.max(padding, pointerY / 6 - tooltipHeight - padding); } } @@ -451,11 +447,11 @@ export default class UI extends Phaser.GameObjects.Container { } playSelect(): void { - (this.scene as BattleScene).playSound("ui/select"); + globalScene.playSound("ui/select"); } playError(): void { - (this.scene as BattleScene).playSound("ui/error"); + globalScene.playSound("ui/error"); } fadeOut(duration: integer): Promise { @@ -466,7 +462,7 @@ export default class UI extends Phaser.GameObjects.Container { this.overlayActive = true; this.overlay.setAlpha(0); this.overlay.setVisible(true); - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.overlay, alpha: 1, duration: duration, @@ -481,7 +477,7 @@ export default class UI extends Phaser.GameObjects.Container { if (!this.overlayActive) { return resolve(); } - this.scene.tweens.add({ + globalScene.tweens.add({ targets: this.overlay, alpha: 0, duration: duration, @@ -508,7 +504,7 @@ export default class UI extends Phaser.GameObjects.Container { } if (chainMode && this.mode && !clear) { this.modeChain.push(this.mode); - (this.scene as BattleScene).updateGameInfo(); + globalScene.updateGameInfo(); } this.mode = mode; const touchControls = document?.getElementById("touchControls"); @@ -523,7 +519,7 @@ export default class UI extends Phaser.GameObjects.Container { && (noTransitionModes.indexOf(this.mode) === -1 && noTransitionModes.indexOf(mode) === -1))) || (chainMode && noTransitionModes.indexOf(mode) === -1))) { this.fadeOut(250).then(() => { - this.scene.time.delayedCall(100, () => { + globalScene.time.delayedCall(100, () => { doSetMode(); this.fadeIn(250); }); @@ -556,7 +552,7 @@ export default class UI extends Phaser.GameObjects.Container { resetModeChain(): void { this.modeChain = []; - (this.scene as BattleScene).updateGameInfo(); + globalScene.updateGameInfo(); } revertMode(): Promise { @@ -570,7 +566,7 @@ export default class UI extends Phaser.GameObjects.Container { const doRevertMode = () => { this.getHandler().clear(); this.mode = this.modeChain.pop()!; // TODO: is this bang correct? - (this.scene as BattleScene).updateGameInfo(); + globalScene.updateGameInfo(); const touchControls = document.getElementById("touchControls"); if (touchControls) { touchControls.dataset.uiMode = Mode[this.mode]; @@ -580,7 +576,7 @@ export default class UI extends Phaser.GameObjects.Container { if (noTransitionModes.indexOf(lastMode) === -1) { this.fadeOut(250).then(() => { - this.scene.time.delayedCall(100, () => { + globalScene.time.delayedCall(100, () => { doRevertMode(); this.fadeIn(250); }); @@ -612,11 +608,10 @@ export default class UI extends Phaser.GameObjects.Container { * @returns gamepad type */ public getGamepadType(): string { - const scene = this.scene as BattleScene; - if (scene.inputMethod === "gamepad") { - return scene.inputController.getConfig(scene.inputController.selectedDevice[Device.GAMEPAD]).padType; + if (globalScene.inputMethod === "gamepad") { + return globalScene.inputController.getConfig(globalScene.inputController.selectedDevice[Device.GAMEPAD]).padType; } else { - return scene.inputMethod; + return globalScene.inputMethod; } } } diff --git a/src/ui/unavailable-modal-ui-handler.ts b/src/ui/unavailable-modal-ui-handler.ts index 92b1c2f1b4e..36f1a191b77 100644 --- a/src/ui/unavailable-modal-ui-handler.ts +++ b/src/ui/unavailable-modal-ui-handler.ts @@ -1,10 +1,11 @@ -import BattleScene from "../battle-scene"; -import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; +import { ModalUiHandler } from "./modal-ui-handler"; import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; +import type { Mode } from "./ui"; import { updateUserInfo } from "#app/account"; import * as Utils from "#app/utils"; import i18next from "i18next"; +import { globalScene } from "#app/global-scene"; export default class UnavailableModalUiHandler extends ModalUiHandler { private reconnectTimer: NodeJS.Timeout | null; @@ -16,8 +17,8 @@ export default class UnavailableModalUiHandler extends ModalUiHandler { private readonly randVarianceTime = 1000 * 10; - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); + constructor(mode: Mode | null = null) { + super(mode); this.reconnectDuration = this.minTime; } @@ -44,7 +45,7 @@ export default class UnavailableModalUiHandler extends ModalUiHandler { setup(): void { super.setup(); - const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, i18next.t("menu:errorServerDown"), TextStyle.WINDOW, { fontSize: "48px", align: "center" }); + const label = addTextObject(this.getWidth() / 2, this.getHeight() / 2, i18next.t("menu:errorServerDown"), TextStyle.WINDOW, { fontSize: "48px", align: "center" }); label.setOrigin(0.5, 0.5); this.modalContainer.add(label); @@ -55,11 +56,11 @@ export default class UnavailableModalUiHandler extends ModalUiHandler { if (response[0] || [ 200, 400 ].includes(response[1])) { this.reconnectTimer = null; this.reconnectDuration = this.minTime; - this.scene.playSound("se/pb_bounce_1"); + globalScene.playSound("se/pb_bounce_1"); this.reconnectCallback(); } else if (response[1] === 401) { Utils.removeCookie(Utils.sessionIdKey); - this.scene.reset(true, true); + globalScene.reset(true, true); } else { this.reconnectDuration = Math.min(this.reconnectDuration * 2, this.maxTime); // Set a max delay so it isn't infinite this.reconnectTimer = From 3a3ebfb783bfe057f2c7d9d96cc030a3c83bcec5 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:39:06 -0800 Subject: [PATCH 10/54] [Balance] Multi-hit moves now use gen 5+ behavior (#5117) --- src/data/move.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index b83b2d06394..54b10a4ab80 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2236,14 +2236,14 @@ export class MultiHitAttr extends MoveAttr { switch (this.multiHitType) { case MultiHitType._2_TO_5: { - const rand = user.randSeedInt(16); - const hitValue = new Utils.IntegerHolder(rand); + const rand = user.randSeedInt(20); + const hitValue = new Utils.NumberHolder(rand); applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); - if (hitValue.value >= 10) { + if (hitValue.value >= 13) { return 2; - } else if (hitValue.value >= 4) { + } else if (hitValue.value >= 6) { return 3; - } else if (hitValue.value >= 2) { + } else if (hitValue.value >= 3) { return 4; } else { return 5; From f1e500400ac367ed1415daee61340e0c3d4424aa Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:45:11 +0100 Subject: [PATCH 11/54] [Balance][Bug] Fix off by one error in Generate random biome and Daily Mode now has its own function (#5121) * [Balance] Allow Island and Laboratory in Generate Random Biome * [Bug] Fix off by one error in Generate Random Biome * [Balance] Daily Mode now has its own Generate Random Starting Biome * [Misc] Filtering out Town and End specifically instead of assuming enum value stays consistent forever --------- Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> --- src/battle-scene.ts | 8 ++--- src/data/daily-run.ts | 74 +++++++++++++++++++++++++++++++++++++++++++ src/game-mode.ts | 3 +- 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6cc33dc476d..6db9311bac8 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1865,7 +1865,7 @@ export default class BattleScene extends SceneBase { generateRandomBiome(waveIndex: integer): Biome { const relWave = waveIndex % 250; - const biomes = Utils.getEnumValues(Biome).slice(1, Utils.getEnumValues(Biome).filter(b => b >= 40).length * -1); + const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); const maxDepth = biomeDepths[Biome.END][0] - 2; const depthWeights = new Array(maxDepth + 1).fill(null) .map((_, i: integer) => ((1 - Math.min(Math.abs((i / (maxDepth - 1)) - (relWave / 250)) + 0.25, 1)) / 0.75) * 250); @@ -1878,9 +1878,9 @@ export default class BattleScene extends SceneBase { const randInt = Utils.randSeedInt(totalWeight); - for (const biome of biomes) { - if (randInt < biomeThresholds[biome]) { - return biome; + for (let i = 0; i < biomes.length; i++) { + if (randInt < biomeThresholds[i]) { + return biomes[i]; } } diff --git a/src/data/daily-run.ts b/src/data/daily-run.ts index b0ce38cebd2..2a4a78a9caf 100644 --- a/src/data/daily-run.ts +++ b/src/data/daily-run.ts @@ -8,6 +8,7 @@ import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import { Biome } from "#app/enums/biome"; export interface DailyRunConfig { seed: integer; @@ -71,3 +72,76 @@ function getDailyRunStarter(starterSpeciesForm: PokemonSpeciesForm, startingLeve pokemon.destroy(); return starter; } + +interface BiomeWeights { + [key: integer]: integer +} + +// Initially weighted by amount of exits each biome has +// Town and End are set to 0 however +// And some other biomes were balanced +1/-1 based on average size of the total daily. +const dailyBiomeWeights: BiomeWeights = { + [Biome.CAVE]: 3, + [Biome.LAKE]: 3, + [Biome.PLAINS]: 3, + [Biome.SNOWY_FOREST]: 3, + [Biome.SWAMP]: 3, // 2 -> 3 + [Biome.TALL_GRASS]: 3, // 2 -> 3 + + [Biome.ABYSS]: 2, // 3 -> 2 + [Biome.RUINS]: 2, + [Biome.BADLANDS]: 2, + [Biome.BEACH]: 2, + [Biome.CONSTRUCTION_SITE]: 2, + [Biome.DESERT]: 2, + [Biome.DOJO]: 2, // 3 -> 2 + [Biome.FACTORY]: 2, + [Biome.FAIRY_CAVE]: 2, + [Biome.FOREST]: 2, + [Biome.GRASS]: 2, // 1 -> 2 + [Biome.MEADOW]: 2, + [Biome.MOUNTAIN]: 2, // 3 -> 2 + [Biome.SEA]: 2, + [Biome.SEABED]: 2, + [Biome.SLUM]: 2, + [Biome.TEMPLE]: 2, // 3 -> 2 + [Biome.VOLCANO]: 2, + + [Biome.GRAVEYARD]: 1, + [Biome.ICE_CAVE]: 1, + [Biome.ISLAND]: 1, + [Biome.JUNGLE]: 1, + [Biome.LABORATORY]: 1, + [Biome.METROPOLIS]: 1, + [Biome.POWER_PLANT]: 1, + [Biome.SPACE]: 1, + [Biome.WASTELAND]: 1, + + [Biome.TOWN]: 0, + [Biome.END]: 0, +}; + +export function getDailyStartingBiome(): Biome { + const biomes = Utils.getEnumValues(Biome).filter(b => b !== Biome.TOWN && b !== Biome.END); + + let totalWeight = 0; + const biomeThresholds: integer[] = []; + for (const biome of biomes) { + // Keep track of the total weight + totalWeight += dailyBiomeWeights[biome]; + + // Keep track of each biomes cumulative weight + biomeThresholds.push(totalWeight); + } + + const randInt = Utils.randSeedInt(totalWeight); + + for (let i = 0; i < biomes.length; i++) { + if (randInt < biomeThresholds[i]) { + return biomes[i]; + } + } + + // Fallback in case something went wrong + return biomes[Utils.randSeedInt(biomes.length)]; +} diff --git a/src/game-mode.ts b/src/game-mode.ts index 4e0f5715851..78a65a54890 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -12,6 +12,7 @@ import { Biome } from "#enums/biome"; import { Species } from "#enums/species"; import { Challenges } from "./enums/challenges"; import { globalScene } from "#app/global-scene"; +import { getDailyStartingBiome } from "./data/daily-run"; export enum GameModes { CLASSIC, @@ -120,7 +121,7 @@ export class GameMode implements GameModeConfig { getStartingBiome(): Biome { switch (this.modeId) { case GameModes.DAILY: - return globalScene.generateRandomBiome(this.getWaveForDifficulty(1)); + return getDailyStartingBiome(); default: return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } From 001b61c1c713d4d481904d0cb7af0490f0819824 Mon Sep 17 00:00:00 2001 From: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:26:35 -0500 Subject: [PATCH 12/54] [Bug][Move] Refactor moves that call a random move (#3380) * Combine moveset from allies and uses it to get a move * Clearer implementation of combining user and teammates' moves * Refactor assist and sleep talk to use metronome's attribute for calling a move * Refactor move filtering in RandomMovesetMoveAttr, creates arrays with invalid moves for assist/sleep talk * Refactor RandomMoveAttr to set moveId in condition, places reused code in callMove in RandomMoveAttr * Correct invalid move lists, adds Max/Z moves to metronome's list * Remove ignoresVirtual from beta merge * Remove Max/Z moves per frutescens' comment * Fix bug with metronome/copycat/assist/sleep talk targeting ally * Experimental async/await to be tested * Refactor other attributes to extend CallMoveAttr * Replace QueuedMove with TurnMove, refactor to attempt two-turn move fix for metronome * Fix Swallow test due to TurnMove refactor * Further fixes for TurnMove refactor * Fix metronome two turn moves for enemy pokemon * Replace nested ternary with if-else block per DayKev's comment * Minor fixes * Adjust command phase args handling * Create metronome test, refactor RandomMoveAttr for easier testing * Add unit test for recharge moves * Refactor Copycat and Mirror Move, adjust move targeting * Add unit test for ally targeting with Aromatic Mist * Add tests for secondary effects and recharge moves for metronome * Add test for Roar, remove test for Acupressure * Create test for Assist * Add test for assist failing * Add sleep talk unit test coverage * Adjust move-phase to better track last move for copycat, write and update unit tests for assist/copycat * Create moveHistory in Battle to track all moves used, adjust mirror move to use this, writes unit tests * Correct mirror move implementation, rewrite unit test to adjust * Add docs to attrs, update assist to only grab allies sets * Update assist unit test to match expected functionality * Update metronome unit test to use getMoveOverride * Update copycat unit test to use metronome getMoveOverride mock * Fix phase interception * Add docs from missed conversations * Update assist tests to use manual moveset overrides Minor fixes to other tests * Remove `export` from `CallMoveAttr` * Add missing `.unimplemented()` to some Max- and Z-Moves --------- Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle.ts | 14 +- src/data/battler-tags.ts | 2 +- src/data/move.ts | 864 ++++++++++++++++++----------- src/field/pokemon.ts | 35 +- src/phases/command-phase.ts | 28 +- src/phases/move-phase.ts | 15 +- src/test/moves/assist.test.ts | 105 ++++ src/test/moves/copycat.test.ts | 91 +++ src/test/moves/metronome.test.ts | 113 ++++ src/test/moves/mirror_move.test.ts | 84 +++ src/test/moves/sleep_talk.test.ts | 75 +++ src/test/moves/spit_up.test.ts | 6 +- src/test/moves/stockpile.test.ts | 2 +- src/test/moves/swallow.test.ts | 6 +- 14 files changed, 1069 insertions(+), 371 deletions(-) create mode 100644 src/test/moves/assist.test.ts create mode 100644 src/test/moves/copycat.test.ts create mode 100644 src/test/moves/metronome.test.ts create mode 100644 src/test/moves/mirror_move.test.ts create mode 100644 src/test/moves/sleep_talk.test.ts diff --git a/src/battle.ts b/src/battle.ts index 6dae845bfe1..b1196bb0139 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -7,7 +7,7 @@ import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/mod import type { PokeballType } from "#enums/pokeball"; import { trainerConfigs } from "#app/data/trainer-config"; import { SpeciesFormKey } from "#enums/species-form-key"; -import type { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon"; +import type { EnemyPokemon, PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattleSpec } from "#enums/battle-spec"; @@ -45,12 +45,12 @@ export enum BattlerIndex { } export interface TurnCommand { - command: Command; - cursor?: number; - move?: QueuedMove; - targets?: BattlerIndex[]; - skip?: boolean; - args?: any[]; + command: Command; + cursor?: number; + move?: TurnMove; + targets?: BattlerIndex[]; + skip?: boolean; + args?: any[]; } export interface FaintLogEntry { diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 2743c36e7b5..3a58ff4a99d 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -612,7 +612,7 @@ export class InterruptedTag extends BattlerTag { super.onAdd(pokemon); pokemon.getMoveQueue().shift(); - pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER }); + pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.OTHER, targets: []}); } lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { diff --git a/src/data/move.ts b/src/data/move.ts index 54b10a4ab80..f3a1f3aa119 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -87,7 +87,6 @@ export enum MoveFlags { NONE = 0, MAKES_CONTACT = 1 << 0, IGNORE_PROTECT = 1 << 1, - IGNORE_VIRTUAL = 1 << 2, /** * Sound-based moves have the following effects: * - Pokemon with the {@linkcode Abilities.SOUNDPROOF Soundproof Ability} are unaffected by other Pokemon's sound-based moves. @@ -98,35 +97,35 @@ export enum MoveFlags { * * cf https://bulbapedia.bulbagarden.net/wiki/Sound-based_move */ - SOUND_BASED = 1 << 3, - HIDE_USER = 1 << 4, - HIDE_TARGET = 1 << 5, - BITING_MOVE = 1 << 6, - PULSE_MOVE = 1 << 7, - PUNCHING_MOVE = 1 << 8, - SLICING_MOVE = 1 << 9, + SOUND_BASED = 1 << 2, + HIDE_USER = 1 << 3, + HIDE_TARGET = 1 << 4, + BITING_MOVE = 1 << 5, + PULSE_MOVE = 1 << 6, + PUNCHING_MOVE = 1 << 7, + SLICING_MOVE = 1 << 8, /** * Indicates a move should be affected by {@linkcode Abilities.RECKLESS} * @see {@linkcode Move.recklessMove()} */ - RECKLESS_MOVE = 1 << 10, + RECKLESS_MOVE = 1 << 9, /** Indicates a move should be affected by {@linkcode Abilities.BULLETPROOF} */ - BALLBOMB_MOVE = 1 << 11, + BALLBOMB_MOVE = 1 << 10, /** Grass types and pokemon with {@linkcode Abilities.OVERCOAT} are immune to powder moves */ - POWDER_MOVE = 1 << 12, + POWDER_MOVE = 1 << 11, /** Indicates a move should trigger {@linkcode Abilities.DANCER} */ - DANCE_MOVE = 1 << 13, + DANCE_MOVE = 1 << 12, /** Indicates a move should trigger {@linkcode Abilities.WIND_RIDER} */ - WIND_MOVE = 1 << 14, + WIND_MOVE = 1 << 13, /** Indicates a move should trigger {@linkcode Abilities.TRIAGE} */ - TRIAGE_MOVE = 1 << 15, - IGNORE_ABILITIES = 1 << 16, + TRIAGE_MOVE = 1 << 14, + IGNORE_ABILITIES = 1 << 15, /** Enables all hits of a multi-hit move to be accuracy checked individually */ - CHECK_ALL_HITS = 1 << 17, + CHECK_ALL_HITS = 1 << 16, /** Indicates a move is able to bypass its target's Substitute (if the target has one) */ - IGNORE_SUBSTITUTE = 1 << 18, + IGNORE_SUBSTITUTE = 1 << 17, /** Indicates a move is able to be redirected to allies in a double battle if the attacker faints */ - REDIRECT_COUNTER = 1 << 19, + REDIRECT_COUNTER = 1 << 18, } type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -441,16 +440,6 @@ export default class Move implements Localizable { return this; } - /** - * Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move - * @see {@linkcode Moves.NATURE_POWER} - * @returns The {@linkcode Move} that called this function - */ - ignoresVirtual(): this { - this.setFlag(MoveFlags.IGNORE_VIRTUAL, true); - return this; - } - /** * Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move * @see {@linkcode Moves.UPROAR} @@ -1552,7 +1541,7 @@ export class RecoilAttr extends MoveEffectAttr { } // Chloroblast and Struggle should not deal recoil damage if the move was not successful - if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result)) { + if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result ?? MoveResult.FAIL)) { return false; } @@ -6483,52 +6472,46 @@ export class FirstMoveTypeAttr extends MoveEffectAttr { } } -export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { - private enemyMoveset: boolean | null; - - constructor(enemyMoveset?: boolean) { - super(); - - this.enemyMoveset = enemyMoveset!; // TODO: is this bang correct? - } - - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const moveset = (!this.enemyMoveset ? user : target).getMoveset(); - const moves = moveset.filter(m => !m?.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL)); - if (moves.length) { - const move = moves[user.randSeedInt(moves.length)]; - const moveIndex = moveset.findIndex(m => m?.moveId === move?.moveId); - const moveTargets = getMoveTargets(user, move?.moveId!); // TODO: is this bang correct? - if (!moveTargets.targets.length) { - return false; - } - let selectTargets: BattlerIndex[]; - switch (true) { - case (moveTargets.multiple || moveTargets.targets.length === 1): { - selectTargets = moveTargets.targets; - break; - } - case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { - selectTargets = [ target.getBattlerIndex() ]; - break; - } - default: { - moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - break; - } - } - const targets = selectTargets; - user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? - globalScene.unshiftPhase(new MovePhase(user, targets, moveset[moveIndex]!, true)); // There's a PR to re-do the move(s) that use this Attr, gonna put `!` for now - return true; +/** + * Attribute used to call a move. + * Used by other move attributes: {@linkcode RandomMoveAttr}, {@linkcode RandomMovesetMoveAttr}, {@linkcode CopyMoveAttr} + * @see {@linkcode apply} for move call + * @extends OverrideMoveEffectAttr + */ +class CallMoveAttr extends OverrideMoveEffectAttr { + protected invalidMoves: Moves[]; + protected hasTarget: boolean; + async apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + const replaceMoveTarget = move.moveTarget === MoveTarget.NEAR_OTHER ? MoveTarget.NEAR_ENEMY : undefined; + const moveTargets = getMoveTargets(user, move.id, replaceMoveTarget); + if (moveTargets.targets.length === 0) { + return false; } + const targets = moveTargets.multiple || moveTargets.targets.length === 1 + ? moveTargets.targets + : [ this.hasTarget ? target.getBattlerIndex() : moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; // account for Mirror Move having a target already + user.getMoveQueue().push({ move: move.id, targets: targets, virtual: true, ignorePP: true }); + globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(move.id, 0, 0, true), true, true)); - return false; + await Promise.resolve(initMoveAnim(move.id).then(() => { + loadMoveAnimAssets([ move.id ], true); + })); + return true; } } -export class RandomMoveAttr extends OverrideMoveEffectAttr { +/** + * Attribute used to call a random move. + * Used for {@linkcode Moves.METRONOME} + * @see {@linkcode apply} for move selection and move call + * @extends CallMoveAttr to call a selected move + */ +export class RandomMoveAttr extends CallMoveAttr { + constructor(invalidMoves: Moves[]) { + super(); + this.invalidMoves = invalidMoves; + } + /** * This function exists solely to allow tests to override the randomly selected move by mocking this function. */ @@ -6536,31 +6519,353 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { return null; } + /** + * User calls a random moveId. + * + * Invalid moves are indicated by what is passed in to invalidMoves: {@linkcode invalidMetronomeMoves} + * @param user Pokemon that used the move and will call a random move + * @param target Pokemon that will be targeted by the random move (if single target) + * @param move Move being used + * @param args Unused + */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { - return new Promise(resolve => { - const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL) && !allMoves[m].name.endsWith(" (N)")); - const moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; - - const moveTargets = getMoveTargets(user, moveId); - if (!moveTargets.targets.length) { - resolve(false); - return; - } - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 - ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); - globalScene.unshiftPhase(new MovePhase(user, targets, new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(moveId).then(() => { - loadMoveAnimAssets([ moveId ], true) - .then(() => resolve(true)); - }); - }); + const moveIds = Utils.getEnumValues(Moves).map(m => !this.invalidMoves.includes(m) && !allMoves[m].name.endsWith(" (N)") ? m : Moves.NONE); + let moveId: Moves = Moves.NONE; + do { + moveId = this.getMoveOverride() ?? moveIds[user.randSeedInt(moveIds.length)]; + } + while (moveId === Moves.NONE); + return super.apply(user, target, allMoves[moveId], args); } } +/** + * Attribute used to call a random move in the user or party's moveset. + * Used for {@linkcode Moves.ASSIST} and {@linkcode Moves.SLEEP_TALK} + * + * Fails if the user has no callable moves. + * + * Invalid moves are indicated by what is passed in to invalidMoves: {@linkcode invalidAssistMoves} or {@linkcode invalidSleepTalkMoves} + * @extends RandomMoveAttr to use the callMove function on a moveId + * @see {@linkcode getCondition} for move selection + */ +export class RandomMovesetMoveAttr extends CallMoveAttr { + private includeParty: boolean; + private moveId: number; + constructor(invalidMoves: Moves[], includeParty: boolean = false) { + super(); + this.includeParty = includeParty; + this.invalidMoves = invalidMoves; + } + + /** + * User calls a random moveId selected in {@linkcode getCondition} + * @param user Pokemon that used the move and will call a random move + * @param target Pokemon that will be targeted by the random move (if single target) + * @param move Move being used + * @param args Unused + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + return super.apply(user, target, allMoves[this.moveId], args); + } + + getCondition(): MoveConditionFunc { + return (user, target, move) => { + // includeParty will be true for Assist, false for Sleep Talk + let allies: Pokemon[]; + if (this.includeParty) { + allies = user.isPlayer() ? globalScene.getPlayerParty().filter(p => p !== user) : globalScene.getEnemyParty().filter(p => p !== user); + } else { + allies = [ user ]; + } + const partyMoveset = allies.map(p => p.moveset).flat(); + const moves = partyMoveset.filter(m => !this.invalidMoves.includes(m!.moveId) && !m!.getMove().name.endsWith(" (N)")); + if (moves.length === 0) { + return false; + } + + this.moveId = moves[user.randSeedInt(moves.length)]!.moveId; + return true; + }; + } +} + +const invalidMetronomeMoves: Moves[] = [ + Moves.AFTER_YOU, + Moves.APPLE_ACID, + Moves.ARMOR_CANNON, + Moves.ASSIST, + Moves.ASTRAL_BARRAGE, + Moves.AURA_WHEEL, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BEHEMOTH_BASH, + Moves.BEHEMOTH_BLADE, + Moves.BELCH, + Moves.BESTOW, + Moves.BLAZING_TORQUE, + Moves.BODY_PRESS, + Moves.BRANCH_POKE, + Moves.BREAKING_SWIPE, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CHILLING_WATER, + Moves.CHILLY_RECEPTION, + Moves.CLANGOROUS_SOUL, + Moves.COLLISION_COURSE, + Moves.COMBAT_TORQUE, + Moves.COMEUPPANCE, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.CRAFTY_SHIELD, + Moves.DECORATE, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DIAMOND_STORM, + Moves.DOODLE, + Moves.DOUBLE_IRON_BASH, + Moves.DOUBLE_SHOCK, + Moves.DRAGON_ASCENT, + Moves.DRAGON_ENERGY, + Moves.DRUM_BEATING, + Moves.DYNAMAX_CANNON, + Moves.ELECTRO_DRIFT, + Moves.ENDURE, + Moves.ETERNABEAM, + Moves.FALSE_SURRENDER, + Moves.FEINT, + Moves.FIERY_WRATH, + Moves.FILLET_AWAY, + Moves.FLEUR_CANNON, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.FREEZE_SHOCK, + Moves.FREEZING_GLARE, + Moves.GLACIAL_LANCE, + Moves.GRAV_APPLE, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.HYPER_DRILL, + Moves.HYPERSPACE_FURY, + Moves.HYPERSPACE_HOLE, + Moves.ICE_BURN, + Moves.INSTRUCT, + Moves.JET_PUNCH, + Moves.JUNGLE_HEALING, + Moves.KINGS_SHIELD, + Moves.LIFE_DEW, + Moves.LIGHT_OF_RUIN, + Moves.MAKE_IT_RAIN, + Moves.MAGICAL_TORQUE, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METEOR_ASSAULT, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIND_BLOWN, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.MOONGEIST_BEAM, + Moves.NATURE_POWER, + Moves.NATURES_MADNESS, + Moves.NOXIOUS_TORQUE, + Moves.OBSTRUCT, + Moves.ORDER_UP, + Moves.ORIGIN_PULSE, + Moves.OVERDRIVE, + Moves.PHOTON_GEYSER, + Moves.PLASMA_FISTS, + Moves.POPULATION_BOMB, + Moves.POUNCE, + Moves.POWER_SHIFT, + Moves.PRECIPICE_BLADES, + Moves.PROTECT, + Moves.PYRO_BALL, + Moves.QUASH, + Moves.QUICK_GUARD, + Moves.RAGE_FIST, + Moves.RAGE_POWDER, + Moves.RAGING_BULL, + Moves.RAGING_FURY, + Moves.RELIC_SONG, + Moves.REVIVAL_BLESSING, + Moves.RUINATION, + Moves.SALT_CURE, + Moves.SECRET_SWORD, + Moves.SHED_TAIL, + Moves.SHELL_TRAP, + Moves.SILK_TRAP, + Moves.SKETCH, + Moves.SLEEP_TALK, + Moves.SNAP_TRAP, + Moves.SNARL, + Moves.SNATCH, + Moves.SNORE, + Moves.SNOWSCAPE, + Moves.SPECTRAL_THIEF, + Moves.SPICY_EXTRACT, + Moves.SPIKY_SHIELD, + Moves.SPIRIT_BREAK, + Moves.SPOTLIGHT, + Moves.STEAM_ERUPTION, + Moves.STEEL_BEAM, + Moves.STRANGE_STEAM, + Moves.STRUGGLE, + Moves.SUNSTEEL_STRIKE, + Moves.SURGING_STRIKES, + Moves.SWITCHEROO, + Moves.TECHNO_BLAST, + Moves.TERA_STARSTORM, + Moves.THIEF, + Moves.THOUSAND_ARROWS, + Moves.THOUSAND_WAVES, + Moves.THUNDER_CAGE, + Moves.THUNDEROUS_KICK, + Moves.TIDY_UP, + Moves.TRAILBLAZE, + Moves.TRANSFORM, + Moves.TRICK, + Moves.TWIN_BEAM, + Moves.V_CREATE, + Moves.WICKED_BLOW, + Moves.WICKED_TORQUE, + Moves.WIDE_GUARD, +]; + +const invalidAssistMoves: Moves[] = [ + Moves.ASSIST, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BELCH, + Moves.BESTOW, + Moves.BOUNCE, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CIRCLE_THROW, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DIG, + Moves.DIVE, + Moves.DRAGON_TAIL, + Moves.ENDURE, + Moves.FEINT, + Moves.FLY, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.KINGS_SHIELD, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.NATURE_POWER, + Moves.PHANTOM_FORCE, + Moves.PROTECT, + Moves.RAGE_POWDER, + Moves.ROAR, + Moves.SHADOW_FORCE, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SKY_DROP, + Moves.SLEEP_TALK, + Moves.SNATCH, + Moves.SPIKY_SHIELD, + Moves.SPOTLIGHT, + Moves.STRUGGLE, + Moves.SWITCHEROO, + Moves.THIEF, + Moves.TRANSFORM, + Moves.TRICK, + Moves.WHIRLWIND, +]; + +const invalidSleepTalkMoves: Moves[] = [ + Moves.ASSIST, + Moves.BELCH, + Moves.BEAK_BLAST, + Moves.BIDE, + Moves.BOUNCE, + Moves.COPYCAT, + Moves.DIG, + Moves.DIVE, + Moves.DYNAMAX_CANNON, + Moves.FREEZE_SHOCK, + Moves.FLY, + Moves.FOCUS_PUNCH, + Moves.GEOMANCY, + Moves.ICE_BURN, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIRROR_MOVE, + Moves.MIMIC, + Moves.PHANTOM_FORCE, + Moves.RAZOR_WIND, + Moves.SHADOW_FORCE, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SKULL_BASH, + Moves.SKY_ATTACK, + Moves.SKY_DROP, + Moves.SLEEP_TALK, + Moves.SOLAR_BLADE, + Moves.SOLAR_BEAM, + Moves.STRUGGLE, + Moves.UPROAR, +]; + +const invalidCopycatMoves = [ + Moves.ASSIST, + Moves.BANEFUL_BUNKER, + Moves.BEAK_BLAST, + Moves.BEHEMOTH_BASH, + Moves.BEHEMOTH_BLADE, + Moves.BESTOW, + Moves.CELEBRATE, + Moves.CHATTER, + Moves.CIRCLE_THROW, + Moves.COPYCAT, + Moves.COUNTER, + Moves.COVET, + Moves.DESTINY_BOND, + Moves.DETECT, + Moves.DRAGON_TAIL, + Moves.ENDURE, + Moves.FEINT, + Moves.FOCUS_PUNCH, + Moves.FOLLOW_ME, + Moves.HELPING_HAND, + Moves.HOLD_HANDS, + Moves.KINGS_SHIELD, + Moves.MAT_BLOCK, + Moves.ME_FIRST, + Moves.METRONOME, + Moves.MIMIC, + Moves.MIRROR_COAT, + Moves.MIRROR_MOVE, + Moves.PROTECT, + Moves.RAGE_POWDER, + Moves.ROAR, + Moves.SHELL_TRAP, + Moves.SKETCH, + Moves.SLEEP_TALK, + Moves.SNATCH, + Moves.SPIKY_SHIELD, + Moves.SPOTLIGHT, + Moves.STRUGGLE, + Moves.SWITCHEROO, + Moves.THIEF, + Moves.TRANSFORM, + Moves.TRICK, + Moves.WHIRLWIND, +]; + export class NaturePowerAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { @@ -6704,45 +7009,35 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { } } -const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => { - const copiableMove = globalScene.currentBattle.lastMove; - - if (!copiableMove) { - return false; +/** + * Attribute used to copy a previously-used move. + * Used for {@linkcode Moves.COPYCAT} and {@linkcode Moves.MIRROR_MOVE} + * @see {@linkcode apply} for move selection and move call + * @extends CallMoveAttr to call a selected move + */ +export class CopyMoveAttr extends CallMoveAttr { + private mirrorMove: boolean; + constructor(mirrorMove: boolean, invalidMoves: Moves[] = []) { + super(); + this.mirrorMove = mirrorMove; + this.invalidMoves = invalidMoves; } - if (allMoves[copiableMove].isChargingMove()) { - return false; - } - - // TODO: Add last turn of Bide - - return true; -}; - -export class CopyMoveAttr extends OverrideMoveEffectAttr { - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const lastMove = globalScene.currentBattle.lastMove; - - const moveTargets = getMoveTargets(user, lastMove); - if (!moveTargets.targets.length) { - return false; - } - - const targets = moveTargets.multiple || moveTargets.targets.length === 1 - ? moveTargets.targets - : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1 - ? [ target.getBattlerIndex() ] - : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true }); - - globalScene.unshiftPhase(new MovePhase(user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true)); - - return true; + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + this.hasTarget = this.mirrorMove; + const lastMove = this.mirrorMove ? target.getLastXMoves()[0].move : globalScene.currentBattle.lastMove; + return super.apply(user, target, allMoves[lastMove], args); } getCondition(): MoveConditionFunc { - return lastMoveCopiableCondition; + return (user, target, move) => { + if (this.mirrorMove) { + return target.getMoveHistory().length !== 0; + } else { + const lastMove = globalScene.currentBattle.lastMove; + return lastMove !== undefined && !this.invalidMoves.includes(lastMove); + } + }; } } @@ -7896,11 +8191,20 @@ export type MoveTargetSet = { multiple: boolean; }; -export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { +export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet { const variableTarget = new Utils.NumberHolder(0); user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); - const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : []; + let moveTarget: MoveTarget | undefined; + if (allMoves[move].hasAttr(VariableTargetAttr)) { + moveTarget = variableTarget.value; + } else if (replaceTarget !== undefined) { + moveTarget = replaceTarget; + } else if (move) { + moveTarget = allMoves[move].moveTarget; + } else if (move === undefined) { + moveTarget = MoveTarget.NEAR_ENEMY; + } const opponents = user.getOpponents(); let set: Pokemon[] = []; @@ -7992,7 +8296,6 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:whippedUpAWhirlwind", { pokemonName: "{USER}" })) .attr(HighCritAttr) .windMove() - .ignoresVirtual() .target(MoveTarget.ALL_NEAR_ENEMIES), new SelfStatusMove(Moves.SWORDS_DANCE, Type.NORMAL, -1, 20, -1, 0, 1) .attr(StatStageChangeAttr, [ Stat.ATK ], 2, true) @@ -8011,8 +8314,7 @@ export function initMoves() { new ChargingAttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) .chargeText(i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) - .condition(failOnGravityCondition) - .ignoresVirtual(), + .condition(failOnGravityCondition), new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1) .attr(TrapAttr, BattlerTagType.BIND), new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1), @@ -8161,8 +8463,7 @@ export function initMoves() { new ChargingAttackMove(Moves.SOLAR_BEAM, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:tookInSunlight", { pokemonName: "{USER}" })) .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]) - .attr(AntiSunlightPowerDecreaseAttr) - .ignoresVirtual(), + .attr(AntiSunlightPowerDecreaseAttr), new StatusMove(Moves.POISON_POWDER, Type.POISON, 75, 35, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.POISON) .powderMove(), @@ -8211,8 +8512,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:dugAHole", { pokemonName: "{USER}" })) - .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERGROUND) - .ignoresVirtual(), + .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERGROUND), new StatusMove(Moves.TOXIC, Type.POISON, 90, 10, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.TOXIC) .attr(ToxicAccuracyAttr), @@ -8236,8 +8536,7 @@ export function initMoves() { .attr(LevelDamageAttr), new StatusMove(Moves.MIMIC, Type.NORMAL, -1, 10, -1, 0, 1) .attr(MovesetCopyMoveAttr) - .ignoresSubstitute() - .ignoresVirtual(), + .ignoresSubstitute(), new StatusMove(Moves.SCREECH, Type.NORMAL, 85, 40, -1, 0, 1) .attr(StatStageChangeAttr, [ Stat.DEF ], -2) .soundBased(), @@ -8273,15 +8572,12 @@ export function initMoves() { new SelfStatusMove(Moves.FOCUS_ENERGY, Type.NORMAL, -1, 30, -1, 0, 1) .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true), new AttackMove(Moves.BIDE, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1) - .ignoresVirtual() .target(MoveTarget.USER) .unimplemented(), new SelfStatusMove(Moves.METRONOME, Type.NORMAL, -1, 10, -1, 0, 1) - .attr(RandomMoveAttr) - .ignoresVirtual(), + .attr(RandomMoveAttr, invalidMetronomeMoves), new StatusMove(Moves.MIRROR_MOVE, Type.FLYING, -1, 20, -1, 0, 1) - .attr(CopyMoveAttr) - .ignoresVirtual(), + .attr(CopyMoveAttr, true), new AttackMove(Moves.SELF_DESTRUCT, Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1) .attr(SacrificialAttr) .makesContact(false) @@ -8309,8 +8605,7 @@ export function initMoves() { .target(MoveTarget.ALL_NEAR_ENEMIES), new ChargingAttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1) .chargeText(i18next.t("moveTriggers:loweredItsHead", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true) - .ignoresVirtual(), + .chargeAttr(StatStageChangeAttr, [ Stat.DEF ], 1, true), new AttackMove(Moves.SPIKE_CANNON, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1) .attr(MultiHitAttr) .makesContact(false), @@ -8350,8 +8645,7 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:isGlowing", { pokemonName: "{USER}" })) .attr(HighCritAttr) .attr(FlinchAttr) - .makesContact(false) - .ignoresVirtual(), + .makesContact(false), new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1) .attr(TransformAttr) // transforming from or into fusion pokemon causes various problems (such as crashes) @@ -8414,12 +8708,10 @@ export function initMoves() { new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) .attr(RecoilAttr, true, 0.25, true) .attr(TypelessAttr) - .ignoresVirtual() .target(MoveTarget.RANDOM_NEAR_ENEMY), new StatusMove(Moves.SKETCH, Type.NORMAL, -1, 1, -1, 0, 2) .ignoresSubstitute() - .attr(SketchAttr) - .ignoresVirtual(), + .attr(SketchAttr), new AttackMove(Moves.TRIPLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2) .attr(MultiHitAttr, MultiHitType._3) .attr(MultiHitPowerIncrementAttr, 3) @@ -8572,10 +8864,9 @@ export function initMoves() { .condition((user, target, move) => user.isOppositeGender(target)), new SelfStatusMove(Moves.SLEEP_TALK, Type.NORMAL, -1, 10, -1, 0, 2) .attr(BypassSleepAttr) - .attr(RandomMovesetMoveAttr) + .attr(RandomMovesetMoveAttr, invalidSleepTalkMoves, false) .condition(userSleptOrComatoseCondition) - .target(MoveTarget.ALL_ENEMIES) - .ignoresVirtual(), + .target(MoveTarget.NEAR_ENEMY), new StatusMove(Moves.HEAL_BELL, Type.NORMAL, -1, 5, -1, 0, 2) .attr(PartyStatusCureAttr, i18next.t("moveTriggers:bellChimed"), Abilities.SOUNDPROOF) .soundBased() @@ -8700,7 +8991,6 @@ export function initMoves() { .attr(FlinchAttr) .condition(new FirstMoveCondition()), new AttackMove(Moves.UPROAR, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3) - .ignoresVirtual() .soundBased() .target(MoveTarget.RANDOM_NEAR_ENEMY) .partial(), // Does not lock the user, does not stop Pokemon from sleeping @@ -8743,7 +9033,6 @@ export function initMoves() { new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3) .attr(MessageHeaderAttr, (user, move) => i18next.t("moveTriggers:isTighteningFocus", { pokemonName: getPokemonNameWithAffix(user) })) .punchingMove() - .ignoresVirtual() .condition((user, target, move) => !user.turnData.attacksReceived.find(r => r.damage)), new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1) @@ -8751,8 +9040,7 @@ export function initMoves() { new SelfStatusMove(Moves.FOLLOW_ME, Type.NORMAL, -1, 20, -1, 2, 3) .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), new StatusMove(Moves.NATURE_POWER, Type.NORMAL, -1, 20, -1, 0, 3) - .attr(NaturePowerAttr) - .ignoresVirtual(), + .attr(NaturePowerAttr), new SelfStatusMove(Moves.CHARGE, Type.ELECTRIC, -1, 20, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPDEF ], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false), @@ -8773,8 +9061,7 @@ export function initMoves() { .triageMove() .attr(AddArenaTagAttr, ArenaTagType.WISH, 2, true), new SelfStatusMove(Moves.ASSIST, Type.NORMAL, -1, 20, -1, 0, 3) - .attr(RandomMovesetMoveAttr, true) - .ignoresVirtual(), + .attr(RandomMovesetMoveAttr, invalidAssistMoves, true), new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3) .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true) @@ -8821,8 +9108,7 @@ export function initMoves() { new ChargingAttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) .chargeText(i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.UNDERWATER) - .chargeAttr(GulpMissileTagAttr) - .ignoresVirtual(), + .chargeAttr(GulpMissileTagAttr), new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3) .attr(MultiHitAttr), new SelfStatusMove(Moves.CAMOUFLAGE, Type.NORMAL, -1, 20, -1, 0, 3) @@ -8959,8 +9245,7 @@ export function initMoves() { .chargeText(i18next.t("moveTriggers:sprangUp", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .attr(StatusEffectAttr, StatusEffect.PARALYSIS) - .condition(failOnGravityCondition) - .ignoresVirtual(), + .condition(failOnGravityCondition), new AttackMove(Moves.MUD_SHOT, Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPD ], -1), new AttackMove(Moves.POISON_TAIL, Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 10, 0, 3) @@ -9087,12 +9372,10 @@ export function initMoves() { .target(MoveTarget.USER_SIDE), new StatusMove(Moves.ME_FIRST, Type.NORMAL, -1, 20, -1, 0, 4) .ignoresSubstitute() - .ignoresVirtual() .target(MoveTarget.NEAR_ENEMY) .unimplemented(), new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4) - .attr(CopyMoveAttr) - .ignoresVirtual(), + .attr(CopyMoveAttr, false, invalidCopycatMoves), new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4) .attr(SwapStatStagesAttr, [ Stat.ATK, Stat.SPATK ]) .ignoresSubstitute(), @@ -9316,8 +9599,7 @@ export function initMoves() { new ChargingAttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) - .ignoresProtect() - .ignoresVirtual(), + .ignoresProtect(), new SelfStatusMove(Moves.HONE_CLAWS, Type.DARK, -1, 15, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true), new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5) @@ -9444,7 +9726,6 @@ export function initMoves() { .chargeAttr(SemiInvulnerableAttr, BattlerTagType.FLYING) .condition(failOnGravityCondition) .condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE)) - .ignoresVirtual() .partial(), // Should immobilize the target, Flying types should take no damage. cf https://bulbapedia.bulbagarden.net/wiki/Sky_Drop_(move) and https://www.smogon.com/dex/sv/moves/sky-drop/ new SelfStatusMove(Moves.SHIFT_GEAR, Type.STEEL, -1, 10, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) @@ -9601,8 +9882,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.ICE_BURN, Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, 30, 0, 5) .chargeText(i18next.t("moveTriggers:becameCloakedInFreezingAir", { pokemonName: "{USER}" })) - .attr(StatusEffectAttr, StatusEffect.BURN) - .ignoresVirtual(), + .attr(StatusEffectAttr, StatusEffect.BURN), new AttackMove(Moves.SNARL, Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) .attr(StatStageChangeAttr, [ Stat.SPATK ], -1) .soundBased() @@ -9645,8 +9925,7 @@ export function initMoves() { new ChargingAttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:vanishedInstantly", { pokemonName: "{USER}" })) .chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN) - .ignoresProtect() - .ignoresVirtual(), + .ignoresProtect(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GHOST), new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6) @@ -9755,8 +10034,7 @@ export function initMoves() { .powderMove(), new ChargingSelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6) .chargeText(i18next.t("moveTriggers:isChargingPower", { pokemonName: "{USER}" })) - .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) - .ignoresVirtual(), + .attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], 1, false, { condition: (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS ].find(a => target.hasAbility(a, false)) }) .ignoresSubstitute() @@ -9823,116 +10101,79 @@ export function initMoves() { .ignoresProtect(), /* Unused */ new AttackMove(Moves.BREAKNECK_BLITZ__PHYSICAL, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BREAKNECK_BLITZ__SPECIAL, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ALL_OUT_PUMMELING__PHYSICAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ALL_OUT_PUMMELING__SPECIAL, Type.FIGHTING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__PHYSICAL, Type.FLYING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__SPECIAL, Type.FLYING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ACID_DOWNPOUR__PHYSICAL, Type.POISON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.ACID_DOWNPOUR__SPECIAL, Type.POISON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TECTONIC_RAGE__PHYSICAL, Type.GROUND, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TECTONIC_RAGE__SPECIAL, Type.GROUND, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CONTINENTAL_CRUSH__PHYSICAL, Type.ROCK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CONTINENTAL_CRUSH__SPECIAL, Type.ROCK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SAVAGE_SPIN_OUT__PHYSICAL, Type.BUG, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SAVAGE_SPIN_OUT__SPECIAL, Type.BUG, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__PHYSICAL, Type.GHOST, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__SPECIAL, Type.GHOST, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CORKSCREW_CRASH__PHYSICAL, Type.STEEL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CORKSCREW_CRASH__SPECIAL, Type.STEEL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.INFERNO_OVERDRIVE__PHYSICAL, Type.FIRE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.INFERNO_OVERDRIVE__SPECIAL, Type.FIRE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.HYDRO_VORTEX__PHYSICAL, Type.WATER, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.HYDRO_VORTEX__SPECIAL, Type.WATER, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLOOM_DOOM__PHYSICAL, Type.GRASS, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLOOM_DOOM__SPECIAL, Type.GRASS, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.GIGAVOLT_HAVOC__PHYSICAL, Type.ELECTRIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.GIGAVOLT_HAVOC__SPECIAL, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SHATTERED_PSYCHE__PHYSICAL, Type.PSYCHIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SHATTERED_PSYCHE__SPECIAL, Type.PSYCHIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUBZERO_SLAMMER__PHYSICAL, Type.ICE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SUBZERO_SLAMMER__SPECIAL, Type.ICE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.DEVASTATING_DRAKE__PHYSICAL, Type.DRAGON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.DEVASTATING_DRAKE__SPECIAL, Type.DRAGON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLACK_HOLE_ECLIPSE__PHYSICAL, Type.DARK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.BLACK_HOLE_ECLIPSE__SPECIAL, Type.DARK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TWINKLE_TACKLE__PHYSICAL, Type.FAIRY, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.TWINKLE_TACKLE__SPECIAL, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.CATASTROPIKA, Type.ELECTRIC, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), /* End Unused */ new SelfStatusMove(Moves.SHORE_UP, Type.GROUND, -1, 5, -1, 0, 7) .attr(SandHealAttr) @@ -10049,35 +10290,33 @@ export function initMoves() { .target(MoveTarget.USER_SIDE), /* Unused */ new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) + .unimplemented() .makesContact(false) - .edgeCase() // I assume it's because the user needs spirit shackle and decidueye - .ignoresVirtual(), + .edgeCase(), // I assume it's because the user needs spirit shackle and decidueye new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) + .unimplemented() .attr(AlwaysHitMinimizeAttr) .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) - .edgeCase() // I assume it's because it needs darkest lariat and incineroar - .ignoresVirtual(), + .edgeCase(), // I assume it's because it needs darkest lariat and incineroar new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs sparkling aria and primarina - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs sparkling aria and primarina new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.SOUL_STEALING_7_STAR_STRIKE, Type.GHOST, MoveCategory.PHYSICAL, 195, -1, 1, -1, 0, 7) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7) - .edgeCase() // I assume it's because it needs thunderbolt and Alola Raichu - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs thunderbolt and Alola Raichu new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs giga impact and snorlax - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs giga impact and snorlax new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7) - .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) - .ignoresVirtual(), + .unimplemented() + .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true), new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) - .attr(TerrainChangeAttr, TerrainType.PSYCHIC) - .ignoresVirtual(), + .unimplemented() + .attr(TerrainChangeAttr, TerrainType.PSYCHIC), /* End Unused */ new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7) .attr(AddBattlerTagHeaderAttr, BattlerTagType.SHELL_TRAP) @@ -10116,8 +10355,8 @@ export function initMoves() { .attr(FormChangeItemTypeAttr), /* Unused */ new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .edgeCase() // I assume it's because it needs thunderbolt and pikachu in a cap - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it's because it needs thunderbolt and pikachu in a cap /* End Unused */ new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7) .condition(failIfDampCondition) @@ -10131,28 +10370,28 @@ export function initMoves() { .ignoresAbilities(), /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) + .unimplemented() .attr(PhotonGeyserCategoryAttr) - .ignoresAbilities() - .ignoresVirtual(), + .ignoresAbilities(), new AttackMove(Moves.SEARING_SUNRAZE_SMASH, Type.STEEL, MoveCategory.PHYSICAL, 200, -1, 1, -1, 0, 7) - .ignoresAbilities() - .ignoresVirtual(), + .unimplemented() + .ignoresAbilities(), new AttackMove(Moves.MENACING_MOONRAZE_MAELSTROM, Type.GHOST, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) - .ignoresAbilities() - .ignoresVirtual(), + .unimplemented() + .ignoresAbilities(), new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) - .edgeCase() // I assume it needs play rough and mimikyu - .ignoresVirtual(), + .unimplemented() + .edgeCase(), // I assume it needs play rough and mimikyu new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) + .unimplemented() .attr(ClearTerrainAttr) - .makesContact(false) - .ignoresVirtual(), + .makesContact(false), new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7) + .unimplemented() .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, { firstTargetOnly: true }) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) - .edgeCase() // I assume it needs clanging scales and Kommo-O - .ignoresVirtual(), + .edgeCase(), // I assume it needs clanging scales and Kommo-O /* End Unused */ new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) // LGPE Implementation .attr(CritOnlyAttr), @@ -10190,9 +10429,9 @@ export function initMoves() { .punchingMove(), /* Unused */ new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8) + .unimplemented() .attr(ProtectAttr) - .condition(failIfLastCondition) - .ignoresVirtual(), + .condition(failIfLastCondition), /* End Unused */ new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, target, move) => { @@ -10205,8 +10444,7 @@ export function initMoves() { return 1; } }) - .attr(DiscourageFrequentUseAttr) - .ignoresVirtual(), + .attr(DiscourageFrequentUseAttr), new AttackMove(Moves.SNIPE_SHOT, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8) .attr(HighCritAttr) @@ -10252,76 +10490,58 @@ export function initMoves() { /* Unused */ new AttackMove(Moves.MAX_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_FLUTTERBY, Type.BUG, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_LIGHTNING, Type.ELECTRIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STRIKE, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_KNUCKLE, Type.FIGHTING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_PHANTASM, Type.GHOST, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_HAILSTORM, Type.ICE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_OOZE, Type.POISON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_GEYSER, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_AIRSTREAM, Type.FLYING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STARFALL, Type.FAIRY, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_WYRMWIND, Type.DRAGON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_MINDSTORM, Type.PSYCHIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_ROCKFALL, Type.ROCK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_QUAKE, Type.GROUND, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_DARKNESS, Type.DARK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_OVERGROWTH, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), new AttackMove(Moves.MAX_STEELSPIKE, Type.STEEL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8) .target(MoveTarget.NEAR_ENEMY) - .unimplemented() - .ignoresVirtual(), + .unimplemented(), /* End Unused */ new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, -1, 0, 8) .attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, 3) @@ -10394,8 +10614,7 @@ export function initMoves() { .makesContact(false), new ChargingAttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 8) .chargeText(i18next.t("moveTriggers:isOverflowingWithSpacePower", { pokemonName: "{USER}" })) - .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) - .ignoresVirtual(), + .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true), new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8) .attr(ShellSideArmCategoryAttr) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -10853,8 +11072,7 @@ export function initMoves() { new ChargingAttackMove(Moves.ELECTRO_SHOT, Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9) .chargeText(i18next.t("moveTriggers:absorbedElectricity", { pokemonName: "{USER}" })) .chargeAttr(StatStageChangeAttr, [ Stat.SPATK ], 1, true) - .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]) - .ignoresVirtual(), + .chargeAttr(WeatherInstantChargeAttr, [ WeatherType.RAIN, WeatherType.HEAVY_RAIN ]), new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(TeraMoveCategoryAttr) .attr(TeraStarstormTypeAttr) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d40254c8a6b..8fc00e2ebeb 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3298,7 +3298,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - getMoveQueue(): QueuedMove[] { + getMoveQueue(): TurnMove[] { return this.summonData.moveQueue; } @@ -4810,17 +4810,19 @@ export class EnemyPokemon extends Pokemon { * the Pokemon the move will target. * @returns this Pokemon's next move in the format {move, moveTargets} */ - getNextMove(): QueuedMove { + getNextMove(): TurnMove { // If this Pokemon has a move already queued, return it. - const queuedMove = this.getMoveQueue().length - ? this.getMoveset().find(m => m?.moveId === this.getMoveQueue()[0].move) - : null; - if (queuedMove) { - if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP)) { - return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP }; - } else { - this.getMoveQueue().shift(); - return this.getNextMove(); + const moveQueue = this.getMoveQueue(); + if (moveQueue.length !== 0) { + const queuedMove = moveQueue[0]; + if (queuedMove) { + const moveIndex = this.getMoveset().findIndex(m => m?.moveId === queuedMove.move); + if ((moveIndex > -1 && this.getMoveset()[moveIndex]!.isUsable(this, queuedMove.ignorePP)) || queuedMove.virtual) { + return queuedMove; + } else { + this.getMoveQueue().shift(); + return this.getNextMove(); + } } } @@ -5242,15 +5244,10 @@ export class EnemyPokemon extends Pokemon { export interface TurnMove { move: Moves; - targets?: BattlerIndex[]; - result: MoveResult; + targets: BattlerIndex[]; + result?: MoveResult; virtual?: boolean; turn?: number; -} - -export interface QueuedMove { - move: Moves; - targets: BattlerIndex[]; ignorePP?: boolean; } @@ -5266,7 +5263,7 @@ export interface AttackMoveResult { export class PokemonSummonData { /** [Atk, Def, SpAtk, SpDef, Spd, Acc, Eva] */ public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ]; - public moveQueue: QueuedMove[] = []; + public moveQueue: TurnMove[] = []; public tags: BattlerTag[] = []; public abilitySuppressed: boolean = false; public abilitiesApplied: Abilities[] = []; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index d7293ec02fe..e2bad953fc5 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -11,7 +11,7 @@ import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Biome } from "#app/enums/biome"; import { Moves } from "#app/enums/moves"; import { PokeballType } from "#enums/pokeball"; -import type { PlayerPokemon } from "#app/field/pokemon"; +import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; import { FieldPosition } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; @@ -86,19 +86,19 @@ export class CommandPhase extends FieldPhase { const moveQueue = playerPokemon.getMoveQueue(); while (moveQueue.length && moveQueue[0] - && moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) + && moveQueue[0].move && !moveQueue[0].virtual && (!playerPokemon.getMoveset().find(m => m?.moveId === moveQueue[0].move) || !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m?.moveId === moveQueue[0].move)]!.isUsable(playerPokemon, moveQueue[0].ignorePP))) { // TODO: is the bang correct? moveQueue.shift(); } - if (moveQueue.length) { + if (moveQueue.length > 0) { const queuedMove = moveQueue[0]; if (!queuedMove.move) { - this.handleCommand(Command.FIGHT, -1, false); + this.handleCommand(Command.FIGHT, -1); } else { const moveIndex = playerPokemon.getMoveset().findIndex(m => m?.moveId === queuedMove.move); - if (moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) { // TODO: is the bang correct? - this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, { targets: queuedMove.targets, multiple: queuedMove.targets.length > 1 }); + if ((moveIndex > -1 && playerPokemon.getMoveset()[moveIndex]!.isUsable(playerPokemon, queuedMove.ignorePP)) || queuedMove.virtual) { // TODO: is the bang correct? + this.handleCommand(Command.FIGHT, moveIndex, queuedMove.ignorePP, queuedMove); } else { globalScene.ui.setMode(Mode.COMMAND, this.fieldIndex); } @@ -120,12 +120,24 @@ export class CommandPhase extends FieldPhase { switch (command) { case Command.FIGHT: let useStruggle = false; + const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined); if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + + let moveId: Moves; + if (useStruggle) { + moveId = Moves.STRUGGLE; + } else if (turnMove !== undefined) { + moveId = turnMove.move; + } else if (cursor > -1) { + moveId = playerPokemon.getMoveset()[cursor]!.moveId; + } else { + moveId = Moves.NONE; + } + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 }; if (!moveId) { turnCommand.targets = [ this.fieldIndex ]; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 0673ad3effe..5330540c8b2 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -296,11 +296,6 @@ export class MovePhase extends BattlePhase { globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } - // Update the battle's "last move" pointer, unless we're currently mimicking a move. - if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { - globalScene.currentBattle.lastMove = this.move.moveId; - } - /** * Determine if the move is successful (meaning that its damage/effects can be attempted) * by checking that all of the following are true: @@ -324,6 +319,14 @@ export class MovePhase extends BattlePhase { const success = passesConditions && !failedDueToWeather && !failedDueToTerrain; + // Update the battle's "last move" pointer, unless we're currently mimicking a move. + if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { + // The last move used is unaffected by moves that fail + if (success) { + globalScene.currentBattle.lastMove = this.move.moveId; + } + } + /** * If the move has not failed, trigger ability-based user type changes and then execute it. * @@ -518,7 +521,7 @@ export class MovePhase extends BattlePhase { frenzyMissFunc(this.pokemon, this.move.getMove()); } - this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL }); + this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL, targets: this.targets }); this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT); this.pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE); diff --git a/src/test/moves/assist.test.ts b/src/test/moves/assist.test.ts new file mode 100644 index 00000000000..81633d9a277 --- /dev/null +++ b/src/test/moves/assist.test.ts @@ -0,0 +1,105 @@ +import { BattlerIndex } from "#app/battle"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Assist", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + // Manual moveset overrides are required for the player pokemon in these tests + // because the normal moveset override doesn't allow for accurate testing of moveset changes + game.override + .ability(Abilities.BALL_FETCH) + .battleType("double") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyLevel(100) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should only use an ally's moves", async () => { + game.override.enemyMoveset(Moves.SWORDS_DANCE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + // These are all moves Assist cannot call; Sketch will be used to test that it can call other moves properly + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + game.move.select(Moves.SKETCH, 1); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]); + // Player_2 uses Sketch, copies Swords Dance, Player_1 uses Assist, uses Player_2's Sketched Swords Dance + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(2); // Stat raised from Assist -> Swords Dance + }); + + it("should fail if there are no allies", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const feebas = game.scene.getPlayerPokemon()!; + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should fail if ally has no usable moves and user has usable moves", async () => { + game.override.enemyMoveset(Moves.SWORDS_DANCE); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.SKETCH, 0); + game.move.select(Moves.PROTECT, 1); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + // Player uses Sketch to copy Swords Dance, Player_2 stalls a turn. Player will attempt Assist and should have no usable moves + await game.toNextTurn(); + game.move.select(Moves.ASSIST, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.PROTECT, 1); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should apply secondary effects of a move", async () => { + game.override.moveset([ Moves.ASSIST, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER, Moves.WOOD_HAMMER ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.SHUCKLE ]); + + const [ feebas, shuckle ] = game.scene.getPlayerField(); + game.move.changeMoveset(feebas, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + game.move.changeMoveset(shuckle, [ Moves.ASSIST, Moves.SKETCH, Moves.PROTECT, Moves.DRAGON_TAIL ]); + + game.move.select(Moves.ASSIST, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.ASSIST, 1); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // should receive recoil damage from Wood Hammer + }); +}); diff --git a/src/test/moves/copycat.test.ts b/src/test/moves/copycat.test.ts new file mode 100644 index 00000000000..d9e64289481 --- /dev/null +++ b/src/test/moves/copycat.test.ts @@ -0,0 +1,91 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Copycat", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.COPYCAT, Moves.SPIKY_SHIELD, Moves.SWORDS_DANCE, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .starterSpecies(Species.FEEBAS) + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should copy the last move successfully executed", async () => { + game.override.enemyMoveset(Moves.SUCKER_PUNCH); + await game.classicMode.startBattle(); + + game.move.select(Moves.SWORDS_DANCE); + await game.toNextTurn(); + + game.move.select(Moves.COPYCAT); // Last successful move should be Swords Dance + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(4); + }); + + it("should fail when the last move used is not a valid Copycat move", async () => { + game.override.enemyMoveset(Moves.PROTECT); // Protect is not a valid move for Copycat to copy + await game.classicMode.startBattle(); + + game.move.select(Moves.SPIKY_SHIELD); // Spiky Shield is not a valid move for Copycat to copy + await game.toNextTurn(); + + game.move.select(Moves.COPYCAT); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should copy the called move when the last move successfully calls another", async () => { + game.override + .moveset([ Moves.SPLASH, Moves.METRONOME ]) + .enemyMoveset(Moves.COPYCAT); + await game.classicMode.startBattle(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.SWORDS_DANCE); + + game.move.select(Moves.METRONOME); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); // Player moves first, so enemy can copy Swords Dance + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(2); + }); + + it("should apply secondary effects of a move", async () => { + game.override.enemyMoveset(Moves.ACID_SPRAY); // Secondary effect lowers SpDef by 2 stages + await game.classicMode.startBattle(); + + game.move.select(Moves.COPYCAT); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2); + }); +}); diff --git a/src/test/moves/metronome.test.ts b/src/test/moves/metronome.test.ts new file mode 100644 index 00000000000..946dc92de0f --- /dev/null +++ b/src/test/moves/metronome.test.ts @@ -0,0 +1,113 @@ +import { RechargingTag, SemiInvulnerableTag } from "#app/data/battler-tags"; +import { allMoves, RandomMoveAttr } from "#app/data/move"; +import { Abilities } from "#app/enums/abilities"; +import { Stat } from "#app/enums/stat"; +import { CommandPhase } from "#app/phases/command-phase"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Metronome", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const randomMoveAttr = allMoves[Moves.METRONOME].getAttrs(RandomMoveAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.METRONOME, Moves.SPLASH ]) + .battleType("single") + .startingLevel(100) + .starterSpecies(Species.REGIELEKI) + .enemyLevel(100) + .enemySpecies(Species.SHUCKLE) + .enemyMoveset(Moves.SPLASH) + .enemyAbility(Abilities.BALL_FETCH); + }); + + it("should have one semi-invulnerable turn and deal damage on the second turn when a semi-invulnerable move is called", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + const enemy = game.scene.getEnemyPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.DIVE); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.getTag(SemiInvulnerableTag)).toBeTruthy(); + + await game.toNextTurn(); + expect(player.getTag(SemiInvulnerableTag)).toBeFalsy(); + expect(enemy.isFullHp()).toBeFalsy(); + }); + + it("should apply secondary effects of a move", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.WOOD_HAMMER); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.isFullHp()).toBeFalsy(); + }); + + it("should recharge after using recharge move", async () => { + await game.classicMode.startBattle(); + const player = game.scene.getPlayerPokemon()!; + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.HYPER_BEAM); + vi.spyOn(allMoves[Moves.HYPER_BEAM], "accuracy", "get").mockReturnValue(100); + + game.move.select(Moves.METRONOME); + await game.toNextTurn(); + + expect(player.getTag(RechargingTag)).toBeTruthy(); + }); + + it("should only target ally for Aromatic Mist", async () => { + game.override.battleType("double"); + await game.classicMode.startBattle([ Species.REGIELEKI, Species.RATTATA ]); + const [ leftPlayer, rightPlayer ] = game.scene.getPlayerField(); + const [ leftOpp, rightOpp ] = game.scene.getEnemyField(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.AROMATIC_MIST); + + game.move.select(Moves.METRONOME, 0); + await game.phaseInterceptor.to(CommandPhase); + game.move.select(Moves.SPLASH, 1); + await game.toNextTurn(); + + expect(rightPlayer.getStatStage(Stat.SPDEF)).toBe(1); + expect(leftPlayer.getStatStage(Stat.SPDEF)).toBe(0); + expect(leftOpp.getStatStage(Stat.SPDEF)).toBe(0); + expect(rightOpp.getStatStage(Stat.SPDEF)).toBe(0); + }); + + it("should cause opponent to flee, and not crash for Roar", async () => { + await game.classicMode.startBattle(); + vi.spyOn(randomMoveAttr, "getMoveOverride").mockReturnValue(Moves.ROAR); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.METRONOME); + await game.phaseInterceptor.to("BerryPhase"); + + const isVisible = enemyPokemon.visible; + const hasFled = enemyPokemon.switchOutStatus; + expect(!isVisible && hasFled).toBe(true); + + await game.phaseInterceptor.to("CommandPhase"); + }); +}); diff --git a/src/test/moves/mirror_move.test.ts b/src/test/moves/mirror_move.test.ts new file mode 100644 index 00000000000..e55c55038ae --- /dev/null +++ b/src/test/moves/mirror_move.test.ts @@ -0,0 +1,84 @@ +import { BattlerIndex } from "#app/battle"; +import { Stat } from "#app/enums/stat"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Mirror Move", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MIRROR_MOVE, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should use the last move that the target used on the user", async () => { + game.override + .battleType("double") + .enemyMoveset([ Moves.TACKLE, Moves.GROWL ]); + await game.classicMode.startBattle([ Species.FEEBAS, Species.MAGIKARP ]); + + game.move.select(Moves.MIRROR_MOVE, 0, BattlerIndex.ENEMY); // target's last move is Tackle, enemy should receive damage from Mirror Move copying Tackle + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.GROWL, BattlerIndex.PLAYER_2); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER_2, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyField()[0].isFullHp()).toBeFalsy(); + }); + + it("should apply secondary effects of a move", async () => { + game.override.enemyMoveset(Moves.ACID_SPRAY); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPDEF)).toBe(-2); + }); + + it("should be able to copy status moves", async () => { + game.override.enemyMoveset(Moves.GROWL); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1); + }); + + it("should fail if the target has not used any moves", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.MIRROR_MOVE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); +}); diff --git a/src/test/moves/sleep_talk.test.ts b/src/test/moves/sleep_talk.test.ts new file mode 100644 index 00000000000..9ad2d23f903 --- /dev/null +++ b/src/test/moves/sleep_talk.test.ts @@ -0,0 +1,75 @@ +import { Stat } from "#app/enums/stat"; +import { StatusEffect } from "#app/enums/status-effect"; +import { MoveResult } from "#app/field/pokemon"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Sleep Talk", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH, Moves.SLEEP_TALK ]) + .statusEffect(StatusEffect.SLEEP) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemyLevel(100); + }); + + it("should fail when the user is not asleep", async () => { + game.override.statusEffect(StatusEffect.NONE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should fail if the user has no valid moves", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.METRONOME, Moves.SOLAR_BEAM ]); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + }); + + it("should call a random valid move if the user is asleep", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.SWORDS_DANCE ]); // Dig and Fly are invalid moves, Swords Dance should always be called + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)); + }); + + it("should apply secondary effects of a move", async () => { + game.override.moveset([ Moves.SLEEP_TALK, Moves.DIG, Moves.FLY, Moves.WOOD_HAMMER ]); // Dig and Fly are invalid moves, Wood Hammer should always be called + await game.classicMode.startBattle(); + + game.move.select(Moves.SLEEP_TALK); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.isFullHp()).toBeFalsy(); // Wood Hammer recoil effect should be applied + }); +}); diff --git a/src/test/moves/spit_up.test.ts b/src/test/moves/spit_up.test.ts index fd21bb3c6c1..7f9dfaad38b 100644 --- a/src/test/moves/spit_up.test.ts +++ b/src/test/moves/spit_up.test.ts @@ -125,7 +125,7 @@ describe("Moves - Spit Up", () => { game.move.select(Moves.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.FAIL }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.FAIL, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).not.toHaveBeenCalled(); }); @@ -148,7 +148,7 @@ describe("Moves - Spit Up", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce(); @@ -176,7 +176,7 @@ describe("Moves - Spit Up", () => { game.move.select(Moves.SPIT_UP); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS, targets: [ game.scene.getEnemyPokemon()!.getBattlerIndex() ]}); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce(); diff --git a/src/test/moves/stockpile.test.ts b/src/test/moves/stockpile.test.ts index e50fe041b0a..f83459cd09d 100644 --- a/src/test/moves/stockpile.test.ts +++ b/src/test/moves/stockpile.test.ts @@ -72,7 +72,7 @@ describe("Moves - Stockpile", () => { expect(user.getStatStage(Stat.SPDEF)).toBe(3); expect(stockpilingTag).toBeDefined(); expect(stockpilingTag.stockpiledCount).toBe(3); - expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: Moves.STOCKPILE }); + expect(user.getMoveHistory().at(-1)).toMatchObject({ result: MoveResult.FAIL, move: Moves.STOCKPILE, targets: [ user.getBattlerIndex() ]}); } } }); diff --git a/src/test/moves/swallow.test.ts b/src/test/moves/swallow.test.ts index c154d3c7c2c..b2435ba77b3 100644 --- a/src/test/moves/swallow.test.ts +++ b/src/test/moves/swallow.test.ts @@ -135,7 +135,7 @@ describe("Moves - Swallow", () => { game.move.select(Moves.SWALLOW); await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.FAIL }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.FAIL, targets: [ pokemon.getBattlerIndex() ]}); }); describe("restores stat stage boosts granted by stacks", () => { @@ -156,7 +156,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]}); expect(pokemon.getStatStage(Stat.DEF)).toBe(0); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0); @@ -183,7 +183,7 @@ describe("Moves - Swallow", () => { await game.phaseInterceptor.to(TurnInitPhase); - expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); + expect(pokemon.getMoveHistory().at(-1)).toMatchObject({ move: Moves.SWALLOW, result: MoveResult.SUCCESS, targets: [ pokemon.getBattlerIndex() ]}); expect(pokemon.getStatStage(Stat.DEF)).toBe(1); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2); From 6681a913fececeb7e4d5372676cd7ece1fce782b Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:25:18 -0500 Subject: [PATCH 13/54] [Bug] Prevent Duplicate Signature Species in Trainer Battles (#5059) * [Bug] Prevent Duplicate Signature Species in Trainer Battles * Apply Kev's Suggestion Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/field/trainer.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index fc12eb57abe..2b74c1e5069 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -428,7 +428,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } // Prompts reroll of party member species if species already present in the enemy party - if (this.checkDuplicateSpecies(ret, baseSpecies)) { + if (this.checkDuplicateSpecies(baseSpecies.speciesId)) { console.log("Duplicate species detected, prompting reroll..."); retry = true; } @@ -443,17 +443,23 @@ export default class Trainer extends Phaser.GameObjects.Container { /** * Checks if the enemy trainer already has the Pokemon species in their party - * @param {PokemonSpecies} species {@linkcode PokemonSpecies} - * @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve + * @param baseSpecies - The base {@linkcode Species} of the current Pokemon * @returns `true` if the species is already present in the party */ - checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean { - const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1); - - const currentPartySpecies = globalScene.getEnemyParty().map(p => { - return p.species.speciesId; + checkDuplicateSpecies(baseSpecies: Species): boolean { + const staticSpecies = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1).map(s => { + let root = s; + while (pokemonPrevolutions.hasOwnProperty(root)) { + root = pokemonPrevolutions[root]; + } + return root; }); - return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId); + + const currentSpecies = globalScene.getEnemyParty().map(p => { + return p.species.getRootSpeciesId(); + }); + + return currentSpecies.includes(baseSpecies) || staticSpecies.includes(baseSpecies); } getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] { From d5f84cf3df7b5d5e0dcd9fffb9b8f49cb5ca0c74 Mon Sep 17 00:00:00 2001 From: damocleas Date: Tue, 14 Jan 2025 21:07:09 -0500 Subject: [PATCH 14/54] Change Archen HA from Emergency Exit to Wimp Out (#5124) --- src/data/pokemon-species.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 84486b30372..bd041fe7559 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -1834,7 +1834,7 @@ export function initSpecies() { new PokemonSpecies(Species.COFAGRIGUS, 5, false, false, false, "Coffin Pokémon", Type.GHOST, null, 1.7, 76.5, Abilities.MUMMY, Abilities.NONE, Abilities.NONE, 483, 58, 50, 145, 95, 105, 30, 90, 50, 169, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.TIRTOUGA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 0.7, 16.5, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 355, 54, 78, 103, 53, 45, 22, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), new PokemonSpecies(Species.CARRACOSTA, 5, false, false, false, "Prototurtle Pokémon", Type.WATER, Type.ROCK, 1.2, 81, Abilities.SOLID_ROCK, Abilities.STURDY, Abilities.SWIFT_SWIM, 495, 74, 108, 133, 83, 65, 32, 45, 50, 173, GrowthRate.MEDIUM_FAST, 87.5, false), - new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden + new PokemonSpecies(Species.ARCHEN, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 0.5, 9.5, Abilities.DEFEATIST, Abilities.NONE, Abilities.WIMP_OUT, 401, 55, 112, 45, 74, 45, 70, 45, 50, 71, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden new PokemonSpecies(Species.ARCHEOPS, 5, false, false, false, "First Bird Pokémon", Type.ROCK, Type.FLYING, 1.4, 32, Abilities.DEFEATIST, Abilities.NONE, Abilities.EMERGENCY_EXIT, 567, 75, 140, 65, 112, 65, 110, 45, 50, 177, GrowthRate.MEDIUM_FAST, 87.5, false), //Custom Hidden new PokemonSpecies(Species.TRUBBISH, 5, false, false, false, "Trash Bag Pokémon", Type.POISON, null, 0.6, 31, Abilities.STENCH, Abilities.STICKY_HOLD, Abilities.AFTERMATH, 329, 50, 50, 62, 40, 62, 65, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GARBODOR, 5, false, false, false, "Trash Heap Pokémon", Type.POISON, null, 1.9, 107.3, Abilities.STENCH, Abilities.WEAK_ARMOR, Abilities.AFTERMATH, 474, 80, 95, 82, 60, 82, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, From 7ae216f0d6b4d2b11f6d757e7056969d014c87ca Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 15 Jan 2025 07:18:24 +0100 Subject: [PATCH 15/54] [UI/UX] Shop cursor freedom (#5110) * Allowing cursor in shop to cycle horizontally * Improved cycling of commands --------- Co-authored-by: damocleas --- src/ui/modifier-select-ui-handler.ts | 38 +++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 05740a349c6..0cca087ce8d 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -364,6 +364,8 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setCursor(0); } else if (this.rowCursor < this.shopOptionsRows.length + 1) { success = this.setRowCursor(this.rowCursor + 1); + } else { + success = this.setRowCursor(0); } break; case Button.DOWN: @@ -371,13 +373,15 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setRowCursor(this.rowCursor - 1); } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { success = this.setCursor(3); + } else { + success = this.setRowCursor(this.shopOptionsRows.length + 1); } break; case Button.LEFT: if (!this.rowCursor) { switch (this.cursor) { case 0: - success = false; + success = this.setCursor(2); break; case 1: if (this.lockRarityButtonContainer.visible) { @@ -395,11 +399,21 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = false; } break; + case 3: + if (this.lockRarityButtonContainer.visible) { + success = this.setCursor(2); + } else { + success = false; + } } } else if (this.cursor) { success = this.setCursor(this.cursor - 1); - } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { - success = this.setRowCursor(0); + } else { + if (this.rowCursor === 1 && this.options.length === 0) { + success = false; + } else { + success = this.setCursor(this.getRowItems(this.rowCursor) - 1); + } } break; case Button.RIGHT: @@ -416,7 +430,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { success = this.setCursor(2); break; case 2: - success = false; + success = this.setCursor(0); break; case 3: if (this.transferButtonContainer.visible) { @@ -428,8 +442,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { success = this.setCursor(this.cursor + 1); - } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { - success = this.setRowCursor(0); + } else { + if (this.rowCursor === 1 && this.options.length === 0) { + success = this.setRowCursor(0); + } else { + success = this.setCursor(0); + } } break; } @@ -519,6 +537,14 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { newCursor = 2; } } + // Allows to find lock rarity button when looping from the top + if (rowCursor === 0 && lastRowCursor > 1 && newCursor === 0 && this.lockRarityButtonContainer.visible) { + newCursor = 3; + } + // Allows to loop to top when lock rarity button is shown + if (rowCursor === this.shopOptionsRows.length + 1 && lastRowCursor === 0 && this.cursor === 3) { + newCursor = 0; + } this.cursor = -1; this.setCursor(newCursor); return true; From d2a3e4bb2d62164a2564d5799e35069bf2703ae8 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 15 Jan 2025 08:23:38 +0100 Subject: [PATCH 16/54] [UI/UX] Looping cursor in save slot selection screen (#5109) * Save slot selection allows looping * Removed debug logs --------- Co-authored-by: damocleas --- src/ui/save-slot-select-ui-handler.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 13f5020e5ad..fe2ac9e1221 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -157,6 +157,12 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); } else if (this.scrollCursor) { success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); + } else if ((this.cursor === 0) && (this.scrollCursor === 0)) { + this.setScrollCursor(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN); + // Revert to avoid an extra session slot sticking out + this.revertSessionSlot(SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN); + this.setCursor(SLOTS_ON_SCREEN - 1); + success = true; } break; case Button.DOWN: @@ -164,6 +170,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor + 1, cursorPosition); } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); + } else if ((this.cursor === SLOTS_ON_SCREEN - 1) && (this.scrollCursor === SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN)) { + this.setScrollCursor(0); + this.revertSessionSlot(SLOTS_ON_SCREEN - 1); + this.setCursor(0); + success = true; } break; case Button.RIGHT: From e2c6bec418e7d4e48ab3c37a0b51aadb860320f4 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:53:16 -0500 Subject: [PATCH 17/54] [Bug] Fix Poltergeist message displaying before move use (#5040) --- src/data/move.ts | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index f3a1f3aa119..572fbf4c2ac 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7878,31 +7878,6 @@ export class LastResortAttr extends MoveAttr { } } - -/** - * The move only works if the target has a transferable held item - * @extends MoveAttr - * @see {@linkcode getCondition} - */ -export class AttackedByItemAttr extends MoveAttr { - /** - * @returns the {@linkcode MoveConditionFunc} for this {@linkcode Move} - */ - getCondition(): MoveConditionFunc { - return (user: Pokemon, target: Pokemon, move: Move) => { - const heldItems = target.getHeldItems().filter(i => i.isTransferable); - if (heldItems.length === 0) { - return false; - } - - const itemName = heldItems[0]?.type?.name ?? "item"; - globalScene.queueMessage(i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName })); - - return true; - }; - } -} - export class VariableTargetAttr extends MoveAttr { private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number; @@ -7976,6 +7951,18 @@ const failIfLastInPartyCondition: MoveConditionFunc = (user: Pokemon, target: Po const failIfGhostTypeCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => !target.isOfType(Type.GHOST); +const failIfNoTargetHeldItemsCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.getHeldItems().filter(i => i.isTransferable)?.length > 0; + +const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => { + const heldItems = target.getHeldItems().filter(i => i.isTransferable); + if (heldItems.length === 0) { + return ""; + } + const itemName = heldItems[0]?.type?.name ?? "item"; + const message: string = i18next.t("moveTriggers:attackedByItem", { pokemonName: getPokemonNameWithAffix(target), itemName: itemName }); + return message; +}; + export type MoveAttrFilter = (attr: MoveAttr) => boolean; function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise { @@ -10641,7 +10628,8 @@ export function initMoves() { new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8) .attr(MovePowerMultiplierAttr, (user, _target, _move) => user.turnData.statStagesDecreased ? 2 : 1), new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8) - .attr(AttackedByItemAttr) + .condition(failIfNoTargetHeldItemsCondition) + .attr(PreMoveMessageAttr, attackedByItemMessageFunc) .makesContact(false), new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) .target(MoveTarget.ALL_NEAR_OTHERS) From 39b4d74e9592b76ae8159dfb43046b3ce7366ce3 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:06:09 -0800 Subject: [PATCH 18/54] [Bug] Fix #5029 Memory leak when saving and exiting (#5128) * Add destroy function to ui handlers * Implement destroy() for StarterSelectUiHandler * Update battlescene to free memory when resetting * Document destroy for starter select --------- Co-authored-by: damocleas --- src/battle-scene.ts | 3 +++ src/ui/settings/navigationMenu.ts | 7 +++++++ src/ui/starter-select-ui-handler.ts | 5 +++++ src/ui/ui-handler.ts | 5 +++++ src/ui/ui.ts | 11 +++++++++++ 5 files changed, 31 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6db9311bac8..e9d5a97ab8d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1191,6 +1191,9 @@ export default class BattleScene extends SceneBase { onComplete: () => { this.clearPhaseQueue(); + this.ui.freeUIData(); + this.uiContainer.remove(this.ui, true); + this.uiContainer.destroy(); this.children.removeAll(true); this.game.domContainer.innerHTML = ""; this.launchBattle(); diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index eeb6da319ef..5fa53b7c270 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -89,6 +89,13 @@ export class NavigationManager { } } + /** + * Removes menus from the manager in preparation for reset + */ + public clearNavigationMenus() { + this.navigationMenus.length = 0; + } + } export default class NavigationMenu extends Phaser.GameObjects.Container { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 29c58d7087e..40325d24af7 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2698,6 +2698,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.updateScroll(); }; + override destroy(): void { + // Without this the reference gets hung up and no startercontainers get GCd + this.starterContainers = []; + } + updateScroll = () => { const maxColumns = 9; const maxRows = 9; diff --git a/src/ui/ui-handler.ts b/src/ui/ui-handler.ts index 1f0155aef8b..89f8d9e65b6 100644 --- a/src/ui/ui-handler.ts +++ b/src/ui/ui-handler.ts @@ -62,4 +62,9 @@ export default abstract class UiHandler { clear() { this.active = false; } + /** + * To be implemented by individual handlers when necessary to free memory + * Called when {@linkcode BattleScene} is reset + */ + destroy(): void {} } diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 6d44997f649..9e8c52b1d24 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -53,6 +53,7 @@ import TestDialogueUiHandler from "#app/ui/test-dialogue-ui-handler"; import AutoCompleteUiHandler from "./autocomplete-ui-handler"; import { Device } from "#enums/devices"; import MysteryEncounterUiHandler from "./mystery-encounter-ui-handler"; +import { NavigationManager } from "./settings/navigationMenu"; export enum Mode { MESSAGE, @@ -614,4 +615,14 @@ export default class UI extends Phaser.GameObjects.Container { return globalScene.inputMethod; } } + + /** + * Attempts to free memory held by UI handlers + * and clears menus from {@linkcode NavigationManager} to prepare for reset + */ + public freeUIData(): void { + this.handlers.forEach(h => h.destroy()); + this.handlers = []; + NavigationManager.getInstance().clearNavigationMenus(); + } } From d6247335659224a58674952af584158f090d9c59 Mon Sep 17 00:00:00 2001 From: Jimmybald1 <122436263+Jimmybald1@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:12:50 +0100 Subject: [PATCH 19/54] [Bug] Fix #5034 removed unnecessary caught data block for certain forms (#5119) Co-authored-by: Jimmybald1 <147992650+IBBCalc@users.noreply.github.com> Co-authored-by: damocleas --- src/data/pokemon-species.ts | 13 ------------- src/system/game-data.ts | 5 +---- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index bd041fe7559..285c2a70236 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -948,19 +948,6 @@ export class PokemonForm extends PokemonSpeciesForm { } } -export const noStarterFormKeys: string[] = [ - SpeciesFormKey.MEGA, - SpeciesFormKey.MEGA_X, - SpeciesFormKey.MEGA_Y, - SpeciesFormKey.PRIMAL, - SpeciesFormKey.ORIGIN, - SpeciesFormKey.THERIAN, - SpeciesFormKey.GIGANTAMAX, - SpeciesFormKey.GIGANTAMAX_RAPID, - SpeciesFormKey.GIGANTAMAX_SINGLE, - SpeciesFormKey.ETERNAMAX -].map(k => k.toString()); - /** * Method to get the daily list of starters with Pokerus. * @returns A list of starters with Pokerus diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 11b98d3fee6..58d416eb468 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -6,7 +6,7 @@ import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import type PokemonSpecies from "#app/data/pokemon-species"; -import { allSpecies, getPokemonSpecies, noStarterFormKeys } from "#app/data/pokemon-species"; +import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import * as Utils from "#app/utils"; import Overrides from "#app/overrides"; @@ -1619,9 +1619,6 @@ export class GameData { const dexEntry = this.dexData[species.speciesId]; const caughtAttr = dexEntry.caughtAttr; const formIndex = pokemon.formIndex; - if (noStarterFormKeys.includes(pokemon.getFormKey())) { - pokemon.formIndex = 0; - } const dexAttr = pokemon.getDexAttr(); pokemon.formIndex = formIndex; From 95c6f4cd52b2a22c5099636d8de7e95d814b440b Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Wed, 15 Jan 2025 03:33:52 -0600 Subject: [PATCH 20/54] Update locales --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 4928231e22a..0d4d614a759 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb +Subproject commit 0d4d614a75998086e9bb10d9328d18b29b133f5b From 608a92b70c638c36e885f8868722fdd94219fd61 Mon Sep 17 00:00:00 2001 From: Madmadness65 Date: Wed, 15 Jan 2025 03:39:52 -0600 Subject: [PATCH 21/54] Revert "Update locales" This reverts commit 95c6f4cd52b2a22c5099636d8de7e95d814b440b. --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 0d4d614a759..4928231e22a 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 0d4d614a75998086e9bb10d9328d18b29b133f5b +Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb From ee6115f49dc895eb13a88f7e092a6cc4211032ce Mon Sep 17 00:00:00 2001 From: Scooom <97370685+Scoooom@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:55:14 -0600 Subject: [PATCH 22/54] [Challenge] Flip Stats Challenge (#5087) * Implement Flip Stat Challange * Add Achivement * Update challenge code to block other challenges. * Add Achievment Image * Add FLIP_STAT to enum ChallengeType * Fix comment for FlipStatChallenge * Add applyFlipStat override to Challenge Class, and add override inside of FlipStatsChallenge * Add ChallengeType.FLIP_STAT case to export function applyChallenges (Master Switch Function) * Properly block other challange achviements * Change the way achivements are blocked by challenge modes to a more flexible method * Adjust the image for Flip Stat, and add an additional achivement for completing both Flip and Inverse * Fix FLIP_INVERSE achivement to check ALL challanges are met, not SOME * Remove outdated image * Fix FlipStat applyChallenges inside calculateBaseStats * Update locales --------- Co-authored-by: Scooom Co-authored-by: Scooom Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- public/locales | 2 +- src/data/challenge.ts | 51 +++++++++++++++++++++++++++++++ src/enums/challenges.ts | 1 + src/field/pokemon.ts | 1 + src/system/achv.ts | 67 +++++++++++++++++++++++------------------ 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/public/locales b/public/locales index 4928231e22a..acad8499a4c 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb +Subproject commit acad8499a4ca488a9871902de140f635235f309a diff --git a/src/data/challenge.ts b/src/data/challenge.ts index b5eca55cb71..a01ceab8aa3 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -88,6 +88,11 @@ export enum ChallengeType { * Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED. */ MOVE_WEIGHT, + /** + * Modifies what the pokemon stats for Flip Stat Mode. + */ + FLIP_STAT, + } /** @@ -405,6 +410,16 @@ export abstract class Challenge { applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean { return false; } + + /** + * An apply function for FlipStats. Derived classes should alter this. + * @param pokemon {@link Pokemon} What pokemon would learn the move. + * @param baseStats What are the stats to flip. + * @returns {@link boolean} Whether this function did anything. + */ + applyFlipStat(pokemon: Pokemon, baseStats: number[]) { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -705,6 +720,33 @@ export class InverseBattleChallenge extends Challenge { } } +/** + * Implements a flip stat challenge. + */ +export class FlipStatChallenge extends Challenge { + constructor() { + super(Challenges.FLIP_STAT, 1); + } + + override applyFlipStat(pokemon: Pokemon, baseStats: number[]) { + const origStats = Utils.deepCopy(baseStats); + baseStats[0] = origStats[5]; + baseStats[1] = origStats[4]; + baseStats[2] = origStats[3]; + baseStats[3] = origStats[2]; + baseStats[4] = origStats[1]; + baseStats[5] = origStats[0]; + return true; + } + + static loadChallenge(source: FlipStatChallenge | any): FlipStatChallenge { + const newChallenge = new FlipStatChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + /** * Lowers the amount of starter points available. */ @@ -890,6 +932,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType * @returns True if any challenge was successfully applied. */ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.IntegerHolder): boolean; + +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; + export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; gameMode.challenges.forEach(c => { @@ -934,6 +979,9 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.MOVE_WEIGHT: ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); break; + case ChallengeType.FLIP_STAT: + ret ||= c.applyFlipStat(args[0], args[1]); + break; } } }); @@ -959,6 +1007,8 @@ export function copyChallenge(source: Challenge | any): Challenge { return FreshStartChallenge.loadChallenge(source); case Challenges.INVERSE_BATTLE: return InverseBattleChallenge.loadChallenge(source); + case Challenges.FLIP_STAT: + return FlipStatChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -971,5 +1021,6 @@ export function initChallenges() { new SingleTypeChallenge(), new FreshStartChallenge(), new InverseBattleChallenge(), + new FlipStatChallenge() ); } diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index c4dc7460dfe..7b506a61a2f 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -5,4 +5,5 @@ export enum Challenges { LOWER_STARTER_POINTS, FRESH_START, INVERSE_BATTLE, + FLIP_STAT, } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8fc00e2ebeb..432f0a92fec 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1057,6 +1057,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); + applyChallenges(globalScene.gameMode, ChallengeType.FLIP_STAT, this, baseStats); // Shuckle Juice globalScene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); // Old Gateau diff --git a/src/system/achv.ts b/src/system/achv.ts index 1f5662dbdbe..fb17e7b1ced 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -5,7 +5,7 @@ import i18next from "i18next"; import * as Utils from "../utils"; import { PlayerGender } from "#enums/player-gender"; import type { Challenge } from "#app/data/challenge"; -import { FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; +import { FlipStatChallenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge, InverseBattleChallenge } from "#app/data/challenge"; import type { ConditionFn } from "#app/@types/common"; import { Stat, getShortenedStatKey } from "#app/enums/stat"; import { Challenges } from "#app/enums/challenges"; @@ -280,6 +280,10 @@ export function getAchievementDescription(localizationKey: string): string { return i18next.t("achv:FRESH_START.description", { context: genderStr }); case "INVERSE_BATTLE": return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "FLIP_STATS": + return i18next.t("achv:FLIP_STATS.description", { context: genderStr }); + case "FLIP_INVERSE": + return i18next.t("achv:FLIP_INVERSE.description", { context: genderStr }); case "BREEDERS_IN_SPACE": return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); default: @@ -288,6 +292,7 @@ export function getAchievementDescription(localizationKey: string): string { } + export const achvs = { _10K_MONEY: new MoneyAchv("10K_MONEY", "", 10000, "nugget", 10), _100K_MONEY: new MoneyAchv("100K_MONEY", "", 100000, "big_nugget", 25).setSecret(true), @@ -330,35 +335,37 @@ export const achvs = { PERFECT_IVS: new Achv("PERFECT_IVS", "", "PERFECT_IVS.description", "blunder_policy", 100), CLASSIC_VICTORY: new Achv("CLASSIC_VICTORY", "", "CLASSIC_VICTORY.description", "relic_crown", 150, (_) => globalScene.gameData.gameStats.sessionsWon === 0), UNEVOLVED_CLASSIC_VICTORY: new Achv("UNEVOLVED_CLASSIC_VICTORY", "", "UNEVOLVED_CLASSIC_VICTORY.description", "eviolite", 175, (_) => globalScene.getPlayerParty().some(p => p.getSpeciesForm(true).speciesId in pokemonEvolutions)), - MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)), - INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0), + MONO_GEN_ONE_VICTORY: new ChallengeAchv("MONO_GEN_ONE", "", "MONO_GEN_ONE.description", "ribbon_gen1", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_TWO_VICTORY: new ChallengeAchv("MONO_GEN_TWO", "", "MONO_GEN_TWO.description", "ribbon_gen2", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_THREE_VICTORY: new ChallengeAchv("MONO_GEN_THREE", "", "MONO_GEN_THREE.description", "ribbon_gen3", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_FOUR_VICTORY: new ChallengeAchv("MONO_GEN_FOUR", "", "MONO_GEN_FOUR.description", "ribbon_gen4", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_FIVE_VICTORY: new ChallengeAchv("MONO_GEN_FIVE", "", "MONO_GEN_FIVE.description", "ribbon_gen5", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_SIX_VICTORY: new ChallengeAchv("MONO_GEN_SIX", "", "MONO_GEN_SIX.description", "ribbon_gen6", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_SEVEN_VICTORY: new ChallengeAchv("MONO_GEN_SEVEN", "", "MONO_GEN_SEVEN.description", "ribbon_gen7", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_EIGHT_VICTORY: new ChallengeAchv("MONO_GEN_EIGHT", "", "MONO_GEN_EIGHT.description", "ribbon_gen8", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GEN_NINE_VICTORY: new ChallengeAchv("MONO_GEN_NINE", "", "MONO_GEN_NINE.description", "ribbon_gen9", 100, (c) => c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_NORMAL: new ChallengeAchv("MONO_NORMAL", "", "MONO_NORMAL.description", "silk_scarf", 100, (c) => c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FIGHTING: new ChallengeAchv("MONO_FIGHTING", "", "MONO_FIGHTING.description", "black_belt", 100, (c) => c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FLYING: new ChallengeAchv("MONO_FLYING", "", "MONO_FLYING.description", "sharp_beak", 100, (c) => c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_POISON: new ChallengeAchv("MONO_POISON", "", "MONO_POISON.description", "poison_barb", 100, (c) => c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GROUND: new ChallengeAchv("MONO_GROUND", "", "MONO_GROUND.description", "soft_sand", 100, (c) => c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ROCK: new ChallengeAchv("MONO_ROCK", "", "MONO_ROCK.description", "hard_stone", 100, (c) => c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_BUG: new ChallengeAchv("MONO_BUG", "", "MONO_BUG.description", "silver_powder", 100, (c) => c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GHOST: new ChallengeAchv("MONO_GHOST", "", "MONO_GHOST.description", "spell_tag", 100, (c) => c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_STEEL: new ChallengeAchv("MONO_STEEL", "", "MONO_STEEL.description", "metal_coat", 100, (c) => c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FIRE: new ChallengeAchv("MONO_FIRE", "", "MONO_FIRE.description", "charcoal", 100, (c) => c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_WATER: new ChallengeAchv("MONO_WATER", "", "MONO_WATER.description", "mystic_water", 100, (c) => c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_GRASS: new ChallengeAchv("MONO_GRASS", "", "MONO_GRASS.description", "miracle_seed", 100, (c) => c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ELECTRIC: new ChallengeAchv("MONO_ELECTRIC", "", "MONO_ELECTRIC.description", "magnet", 100, (c) => c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_PSYCHIC: new ChallengeAchv("MONO_PSYCHIC", "", "MONO_PSYCHIC.description", "twisted_spoon", 100, (c) => c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_ICE: new ChallengeAchv("MONO_ICE", "", "MONO_ICE.description", "never_melt_ice", 100, (c) => c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_DRAGON: new ChallengeAchv("MONO_DRAGON", "", "MONO_DRAGON.description", "dragon_fang", 100, (c) => c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_DARK: new ChallengeAchv("MONO_DARK", "", "MONO_DARK.description", "black_glasses", 100, (c) => c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c) => c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, (c) => c instanceof InverseBattleChallenge && c.value > 0), + FLIP_STATS: new ChallengeAchv("FLIP_STATS", "", "FLIP_STATS.description", "dubious_disc", 100, (c) => c instanceof FlipStatChallenge && c.value > 0), + FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.every(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; From c3641a370f66b26e1a2c6f3ed6a2d7d11ecd61b1 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:11:19 -0500 Subject: [PATCH 23/54] Add BW crit throw sound (#5131) --- public/audio/se/crit_throw.wav | Bin 0 -> 134444 bytes src/loading-scene.ts | 1 + src/phases/attempt-capture-phase.ts | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 public/audio/se/crit_throw.wav diff --git a/public/audio/se/crit_throw.wav b/public/audio/se/crit_throw.wav new file mode 100644 index 0000000000000000000000000000000000000000..a737410e7ce3857ea6369f1f828d555d476b5567 GIT binary patch literal 134444 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 { globalScene.field.moveBelow(this.pokeball as Phaser.GameObjects.GameObject, pokemon); }); From 188664f389aff4318c0a558339c8eebf2b7d10a6 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:25:08 -0800 Subject: [PATCH 24/54] [Sprite] Fix game not waiting for variant data to finish loading (#5130) Co-authored-by: Moka Co-authored-by: damocleas --- src/battle-scene.ts | 30 +++++++++++++++------------- src/data/pokemon-species.ts | 3 +-- src/field/mystery-encounter-intro.ts | 5 +++-- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index e9d5a97ab8d..9b578c1e977 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -363,28 +363,30 @@ export default class BattleScene extends SceneBase { /** * Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache} */ - loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) { + public async loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant): Promise { const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey); if (useExpSprite) { fileRoot = `exp/${fileRoot}`; } let variantConfig = variantData; - fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null); + fileRoot.split("/").map((p) => (variantConfig ? (variantConfig = variantConfig[p]) : null)); const variantSet = variantConfig as VariantSet; - if (variantSet && (variant !== undefined && variantSet[variant] === 1)) { - const populateVariantColors = (key: string): Promise => { - return new Promise(resolve => { - if (variantColorCache.hasOwnProperty(key)) { - return resolve(); - } - this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => { - variantColorCache[key] = c; + + return new Promise((resolve) => { + if (variantSet && variant !== undefined && variantSet[variant] === 1) { + if (variantColorCache.hasOwnProperty(spriteKey)) { + return resolve(); + } + this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`) + .then((res) => res.json()) + .then((c) => { + variantColorCache[spriteKey] = c; resolve(); }); - }); - }; - populateVariantColors(spriteKey); - } + } else { + resolve(); + } + }); } async preload() { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 285c2a70236..574c2a67f65 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -516,8 +516,7 @@ export abstract class PokemonSpeciesForm { globalScene.anims.get(spriteKey).frameRate = 10; } const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, ""); - globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant); - resolve(); + globalScene.loadPokemonVariantAssets(spriteKey, spritePath, variant).then(() => resolve()); }); if (startLoad) { if (!globalScene.load.isLoading()) { diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 4fce9b1dfc9..0110dabc7a9 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -215,11 +215,12 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con resolve(); } + const shinyPromises: Promise[] = []; this.spriteConfigs.forEach((config) => { if (config.isPokemon) { globalScene.loadPokemonAtlas(config.spriteKey, config.fileRoot); if (config.isShiny) { - globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant); + shinyPromises.push(globalScene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant)); } } else if (config.isItem) { globalScene.loadAtlas("items", ""); @@ -254,7 +255,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con return true; }); - resolve(); + Promise.all(shinyPromises).then(() => resolve()); }); if (!globalScene.load.isLoading()) { From 481616c2ad3cbd12ef8a7d6f603677efc9475732 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:25:50 -0500 Subject: [PATCH 25/54] [Sprite] Fix female Scizor variants not showing (#5097) Co-authored-by: damocleas --- .../images/pokemon/variant/_masterlist.json | 2 + .../pokemon/variant/back/female/212.json | 41 +++++++++++++++++++ public/images/pokemon/variant/female/212.json | 41 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 public/images/pokemon/variant/back/female/212.json create mode 100644 public/images/pokemon/variant/female/212.json diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index 11f8e9cb7ec..3c5aedf2084 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -869,6 +869,7 @@ "198": [0, 1, 1], "203": [0, 1, 1], "207": [0, 1, 1], + "212": [1, 1, 1], "215": [0, 1, 1], "217": [1, 1, 1], "229": [0, 1, 1], @@ -1778,6 +1779,7 @@ "198": [0, 1, 1], "203": [0, 1, 1], "207": [0, 1, 1], + "212": [1, 1, 1], "215": [0, 1, 1], "217": [1, 1, 1], "229": [0, 1, 1], diff --git a/public/images/pokemon/variant/back/female/212.json b/public/images/pokemon/variant/back/female/212.json new file mode 100644 index 00000000000..84f12bf1434 --- /dev/null +++ b/public/images/pokemon/variant/back/female/212.json @@ -0,0 +1,41 @@ +{ + "0": { + "632929": "215a2d", + "f76b6b": "8cce73", + "a52929": "2f794e", + "101010": "101010", + "d63a3a": "4a9c53", + "9494a5": "9494a5", + "ffffff": "ffffff", + "b5b5ce": "b5b5ce", + "3a3a4a": "3a3a4a", + "9c6b21": "9c6b21", + "dec510": "dec510" + }, + "1": { + "632929": "2f2962", + "f76b6b": "639cf7", + "a52929": "29429c", + "101010": "101010", + "d63a3a": "4263ef", + "9494a5": "6262a4", + "ffffff": "ffffff", + "b5b5ce": "b5b5ce", + "3a3a4a": "3c3c50", + "9c6b21": "131387", + "dec510": "10bdde" + }, + "2": { + "632929": "645117", + "f76b6b": "c59f29", + "a52929": "b88619", + "101010": "101010", + "d63a3a": "ffca2a", + "9494a5": "3c4543", + "ffffff": "ffffff", + "b5b5ce": "b5b5ce", + "3a3a4a": "282d2c", + "9c6b21": "9c6b21", + "dec510": "dec510" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/212.json b/public/images/pokemon/variant/female/212.json new file mode 100644 index 00000000000..55fcc0858ac --- /dev/null +++ b/public/images/pokemon/variant/female/212.json @@ -0,0 +1,41 @@ +{ + "0": { + "632929": "215a2d", + "f76b6b": "8cce73", + "101010": "101010", + "3a3a4a": "3a3a4a", + "ffffff": "ffffff", + "d63a3a": "4a9c53", + "b5b5ce": "b5b5ce", + "9494a5": "9494a5", + "a52929": "2f794e", + "dec510": "dec510", + "9c6b21": "9c6b21" + }, + "1": { + "632929": "2f2962", + "f76b6b": "639cf7", + "101010": "101010", + "3a3a4a": "3c3c50", + "ffffff": "ffffff", + "d63a3a": "4263ef", + "b5b5ce": "b5b5ce", + "9494a5": "6262a4", + "a52929": "29429c", + "dec510": "10bdde", + "9c6b21": "131387" + }, + "2": { + "632929": "645117", + "f76b6b": "c59f29", + "101010": "101010", + "3a3a4a": "282d2c", + "ffffff": "ffffff", + "d63a3a": "ffca2a", + "b5b5ce": "b5b5ce", + "9494a5": "3c4543", + "a52929": "b88619", + "dec510": "dec510", + "9c6b21": "9c6b21" + } +} \ No newline at end of file From ca0522436aff60e3229e2350c244660f233b8449 Mon Sep 17 00:00:00 2001 From: Scooom <97370685+Scoooom@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:50:19 -0600 Subject: [PATCH 26/54] [Challenge] Make the Flip Inverse Challenge Secret (#5133) Co-authored-by: Scooom --- src/system/achv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/system/achv.ts b/src/system/achv.ts index fb17e7b1ced..a2777101186 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -365,7 +365,7 @@ export const achvs = { FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, (c) => c instanceof InverseBattleChallenge && c.value > 0), FLIP_STATS: new ChallengeAchv("FLIP_STATS", "", "FLIP_STATS.description", "dubious_disc", 100, (c) => c instanceof FlipStatChallenge && c.value > 0), - FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.every(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), + FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.every(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)).setSecret(), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; From ae2c9071e456e58981199e6bb17009ea05b1f191 Mon Sep 17 00:00:00 2001 From: damocleas Date: Fri, 17 Jan 2025 17:40:40 -0500 Subject: [PATCH 27/54] [Balance] Update starters.ts, removed redundant starters (#5125) --- src/data/balance/starters.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index ec66401675b..ee33142e981 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -51,9 +51,7 @@ export const speciesStarterCosts = { [Species.SANDSHREW]: 2, [Species.NIDORAN_F]: 3, [Species.NIDORAN_M]: 3, - [Species.CLEFAIRY]: 3, [Species.VULPIX]: 3, - [Species.JIGGLYPUFF]: 2, [Species.ZUBAT]: 3, [Species.ODDISH]: 3, [Species.PARAS]: 2, @@ -84,22 +82,15 @@ export const speciesStarterCosts = { [Species.VOLTORB]: 2, [Species.EXEGGCUTE]: 3, [Species.CUBONE]: 3, - [Species.HITMONLEE]: 4, - [Species.HITMONCHAN]: 4, [Species.LICKITUNG]: 3, [Species.KOFFING]: 2, [Species.RHYHORN]: 4, - [Species.CHANSEY]: 3, [Species.TANGELA]: 3, [Species.KANGASKHAN]: 4, [Species.HORSEA]: 3, [Species.GOLDEEN]: 2, [Species.STARYU]: 3, - [Species.MR_MIME]: 3, [Species.SCYTHER]: 5, - [Species.JYNX]: 4, - [Species.ELECTABUZZ]: 4, - [Species.MAGMAR]: 4, [Species.PINSIR]: 4, [Species.TAUROS]: 4, [Species.MAGIKARP]: 4, @@ -110,7 +101,6 @@ export const speciesStarterCosts = { [Species.OMANYTE]: 3, [Species.KABUTO]: 3, [Species.AERODACTYL]: 5, - [Species.SNORLAX]: 5, [Species.ARTICUNO]: 5, [Species.ZAPDOS]: 6, [Species.MOLTRES]: 6, @@ -132,8 +122,6 @@ export const speciesStarterCosts = { [Species.TOGEPI]: 3, [Species.NATU]: 2, [Species.MAREEP]: 2, - [Species.MARILL]: 4, - [Species.SUDOWOODO]: 3, [Species.HOPPIP]: 2, [Species.AIPOM]: 2, [Species.SUNKERN]: 1, @@ -142,7 +130,6 @@ export const speciesStarterCosts = { [Species.MURKROW]: 3, [Species.MISDREAVUS]: 2, [Species.UNOWN]: 1, - [Species.WOBBUFFET]: 2, [Species.GIRAFARIG]: 3, [Species.PINECO]: 2, [Species.DUNSPARCE]: 3, @@ -158,7 +145,6 @@ export const speciesStarterCosts = { [Species.CORSOLA]: 2, [Species.REMORAID]: 2, [Species.DELIBIRD]: 2, - [Species.MANTINE]: 3, [Species.SKARMORY]: 4, [Species.HOUNDOUR]: 3, [Species.PHANPY]: 3, @@ -206,7 +192,6 @@ export const speciesStarterCosts = { [Species.MINUN]: 2, [Species.VOLBEAT]: 2, [Species.ILLUMISE]: 2, - [Species.ROSELIA]: 3, [Species.GULPIN]: 1, [Species.CARVANHA]: 3, [Species.WAILMER]: 2, @@ -232,7 +217,6 @@ export const speciesStarterCosts = { [Species.SHUPPET]: 2, [Species.DUSKULL]: 3, [Species.TROPIUS]: 3, - [Species.CHIMECHO]: 3, [Species.ABSOL]: 4, [Species.WYNAUT]: 2, [Species.SNORUNT]: 2, @@ -543,7 +527,6 @@ export const speciesStarterCosts = { [Species.GALAR_PONYTA]: 2, [Species.GALAR_SLOWPOKE]: 3, [Species.GALAR_FARFETCHD]: 3, - [Species.GALAR_MR_MIME]: 3, [Species.GALAR_ARTICUNO]: 6, [Species.GALAR_ZAPDOS]: 6, [Species.GALAR_MOLTRES]: 6, From ea3fbecfd33737108052bdf558c6b0c2fa5b2f72 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 18 Jan 2025 19:12:50 -0800 Subject: [PATCH 28/54] [Misc] Replace `globalScene` with `this` in `BattleScene` (#5142) --- src/battle-scene.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 9b578c1e977..65ec6a844ee 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -112,7 +112,7 @@ import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { BattlerTagType } from "#enums/battler-tag-type"; import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; import { StatusEffect } from "#enums/status-effect"; -import { globalScene, initGlobalScene } from "#app/global-scene"; +import { initGlobalScene } from "#app/global-scene"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -394,10 +394,10 @@ export default class BattleScene extends SceneBase { const originalRealInRange = Phaser.Math.RND.realInRange; Phaser.Math.RND.realInRange = function (min: number, max: number): number { const ret = originalRealInRange.apply(this, [ min, max ]); - const args = [ "RNG", ++globalScene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; - args.push(`seed: ${globalScene.rngSeedOverride || globalScene.waveSeed || globalScene.seed}`); - if (globalScene.rngOffset) { - args.push(`offset: ${globalScene.rngOffset}`); + const args = [ "RNG", ++this.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; + args.push(`seed: ${this.rngSeedOverride || this.waveSeed || this.seed}`); + if (this.rngOffset) { + args.push(`offset: ${this.rngOffset}`); } console.log(...args); return ret; @@ -410,7 +410,7 @@ export default class BattleScene extends SceneBase { } create() { - globalScene.scene.remove(LoadingScene.KEY); + this.scene.remove(LoadingScene.KEY); initGameSpeed.apply(this); this.inputController = new InputsController(); this.uiInputs = new UiInputs(this.inputController); @@ -2954,7 +2954,7 @@ export default class BattleScene extends SceneBase { */ applyShuffledModifiers(modifierType: Constructor, player: boolean = true, ...args: Parameters): T[] { let modifiers = (player ? this.modifiers : this.enemyModifiers).filter((m): m is T => m instanceof modifierType && m.shouldApply(...args)); - globalScene.executeWithSeedOffset(() => { + this.executeWithSeedOffset(() => { const shuffleModifiers = mods => { if (mods.length < 1) { return mods; @@ -2963,7 +2963,7 @@ export default class BattleScene extends SceneBase { return [ mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand)) ]; }; modifiers = shuffleModifiers(modifiers); - }, globalScene.currentBattle.turn << 4, globalScene.waveSeed); + }, this.currentBattle.turn << 4, this.waveSeed); return this.applyModifiersInternal(modifiers, player, args); } From 24f1fedcd0012a0457c3db4a7782595275580a74 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 18 Jan 2025 21:11:29 -0800 Subject: [PATCH 29/54] [i18n] Update locales submodule --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index acad8499a4c..7bfcbccb9b8 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit acad8499a4ca488a9871902de140f635235f309a +Subproject commit 7bfcbccb9b8192b1059ca7c4c7e7d24901cf579d From ab17df83c7edc173a4859ca3c1a69096bffa9c76 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 19 Jan 2025 09:13:48 -0800 Subject: [PATCH 30/54] [Misc] Disable enforced type-only imports in `overrides.ts` (#5154) --- src/overrides.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/overrides.ts b/src/overrides.ts index db54095a75a..1f8601b7659 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -1,19 +1,20 @@ +/* eslint-disable @typescript-eslint/consistent-type-imports */ import { type PokeballCounts } from "#app/battle-scene"; -import type { Gender } from "#app/data/gender"; -import type { Variant } from "#app/data/variant"; +import { Gender } from "#app/data/gender"; +import { Variant } from "#app/data/variant"; import { type ModifierOverride } from "#app/modifier/modifier-type"; -import type { Unlockables } from "#app/system/unlockables"; +import { Unlockables } from "#app/system/unlockables"; import { Abilities } from "#enums/abilities"; import { Biome } from "#enums/biome"; -import type { EggTier } from "#enums/egg-type"; -import type { Moves } from "#enums/moves"; -import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { EggTier } from "#enums/egg-type"; +import { Moves } from "#enums/moves"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokeballType } from "#enums/pokeball"; -import type { Species } from "#enums/species"; +import { Species } from "#enums/species"; import { StatusEffect } from "#enums/status-effect"; -import type { TimeOfDay } from "#enums/time-of-day"; -import type { VariantTier } from "#enums/variant-tier"; +import { TimeOfDay } from "#enums/time-of-day"; +import { VariantTier } from "#enums/variant-tier"; import { WeatherType } from "#enums/weather-type"; /** From 46e1268a64d658cfccf5a2a7ff00b45a2b9b2d60 Mon Sep 17 00:00:00 2001 From: Unicornpowerstar Date: Sun, 19 Jan 2025 18:26:59 +0100 Subject: [PATCH 31/54] [Sprite][Color fix] 3 Venusaur and all forms (#5153) --- public/images/pokemon/3-gigantamax.png | Bin 1693 -> 1771 bytes public/images/pokemon/3-mega.png | Bin 1681 -> 1754 bytes public/images/pokemon/3.png | Bin 25756 -> 34719 bytes public/images/pokemon/back/3-gigantamax.png | Bin 1536 -> 1606 bytes public/images/pokemon/back/3-mega.png | Bin 1378 -> 1455 bytes public/images/pokemon/back/3.png | Bin 23970 -> 30200 bytes public/images/pokemon/back/female/3.png | Bin 23973 -> 29984 bytes .../pokemon/back/shiny/3-gigantamax.png | Bin 1539 -> 1588 bytes public/images/pokemon/back/shiny/3.png | Bin 24018 -> 29975 bytes public/images/pokemon/back/shiny/female/3.png | Bin 24018 -> 29941 bytes public/images/pokemon/female/3.png | Bin 25751 -> 34777 bytes public/images/pokemon/shiny/3-gigantamax.png | Bin 1693 -> 1734 bytes public/images/pokemon/shiny/3.png | Bin 25834 -> 34560 bytes public/images/pokemon/shiny/female/3.png | Bin 25818 -> 34586 bytes .../images/pokemon/variant/3-gigantamax.json | 12 +- public/images/pokemon/variant/3-mega_2.png | Bin 1753 -> 1775 bytes public/images/pokemon/variant/3-mega_3.png | Bin 1758 -> 1781 bytes public/images/pokemon/variant/3.json | 4 + .../images/pokemon/variant/_masterlist.json | 2 +- .../pokemon/variant/back/3-gigantamax.json | 18 +- .../images/pokemon/variant/back/3-mega_2.png | Bin 1449 -> 1470 bytes .../images/pokemon/variant/back/3-mega_3.png | Bin 1441 -> 1471 bytes public/images/pokemon/variant/back/3.json | 6 +- .../images/pokemon/variant/back/female/3.json | 4 + public/images/pokemon/variant/female/3.json | 30 +- public/images/pokemon/variant/female/3_2.json | 4136 ----------------- public/images/pokemon/variant/female/3_2.png | Bin 34388 -> 0 bytes 27 files changed, 61 insertions(+), 4151 deletions(-) delete mode 100644 public/images/pokemon/variant/female/3_2.json delete mode 100644 public/images/pokemon/variant/female/3_2.png diff --git a/public/images/pokemon/3-gigantamax.png b/public/images/pokemon/3-gigantamax.png index 4036c641327c02aab712918a477c233a5d06290e..d551638c8803dc40c0bf7e01e79c5f385bbdfbb7 100644 GIT binary patch delta 1747 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 O0000#(W01580cY^=`01|XkPE!E? z|NsC0|NsC0|NsC0|NsC0UcrVw000IwNkl0;w5422^Ryw!2w{a^O#NMiAl zw4L7hHPysvg5l$E5SYyE_BOJ_*NeX#()=Oel*uoYd+{IAbKbV?VIjn6ne@6%*$d_N zpRkpGtLXKy3UMhRpVt+IcK_S#Y}AJ`7FznC)zBt;`yRRsSlUZ0PoV4dTs0J}hJOp3 zOFWb;XsXvUx4Yj&*Crxb0n2llp3kRVGJq!8@Lwe>b}Mhy>jdPb+?fojskJp0X?yYhMLU9U9etKV@ggDcsI){kHVq3%m~S6Lj`Wm3BL< zvhsQ;sO*Qf`9w0nM7I<7K@&~uiLGPw74;6CH5Ev;H$BFfh-oV9uG~tnrrwZy@Y-1j z?qpSSEp9Xv2VBj4|LVmlCsYOt&3fox2Q)e&1kLBnL_&T0#~)eCYk9O9`v6fnwvGTBY5 zZ641i4_}OIMTKkRq2k4HB(t7n^d+AsgJ>12j=Gix%0^$B8FSI=+MPOB78)5Q+snyE zbj1Lj)MEu2)?Y*M!u-Or>yJqC)$)I?=+s7>fL1n>7TgQ}6_*4F$2L zL>so@&&ljCD&cd|tS_t8QpfFo(0{fkk#h^WAM@N`J95pMwoJ_q%a6&(x03&O-?=%c z8<;}!E?~~h#U{s+KEe@Xc2Esj1I1df&`J!TsylgeWTC+lbyV5w%ML-=3p})uCPH1j zJF=pcvfKO&B2qgtF}`-;gROjVKn?I@yV$K=TGj_pA(MV5S8F}foGMj+*TKnv1J@K~ zJBH>$*1vk78!gCG7qVn{s+ryU*mJOZvXazqM zEnLn)ALTP~s&=Q>TzQB}?bXmH*>)fjFtdyynCEO{nmvcF!PwO0GIY*^2`H08r)S0sbH$rqT7OCu_o zKenE%Ngd%~?>9$(zMZ{rvWf;&E|iprTI8`;Ny28S}2$P{@yfkQ*rV3*rxnsGT)^2kPZ#EBfnct$gyp;d4o z_kv?VpTQuP6z#zA63ix;s1;#ZxnI{d8#-2S3|BtL{GPafP7YUYw1N3Ewhgpb%aehv zAQY~WHSJFi6-U@?xKiH1L7l=hDcA}zH)ACSSwvym&PF#Cku2L{-83O~S;7=HRwulE zaIpGJLIwGVKJ2`_g*JQ4cECj$rmmCXf+h9kP`7OtzrR^WQWx$`RBvJNhjaN~T;ozV z%CVABdUYs&;H{E^Eud@7MlY2?N1^G=Hjuuy(3%8eS7NS)IeqeOuUCvuKgr1H(=t=MH>GgFf~3qFaNLShaGJHKeV#{ k7Wp@R<2QceACCV3GQf_Zo(euK00000NkvXXu0mh|f+C_crT_o{ delta 1616 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 diff --git a/public/images/pokemon/back/3-gigantamax.png b/public/images/pokemon/back/3-gigantamax.png index a368c97741eedaa1b70ca7d3cae1e7547f5209b3..278cb58b7657aa7328ff146bed381776c7ace6b1 100644 GIT binary patch delta 1555 zcmV+u2JHEO48{zQ7#auz0001;3{zYH0004VQb$4nuFf3k0000vktH#Ig8&IGRT=1` zqn4+wH2akg0000JbW%=J0RR90|NsC0|NsC0|NsC0|Ns9!Kin+<00o>$L_t(&f$f&t zqN6Gdh8xd1w$Szd54&@O0NQqq-FK6jWkumj@+Z*VZlBn+*xmAK+TYsrSjwB_t8S)E zu==?ykMH0Yr7q0HPXgnAM;lVlGmbjqn@ia5;q(_cr0* z#&_(2b9g*1;z<~Zo1oJs#;ypC*DG}H6T)-6(_<0(gZwP$w+q$zF}-GQ03h*%p9lB*UEyTwuN_d*+55Wereyfk|0KwEQrhhaR(JP&-U6?BH00PwcpGK8r6Br<<|&@aE*pSThJ5G+d}#1{IF_dEy;t}km$0ri zUinZhY3St`%4Z?-vxiXPUusjilNN@0=zgM<_)xb^jj!qsnVP`$lB`an&06n)L%cl% zP7b}v4>t2D{rUww5IAp3b|Irf{0fz~`t=yn6=61i1Ty;p3X%uNmk1QN8eeNa(qg=7 zpbQ?zG2%aS0UDQliPU)4E1_@~6wFk90C7~qn@DcNhx8NgDGN`E!wW-5l|q868wUz@ zCAj0q8RSmH2_Ppi=V`yqfEM~n#LAlIX8G`m-=FK9ijCnY- zN)C_o7<22o;Ilr0hztTxNHGhBa5m(wp=?lQ{4{w84`+PqobtWL&v5|GN1Bq=MJJ4pQM)I11k`vi z-!XXy(}QIg`c4vvA`Uh7jrll_3!ohbORN4o zT#8z~rec!Lh^IK@nBefWsx<9(TGx`!e45d9eJ*AkVmE*(rqV#FYTk^EFg0DhccPgU zGY&%!DkQN{!vSuNuMR|*&bNGpXa6<APV-b963(->NQrz|?iOOHaGE)axLB=R(5M)1H#KmFAP~T?K#-zsO%iafa$8>&$p? zG0~eA?}N?^J-*p0I=`h&L1%kj-L-V$7yh#Y$Xl+VGoCU)Kw5r+@2mWh2S}x9_8jn0 z&NN$%pIt*2^}TgcardC}Q#zMrf#;cT@irs>WGH3~gKJ-z>D2$z^1G7%E`W?~hvs$n zr{&j0dzW9=Otj|g+>vu8{Q>@VpnX@&Ga287_8zZc9?UwlD)Ym2?Rk1U?{ID2NT)$H z{O;#te(J%GVi5cJ`33*O7z**fOIHscB>K<(*+2V#>_38NpmIqh0zd!&002ovPDHLk FV1j=w6`ueA delta 1485 zcmV;;1v2`^41f%f7!3pi00013{JvcP001qKGBJM+-P!v90004WQchC|=cvo_1sCu zgJ6!U{GBtW{&~oc@ic}X=z^J)I(02+SPxK z(mHCuKG;hfz^bqb8}eKGx=D$<92abQ5JvVsW9UByRE&_6N$wT&j(PbZw4MgDj7J9#f z6aS}Az75Cc=rwR5BFC@_a(G4KWbb|Ni`6zy|1^3}Fh}h_4}O+~IP;?S|JK=Ul-`@^Ip->?|DoRPoEs|Ay*eGEo3>My*C8mA&n~?fpzng;a#6Pxtqn zAwX3$p5D(CGnw-wVW5}2eWHK3f1Ex&ObC#-b5{JjrB%WFFU#A60GZpTd-9Ku|Ik9` znU4xpSyEu952XQhu$hHGF0U0CKW$^|_?HkDosG)Xzt9AAo;A1d8^Fo`wud zU#JtxlugDc{wq7Gm{uP{2#YN1+hgBS^*&>HN5|8BDMY0z=<02yBR8^d-ml!ymJr8Y zDK(T8Jeib;vj@+$n#YZO&V~LKMVY1;X6tD#aAsJfxq&xD{=DTW%) zJ~@E8;Vt=x#4|&6WAdgeI&dE%4aZ!msS=^I@(Bj$8+@9f$+HwtGe93<%@@7pr;f~UMYl!29 zusGT1#a`)4PwjxBblD=VNk6e56rUKPh=n|I)8%HqO=xbyCp5afi! z2NSPQeaCmjG%zS`@`y;@v0BYqUN!w4;_cQeNe2*`4^M^oo>9vyM6+hcKETT>c}Z8T zmI4{AN4teNaKL|z*#UXZFYkoP2PPv$2VITPe(c4&S+#J%_QA}z3$%RmW>lx6U)tTu z12liy{L+hG!ujF5c2x>Ml&#!JP4*mn^s)kQ>cGo;DM~>4eSQ=;>AOD^6yFoj;=do) z?+;$CSb(D!cuebqZzHVwcBeq_Z(cGoZ#nw$IMT+B>Etz{;P;W{pN|C|?fp3ccmLy? ndRu^N%Ave59dOh?o&Wg>tG0v9ss!P_00000NkvXXu0mjf=Z@|u diff --git a/public/images/pokemon/back/3-mega.png b/public/images/pokemon/back/3-mega.png index 0c8512cb91bc87955664e8f9f6209ced2b32cf9d..5f4986de090e971f718e78b1102b4e217a8a964f 100644 GIT binary patch delta 1394 zcmV-&1&#XR3a<;0Bn|;{Qb$4nuFf3k0000pktHsFg8&IGRT%&P05JuR*8l(j5p+^c zQvm<}|NsC0|NsC0|NsC0|NjsbK%oEt1r13=K~z|U?U&h_sxT0Q>3|4`$@{FOmL zh&tc7v_~Dx@~67eQ5=TLh=}v%>xplv4}@a;Iau?b;W6&}{;*i%5bZfcYvb;}u!ZaB zjfty&2VZZSg8#yfdl9^CL~9+iZo*djcW6&o!5ckmG(ADy@_=v9296%|WV8t97k(q; z-1`kWDzp~8Mxj5Saph}77X3wy6sUnW=zKDjpL`*bi_rrAjO!a;?I1lBiV(rBc()RI zk7n+^R|WTADygfEb(3rM_7?v5>%M6Tq2SXD)a1I*o*VCHOsHtwSdid_T4IU!m3 zaZTn?o1oR)ma@^E{~qUT$?1|T;F_BcINk3Q7~Qo)D@mSrac+ce5U?#?_ppnchF(asd z9a=*@`c2jGfF$6IuEA?TmwppNuO%mrnP`?Hhi!ZuB;U0jR zqouUvbj2h#2hkj0bdv0x3-l~;q*qwhtOeo)$feEgTpA1pd1cKL<@JrMnrFr|@Fdt} zA;N{g>!5jN3jh@~U&6LR#j)Zc{E{?(^#UiXwIp!oS>Sn&WeUaNu4Hx_iIWW=L(U4H z9j;_bvvPGo8G{r}av}k){D&1nkRj-b;+0_446qkvo9d)o^y+5J*%T-W1S?ioEdr$r zB)k!z$5<@`VPh?+jK%eucB7&nr$WduSszC3(HR4@SV00*tUYG;!ghga^_>jA z)%ssDI+ctQ(84BCH)h8w8rYyAqHFM7oL*FTG9@yxq>mj;Suf!l7Lj~pTKJ6;?vQYg zq+wh~*Euf*){w=5P=Wm@>T0%sCVBniwQ+M6H$DU+s5S=BSt4jTR^dRF7B%G*#T*@ zVr#z%lsn<2e3aH7Pn)XMtn{qS8J>7zXFvuk7n;qCW2hgFE@ZA?NK$rx_su2aO|#6b zZsKaP^vXkW&$?;SnG=oFbEn{3_+0M23p_^e77Q&EQnowkY#PTJ?Lu8pb90{W?3`QB z)0FI617x~kb6YY^mW$jG+-nXB=-D z)R2b)=5ZPAUgrgPccwOfAkiX|d+>q0#e;UDEVq_DhK3v}GU+(dqT$pCcim$q#|9kQ zT?xJDrkYXTTIM#2;#h%4pd%c#r1Aq*GKL1-AC$3BJu31TqxO=6^AjyKdF;qNRl<); zY+6n4%~vR7u*VZ}=)uB9^ts4~(x`%oMRwgiIo!@Eh#P>FC@y8;hcgAF~wp3FP0enP(ZS^Rq5RsD>f{0Cf3sGjv^aW?3D&`UU7IM#{g zO86Z8cIDRO{{|lFto|$Z7r*$$FMjchUwjn*0F$0_8RNPP-T(jq07*qoM6N<$f&^=} AP5=M^ delta 1317 zcmV+=1={+r3*ripBmpXsGA@78)CR8r0004WQchCyoN4 z5QOOoG8pmwU-x8gxd5VWou5vr#ggPpPiKO0r|B|S;{5jX_mJTa3B$yD^4ougGjH4W z@VN8D{+`(HmFB;&l^f_>i1&zcowI@e!se|B&KbD(fqPcA(Z54S!UliNc=l*Kg`8^# z{vON`cowt@FCXO(CD+z(&}`6N^qrOdeDb|qOH|Qc)JTImIE&6_p_X^KP|3|`g@4A4 zh3{4nPlHOR;M_`2yYeU8ZR-l|0RZM|V8i5|o!_FX-Yu}r66QN|R}~d5;L>~vE}X1r z=RSr2?`{-0kagzAHMxKE%8E8~M@k0cfDX3ibV*il&#fCA%pfp!_eCqTR<6k2qi`p5 z3m;%5v$LZqe}qQPxsD!>PiPva7FjvdqcN&7lYT5iHH_p(Jd#-_&9$^W;a?yTeB}6sa5m> zyRPe^Ym}ozBx6~7QkzLN69XX4F1UhTG>-HRiv?C7QGhu^7mkVULcTL&MxEcurg?fxBSrRW1>g5nLlL74MHBJtiP7CP?8f8aEFC+ z1KsDmHrPN$Je~p^ThV6oIT7_QpM#r=x}o}f3M~|LCaBtu>>FFzT5@5nu*8s+W>npz zTTlcb7L0#ro}H@?NDeT^jx*h6NXS-ud-59l8}+fL-16Z>4l?@$hL!gkt{Q(O5LV6J zcgpZM749q_oX4orMXhCJdu^$38Mrf`f{iPh-Hc$|U%{sC>#Pwut z)tBa8bz`=XGZOaPnSv`mx3hPHGe@&xuv&$9+1Y<+q|~EBXb2h(=j~)S*Kl;X@NW$v z<38FPn@Rl(}zMs$jx_uAVE&usgijB)3_?M7d;;7Afupu zX@GxI2Tp=!anO^hFR7A51A4zvQlmz6P2ylJNot~U_ZP(NyVx5{+h=_MQ&j_pL#wqg2DF_sJP-@rpw{}ua-U;N@1zxWx& bFFuNYqqt#rzg`cT00000NkvXXu0mjfnp=Gs diff --git a/public/images/pokemon/back/3.png b/public/images/pokemon/back/3.png index 4312bfbce2c55914f3ba726105876b219206282d..9f833c7d122ad9f7dd6c7ef083131353473ea8d9 100644 GIT binary patch literal 30200 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 AOaK4? diff --git a/public/images/pokemon/back/female/3.png b/public/images/pokemon/back/female/3.png index 0fdbf4e9be2e3e9cfe1d4a83461f820f60703845..ef103db8ce07bd8723c9aae3d814388ace27e932 100644 GIT binary patch literal 29984 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 VMY)t{coP5s002ovPDHLkV1nhBzajtt literal 23973 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�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 delta 1488 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 VFm9;)oQVJc002ovPDHLkV1kcL+q3`x literal 24018 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 diff --git a/public/images/pokemon/variant/3-gigantamax.json b/public/images/pokemon/variant/3-gigantamax.json index 7b341b367aa..9b74b1131ba 100644 --- a/public/images/pokemon/variant/3-gigantamax.json +++ b/public/images/pokemon/variant/3-gigantamax.json @@ -4,6 +4,8 @@ "ffee52": "37d6de", "debd29": "078a8f", "833100": "002112", + "830009": "23033b", + "189d87": "c2247b", "ff7b73": "712f8f", "de4141": "3f1375", "ffbdbd": "a266b0", @@ -11,6 +13,7 @@ "107b6a": "9e1976", "105241": "4f2800", "83de7b": "a37707", + "2e5529": "38001c", "5a9c39": "705207", "20b49c": "de3592", "fdfdfd": "fdfdfd", @@ -21,14 +24,17 @@ "ffee52": "f75ea8", "debd29": "a30a66", "833100": "0b2e01", + "830009": "154205", + "189d87": "f17f05", "ff7b73": "9db042", "de4141": "3c8227", "ffbdbd": "e7e385", "101010": "101010", "107b6a": "d44300", - "105241": "030129", - "83de7b": "433d99", - "5a9c39": "19164f", + "105241": "381601", + "83de7b": "80ced9", + "2e5519": "011c38", + "5a9c39": "446b94", "20b49c": "fa8405", "fdfdfd": "fdfdfd", "5ad5c5": "faa405" diff --git a/public/images/pokemon/variant/3-mega_2.png b/public/images/pokemon/variant/3-mega_2.png index ddc2bd976ad258902f12933c03b341676e8df0b0..54ca687d1c3ac6da1ec8f2d3774650158c985e55 100644 GIT binary patch delta 1689 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 diff --git a/public/images/pokemon/variant/3-mega_3.png b/public/images/pokemon/variant/3-mega_3.png index 56945d383e6929361b1c0508b53e2fbd4d27635c..07ddb5ae7ede614fce33721327e3472203762d19 100644 GIT binary patch delta 1692 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 delta 1366 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 delta 1361 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�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 Date: Sun, 19 Jan 2025 18:50:19 +0100 Subject: [PATCH 32/54] [Sprite] Update Shelly sprite (#5155) Co-authored-by: damocleas --- public/images/trainer/shelly.png | Bin 865 -> 985 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/trainer/shelly.png b/public/images/trainer/shelly.png index 505dce1b1105c144b0fe43d26705340cd9575580..2885fbde48e6d99a641834e0237c03724540a9ff 100644 GIT binary patch delta 885 zcmV-*1B(3N2H6LYBnkm@Qb$4nuFf3kks(ok7IachQvm<}|NsC0|NsC0|NsC0|NsC0 z|Ns9~fsLa800SdQL_t(oh3(kcZrd;r1yEnET5Zc}|NmEeXGpC^5s^uOK6rp24gnnx zml;X9-9GV&PkiFP3;eV<{*ag%%o_-aYA-t*2#gZ7B<2-V5qP}Dv7W@7Gm{S_B)e6A ztXNet3{6E+9HHE6d_2m*C9gTBrXK_(^t#`R#k_V&TmlY)JKVEa1g1SO<^Vd#PuCTL z&u zAO?b#?KFpo!K~e}OcamceVRnc5v zt%qu!3+60fzz~8QooTuc=L8{tJ9phvk!P8^5G2XrP}d{oqUHoNO&w+fNP zD;^-EJ%hL62RcZj3Jr6~;C@G{9=jG$#4}hSYCE+-TepIzBU22T)Sc4<6GgN*oay2% z&b|5ePe6(a+I*j>5shzLvv>m9^bE0qg`)9nr7Os%L@uZGMvAQ}Mb4Oi8JfcY_dN}y zJ8Y>ZaRm90xD1q530ypbcpE7KoAc#x={xE`(^AhBTLn$?!N<@>I64%f3Y^% zr9ak+M=#^$~kMY~Z6q}|_G>x@~Zn%Qdv}-_Z*XN>OOSDd3PD;Jy zjpQB7Z>6Q>2YDZf?_ho*t*B=l?x8zU&Fe2GJMpjoEh~7}`A_2y)IvFnm9HsN00000 LNkvXXu0mjfs%5Uh delta 766 zcmVgB#|*re*tt-PE!Ct=GbNc00PZPL_t(|UhUP{j^jEE1W*@AEnc$U z`~SZ>B}R4C|Sp717&KtU-^*04^56x$EI{Y6tNE3BhRx30;rJe^!xH6`un`!3{@> z&7fZhA}^q!Jk!(+!Y>F3ApqD!5i=1Tnd|j*Y6MA&QgjpZJj2P+^nZdlvE`}BV?+_| zNi-&kq~yPmq8<1m(F#Hp8|dO#Guh$V2P$wE(+g;ICOx8naWVn(IMssvpyN&|Gmc)> z(6}G4fva7`nc@n5f4d`G+`!Fq5xnsXGvK=$hZay6?7&K5o^LlXl8i~qb@B8HQ&zgDD*l+ zdJY~hmmbcb+BDn_?Qup8hw0a>F5W@;?-qx)Xr1${) zMFd78JqK5>e;<_KB8Dnr1i@~xdV${WgDyJgBKkxL*)qKiRC)?H*dt0XGlDgcKrzSw z3|$o^l!o$hfI!goLZZ?0yn+F^gY91lEbT$8CtX3_gD^||pqRaZIsZVE`s1`sMU1In zf5Bw-1m>JqvA^I0^ndt%kX^iB#$zTch#oMi5_GQyblG_2E{}mSzWsMo{wcV5>yoYe zv0HovqcT4N@%351i1)L8Fy~D0P0YDZl&or5z5rEG$&Pg$WPcjem}au>zOO(}O4163 w#Jq!WEqV1;(S!ZDxGD5n1J9T9BKL3S4+3R5 Date: Sun, 19 Jan 2025 18:52:50 +0100 Subject: [PATCH 33/54] [Move] Improve implementation of Rage Fist damage increase (#5129) * implementation of rage fist * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/moves/rage_fist.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * added removed TODO from some test cases * Apply suggestions from code review Added changes to documentation and cleaning up code Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * added protected to updateHitReceivedCount() --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/data/custom-pokemon-data.ts | 10 +- src/data/move.ts | 28 ++++- src/field/pokemon.ts | 3 + src/phases/encounter-phase.ts | 6 + src/phases/new-biome-encounter-phase.ts | 1 + src/test/moves/rage_fist.test.ts | 143 ++++++++++++++++++++++++ 6 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 src/test/moves/rage_fist.test.ts diff --git a/src/data/custom-pokemon-data.ts b/src/data/custom-pokemon-data.ts index 1c3bbbc3180..4a5eb89aeed 100644 --- a/src/data/custom-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -5,7 +5,8 @@ import type { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species - * Currently only used by Mystery Encounters and Mints. + * Used by Mystery Encounters and Mints + * Also used as a counter how often a Pokemon got hit until new arena encounter */ export class CustomPokemonData { public spriteScale: number; @@ -13,6 +14,8 @@ export class CustomPokemonData { public passive: Abilities | -1; public nature: Nature | -1; public types: Type[]; + /** `hitsReceivedCount` aka `hitsRecCount` saves how often the pokemon got hit until a new arena encounter (used for Rage Fist) */ + public hitsRecCount: number; constructor(data?: CustomPokemonData | Partial) { if (!isNullOrUndefined(data)) { @@ -24,5 +27,10 @@ export class CustomPokemonData { this.passive = this.passive ?? -1; this.nature = this.nature ?? -1; this.types = this.types ?? []; + this.hitsRecCount = this.hitsRecCount ?? 0; + } + + resetHitReceivedCount(): void { + this.hitsRecCount = 0; } } diff --git a/src/data/move.ts b/src/data/move.ts index 572fbf4c2ac..06f3c85e9c4 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3993,12 +3993,32 @@ export class FriendshipPowerAttr extends VariablePowerAttr { } } -export class HitCountPowerAttr extends VariablePowerAttr { +/** + * This Attribute calculates the current power of {@linkcode Moves.RAGE_FIST}. + * The counter for power calculation does not reset on every wave but on every new arena encounter + */ +export class RageFistPowerAttr extends VariablePowerAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - (args[0] as Utils.NumberHolder).value += Math.min(user.battleData.hitCount, 6) * 50; + const { hitCount, prevHitCount } = user.battleData; + const basePower: Utils.NumberHolder = args[0]; + + this.updateHitReceivedCount(user, hitCount, prevHitCount); + + basePower.value = 50 + (Math.min(user.customPokemonData.hitsRecCount, 6) * 50); return true; } + + /** + * Updates the number of hits the Pokemon has taken in battle + * @param user Pokemon calling Rage Fist + * @param hitCount The number of received hits this battle + * @param previousHitCount The number of received hits this battle since last time Rage Fist was used + */ + protected updateHitReceivedCount(user: Pokemon, hitCount: number, previousHitCount: number): void { + user.customPokemonData.hitsRecCount += (hitCount - previousHitCount); + user.battleData.prevHitCount = hitCount; + } } /** @@ -10991,8 +11011,8 @@ export function initMoves() { new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9) .attr(MultiHitAttr, MultiHitType._2), new AttackMove(Moves.RAGE_FIST, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9) - .partial() // Counter resets every wave instead of on arena reset - .attr(HitCountPowerAttr) + .edgeCase() // Counter incorrectly increases on confusion self-hits + .attr(RageFistPowerAttr) .punchingMove(), new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9) .attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 432f0a92fec..a833facd2f8 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5282,7 +5282,10 @@ export class PokemonSummonData { } export class PokemonBattleData { + /** counts the hits the pokemon received */ public hitCount: number = 0; + /** used for {@linkcode Moves.RAGE_FIST} in order to save hit Counts received before Rage Fist is applied */ + public prevHitCount: number = 0; public endured: boolean = false; public berriesEaten: BerryType[] = []; public abilitiesApplied: Abilities[] = []; diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 6dae7dff8f9..353dd6681cb 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -104,6 +104,12 @@ export class EncounterPhase extends BattlePhase { } if (!this.loaded) { if (battle.battleType === BattleType.TRAINER) { + //resets hitRecCount during Trainer ecnounter + for (const pokemon of globalScene.getPlayerParty()) { + if (pokemon) { + pokemon.customPokemonData.resetHitReceivedCount(); + } + } battle.enemyParty[e] = battle.trainer?.genPartyMember(e)!; // TODO:: is the bang correct here? } else { let enemySpecies = globalScene.randomSpecies(battle.waveIndex, level, true); diff --git a/src/phases/new-biome-encounter-phase.ts b/src/phases/new-biome-encounter-phase.ts index be6815333e5..2de9a4300c5 100644 --- a/src/phases/new-biome-encounter-phase.ts +++ b/src/phases/new-biome-encounter-phase.ts @@ -14,6 +14,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { for (const pokemon of globalScene.getPlayerParty()) { if (pokemon) { pokemon.resetBattleData(); + pokemon.customPokemonData.resetHitReceivedCount(); } } diff --git a/src/test/moves/rage_fist.test.ts b/src/test/moves/rage_fist.test.ts new file mode 100644 index 00000000000..a85be5a88d9 --- /dev/null +++ b/src/test/moves/rage_fist.test.ts @@ -0,0 +1,143 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { allMoves } from "#app/data/move"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Rage Fist", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + const move = allMoves[Moves.RAGE_FIST]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .moveset([ Moves.RAGE_FIST, Moves.SPLASH, Moves.SUBSTITUTE ]) + .startingLevel(100) + .enemyLevel(1) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.DOUBLE_KICK); + + vi.spyOn(move, "calculateBattlePower"); + }); + + it("should have 100 more power if hit twice before calling Rage Fist", async () => { + game.override + .enemySpecies(Species.MAGIKARP); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + it("should maintain its power during next battle if it is within the same arena encounter", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(1); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(250); + }); + + it("should reset the hitRecCounter if we enter new trainer battle", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(4); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextWave(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + it("should not increase the hitCounter if Substitute is hit", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(4); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.SUBSTITUTE); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to("MoveEffectPhase"); + + expect(game.scene.getPlayerPokemon()?.customPokemonData.hitsRecCount).toBe(0); + }); + + it("should reset the hitRecCounter if we enter new biome", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(10); + + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + game.move.select(Moves.RAGE_FIST); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.phaseInterceptor.to("BerryPhase", false); + + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); + + it("should not reset the hitRecCounter if switched out", async () => { + game.override + .enemySpecies(Species.MAGIKARP) + .startingWave(1) + .enemyMoveset(Moves.TACKLE); + + await game.classicMode.startBattle([ Species.CHARIZARD, Species.BLASTOISE ]); + + game.move.select(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + game.move.select(Moves.RAGE_FIST); + await game.phaseInterceptor.to("MoveEndPhase"); + + expect(game.scene.getPlayerParty()[0].species.speciesId).toBe(Species.CHARIZARD); + expect(move.calculateBattlePower).toHaveLastReturnedWith(150); + }); +}); From 05485aefdd3b6b439a38b0af17c8ac160ec7219b Mon Sep 17 00:00:00 2001 From: Unicornpowerstar Date: Sun, 19 Jan 2025 19:15:33 +0100 Subject: [PATCH 34/54] [Sprite] Flutter mane back sprite adjustments (#5057) Co-authored-by: damocleas --- public/images/pokemon/back/987.png | Bin 712 -> 748 bytes public/images/pokemon/back/shiny/987.png | Bin 713 -> 748 bytes public/images/pokemon/exp/back/987.png | Bin 2260 -> 2367 bytes public/images/pokemon/exp/back/shiny/987.png | Bin 2179 -> 2373 bytes public/images/pokemon/variant/back/987.json | 6 +++--- .../images/pokemon/variant/exp/back/987.json | 6 +++--- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/images/pokemon/back/987.png b/public/images/pokemon/back/987.png index 64a47fbf0ea70325c44db3510f11fd7b4fd575f6..ab4e628de28565fbd8bb625b0d3ffccc41e0caa4 100644 GIT binary patch delta 700 zcmV;t0z>`C1?&Zo7#auz0000hQ`;K=0004VQb$4nuFf3k0000dktHa9RE~%OSBm2R z000eiQchC<|NsC0|NsC0|NsC0Ed)WW00077Nkl$a;P41`Sr3n+d6=RKK_ zpw*tbxBa=D)yt}ekI4vhSq>`4OK()l$GXx#-*-gs{ZpCU-xcH?qeE?1R3K&^zo_py z`H{E9hG?&l;jVK^X@IDIp-KY!N)Z`|J){q@q@=IF$n*K+EoE>}VUT^#L{xNGV}TvT zkd`!1p0JbiLiO<&c2FSRkYr{k*-VX)!|TGcWTu%h$%;{bM9pGPP%n##EJ0fk zTM{_TgCc&#gGsf50g+w}2rP}KJ2@7vxR)dx9B@+z_t+?G>4>G==T`7^M=<2(m+3zk zRzPqo5+UnTCz=hZMnhVYoKuQO!N)2F#woUL=&)=Q#y*pB&?ONj!-b$Rdwk~Q&Bk?8 zJ}bJUu?7ggdZTH7#@B?k^o~R95Hm3;*jYvs_JAW^;~cso>k5z(kfH;f0w>{i#A}Hj zdm2&OiDQ27Jt+DO3I~PxUA8+Akuxw@F-|c%)64ncN`@Fil@eA4ha;CW4h3RhwiJtp z*uy`wvrG&*WC?S#?{ml&6Jw3@lcB3}U7K3R{%HOeN?sX%d|v+_MZi=k*Y6lu_&CEa zVRZQ1!a`JQJ|A;r*qQlY7qQsupb0=bPcm$rIDKM z!>l`4thGxNot}If1?&RG9xQDR19(;GXw{HCxE&T(agcjEXml5j#0000%-okU&znImNjSxmHy>wTGfN_3POW`j z)k+owgYTB(@n!EOv(|=^`Pj0-N9UVC3x*kXfi4p-ET@{eXV^HP2@E!~Nm4ri!U-T| zrYL1jjB};j43dQF8PL(y%Nzh9?M=U&hN6y4@XEd}=CQkC?+g))xy77&gezRQ*tvf> z7wZTnVyM3R3n`G)mENSc>p2@0p$EoQ&9y)#9G6q)+*HFD^2LVxb1B7r{J%PNW$w+C zNO?oeXo=DR x4t8amkpf0_#et|$iNhv1XdwQ#Jmet{`3){_x=B+S(u4p2002ovPDHLkV1n|CIc@*| diff --git a/public/images/pokemon/back/shiny/987.png b/public/images/pokemon/back/shiny/987.png index 8022d2f900a6beeb514bb881d0de2fbccd6fd28b..8c4c0c8b62a8a2656833ad7e9eb650696aedec01 100644 GIT binary patch delta 736 zcmV<60w4X!1?&Zo7=H)@0000hQ`;K=0004VQb$4nuFf3k0000dP)t-s0000F4-ZLO z5_=#3i=;uca{#>{7{%3M=9~cZ^z{E)DgVU)y}-LLZ8-!0000eiQchC<|NsC0|NsC0 z|NsC0Ed)WW00077NklYh8glWbmdY3`MA~C z&axbokC)yk=Z|%{f4=XC-utIAyT2<)J4T1vt|&vyJbqE%bJ8Piiw)6UA;VqgTI&E& zLzM*dl_D|_dq^K*sa0Qrk>~TtTgu>|!XW#ei74x^#sWKvAuVa3JYgrLh05bG?4Uvz zjNa)GsH!z)5r2UhY6>xoa_Es>D59-4%(|xl(cncXGFlo8h?G!@zJUQns}ysDSXd2r zIWV-P03p@PP_>yFA&1w6XUR-6W0Do4h?2yfpk5XeS%S78wj^+v2Sxmf2UAM{10uZ| z5Lg;dcXBLTaW7RkIN;V0?y*tW(h*C!&#mC;j$p{mFMrd2Fsy*!RwP2!r%p5*QjLbR zraC8Qk&KU342)B3-Oyp#D2#n3<)BL;Xr-#yNCF))gQ*AXx`G1x~{4h}RN5_B5il6UY4Edrx?qX5Z(K zEhfeq=O;r~rLs1)j{VX6FO;-0_`Lo2GI|)rCmI`Q@0MG2K{q=S4T>*Yk4l@;lY?6?|ZUTlT)>oeB-yy z?I!Bv)$N9sZYV!`TVFkUXzft&Wr^PlQ|j}Q;Lfm~ey9UZR? zlT@>sbPAbUW(>MoAVO(8O8Vyl9uf6R}Q@1F;M>X5mwb)GkaBuzQP zH*dal({z}BI6A;lbb3XgBXgvp(OG>x!TN=TpObJ)=&au{GKhOH6cmq4Uje> z5^gmaZ*VHqF#fz&TL}n34LT`Q|IUdaAAdlrUFL-+Mztk>Ku_}FWFO(=c>I1hIY^08 zW_>Z(O?ng5AbNLAZ;CM>!?0zIHvU~&cd1(Tn1mwEdToY`!7&rLu*LY-ol(+af+RFV z_(YexO{ffr?@}!>Nq~c0-Db3cFwI{*Lx diff --git a/public/images/pokemon/exp/back/987.png b/public/images/pokemon/exp/back/987.png index 1da8db869dc117a8f4bb73752fba78576c9260e6..4956b630d3eca7102acf6a0f9e2a6f8e7c7891a5 100644 GIT binary patch delta 2319 zcmV+q3GnvR5x)|UBn|;{Qb$4nuFf3k0000dktHa9RE~%OSBm2R000eiQchC<|NsC0 z|NsC0|NsC0Ed)WW000QBNkl&mMuG$-^+~+C$UHgY&hnKKLhihcBf}+MIJi(JeL4F_rOQT z>ovjq=Z75RkcN2H3XYn0v`iI1tjVh^5P4Cb0+E6TuLyAaJ5c&!YN(m;(&uJekL3s91Njw@KyxURqA-&PdvLo^c0B2G{93um+q7?SP3le zm#0e+9E=P6fu`04_F*J6aakzODG(=~`9>fH7vW^hx?A8IZhpTh5W)zr*gakh9a+>2 zrd0AYWN#h)whQbMFD8&zb zh9^uN6kp7|uq-8e3am(WUg8!uf5~7SmVRBB1 zD<8!;Svm~9DhA>lmqj6MC=aezjDmIHSBvtn6~{O@yCpZvvPknzZafSntJR{SEJ{sXM2*i}d=|%|#a0~)5I0Ph9DAjYG@j&ETrwGGddWvHdh;(bC zOr19tdm58KOfG_YHSHdMj#(gL7)?X_xp5%Ut-uhDX&{d857T5Ah&ID45G_Y{Al|E} zcWFomoSxZVF7+c~>D4_FPv7B7sibv);qvq|oEroWN%d@ZD!CWf8Pt&U&iMl3r zA$&~S)?7hKmGV?7@9<5RdZc0>s%uP4Jm%G*LS{0hTyp{=Che~08_pBd>W<4 zW;Dyko0W2ZNGrW&$+OfK@8|-J-{n&maaahwc2GmIfg~YSG~OsV>+54aO(E!t-&QEP z=v*m}K;bWIUC{zfS!D?v^jid^*4*MLRm$Ug5Pet8G}BE5iFA)fK82W0TRP}}evC>sb>%TjV3bR5;*3f*7s_MQ zLGSU5N;WO!F|I7Q`Pe=z+0^r?rVW~`Tldlil1)Q-l>caH-N3PvY#Pd=;$E6Ilx(UJ zcuE|1@|dM$GpsxsOE$w2xTR!cAc4pI+n`cDo=;6Hh~(x|>g9ZBg`y#tG4lJwiMEbEYbte8yU*$? z4KYinidDn8NMhVyBlUm4{+yk5q`wHv;M%)?kpQXGskKfOM-I(L+zHj$ME7JYsSwQS z$f@~gVpXl#dSo|Jsgrm9D2}8Sl-PiB&WbHNjh1+8)MkaSsO%|`%# zSs^$|6^=@a&0a$r9=EH!9$HY+ov!v+d^rPYsZMwfgxoDND=38A^1x?e*=wKFO(IIQ7@Fy zuWbpt1002ovPDHLkV1l07W7Gfu delta 2212 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 delta 2166 zcmV-+2#NQ_5`z(tB!3}LOjJbx000jU4|^a0i=;uca{#>{7{%3M=9~cZ^z{E)DgVU) z|5Br%00001bW%=J06^y0W&i*P5J^NqRCwC$n}KrUDhx$^BLXS^|G_UH0AYh|g3{fc zd7kZT)28HFTm;dHw(VcI%=}*%)BXO=^OfZoV~qFS5D~@4-hUf&^(Wq&nOB(6u z3{dzrMLiB@Mh|kry=6sdw4)JuQ6G#L%|k>KlViyNK%A3~hrr7s-56gl z5uVnFT_;90JXaPrg)7VXc|4!*uP_SYtJwG-ake@LGJombq@FaGm2%>}QP6U{CjgZ6 zAdiA9Cup%Y%1L^x(6bkmS1HNx2tADvZ8Tyx4e$-cTc@n?J<40l^N}(n%c1y>J?#w8 zQ&W~jV;^b6jYm2$B0za2vLGV+eCBw7D2Im!+H0xH!p883U&r`JBSt-O6kwP@k>-Q- zq=41=34eN0gu=8BvTa!x@`Zw))`)Q+02XF&LI8=EPWpFxQa0e(>Ux<|YVx6!gOL%5 z!Z=+^*Vi^Cd>JVR$)Y78_EMtbXQgE!{DZA)M6pae^Hsz+2LyxgBh%O?zFwU7{>+oe z2qYFu&xlk5-3<@`8$u4=%wVl?-tpMgT=Dy}jeop^n)CDCh$+%#UHG%X;=0Ll;6nOD zD~HIPZtP8xvK=F$8j;tA~=LbO^Y+%IfuIR(@vZx^fK*VxbO?ph2 zI)96gj7U|E1Fp`v5Yv-_1Kw?C)r^f;mc^aZchQ5arp&>8HMflDCiP5D(X|ni5#q*; z^Hq@!l275^&(xe6F$)5#zLxV4L0u>t42-w|gsEZD2v0noiLMET4X zQ`L%?Wh2JOr0L=qVMWZU5o;EY2PG`XQ7|G1NSPw5`drVt5&7s;#Bh`ryrW^nc(loy zHmsOO!-zFRAX-~kYDNq;+LrcnWh2I;z*-8_G~)XHplU>&OU;O?qGpYFucB^@`F|B7 z8h`yli8=j-5skkBTB(-p%$MdfzA-CRavaOEkAs*QXfsQX5?xDmAvo{7lTf2p%4?~7 zA~t4nZnB(IQVx2C1Sv9`IfOK&2Y_+jyFy3`(0W-xkRF?0HX)$*Ejep?rhQa-Rt8#c zK~1GR0m@Jra(CrKE6%BD@tOcyyMIb~VK#&J_okDOFv&s-?X!hz2%yze$`i8@2&uC~ zcfq(~kA3%)@L<~kEm0{yAf&ydg4e`0;9g_$ILPinP{wO5&}xuP1_=#9+6&4cv*ZS` zD7wqK>z>4g1kehC+DiFGJA`y7-ZNcms_hazKH3%6ul*-07rs^iQm0amJbzWWlG{)u z$-p~uY67Fp`XQ1B67MTB!Ge0ora?#px<(SN@wD4xJ{_RP4quDU9#zVzM5njgCb}lk zV+l{mr-iNg3y=VN2icHd>IaIS;o8>dY4T~%L)UsLUmGHu^v;$BL`%RmG;&(t_HOddr7i@EgcmGUI$oIDl|>TllO8nQVfj|~UKeKDUSn{hrx0grU^ zEu;dnIU$e8;L+Zaqlaux$YYj!IkY0O86xnSIB<6mC1kTq9+i>JG6L5`HVOz_vr@jE zPgN`B3i(vCQm%x+b$=@5Y6x7TQm&4`mynG<0$)Qm>Ii%l*{J5zW#A`P7s~f)JnclJw^g>ux@6`)jtd z_M8w#w+68;^Qm*Yb{}U44KZetVttrTxxXfrUJ?3p8g?WsKYz=OKFg=E)R}9Y8IJ6l zkGK!)ACRr#o z>H6lT(v~{02W#crluCVh>(5**#3 z8#?wG^+MI&BmCA!rEt}P!o|gJAFoA>E?ZED6!vzdLU69z|HAflRS3?d*Xw2EM(vqXp3#E9Z*Xo7R z5~I}%wfxpXwHIpreU54`)RLMn^g<~lWyM19YJmRHLhvuiQ3PuWyOkXwpSEdT%j07*qoM6N<$g7x(xIsgCw diff --git a/public/images/pokemon/variant/back/987.json b/public/images/pokemon/variant/back/987.json index 1773ba70c36..e28a34d5435 100644 --- a/public/images/pokemon/variant/back/987.json +++ b/public/images/pokemon/variant/back/987.json @@ -9,7 +9,7 @@ "de62a4": "ffc668", "4a83a4": "387fa7", "314a62": "244260", - "70bbb4": "f8d371", + "548e88": "2d60bb", "a4295a": "cc762f" }, "1": { @@ -22,7 +22,7 @@ "de62a4": "ffdf90", "4a83a4": "a1c8db", "314a62": "7396b4", - "70bbb4": "70bbb4", + "548e88": "a9c0c6", "a4295a": "e28c27" }, "2": { @@ -35,7 +35,7 @@ "de62a4": "e25038", "4a83a4": "e6aa47", "314a62": "b56f2a", - "70bbb4": "f8d371", + "548e88": "e0b544", "a4295a": "a62a21" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/987.json b/public/images/pokemon/variant/exp/back/987.json index 385a9eeb29f..5fb59f6979d 100644 --- a/public/images/pokemon/variant/exp/back/987.json +++ b/public/images/pokemon/variant/exp/back/987.json @@ -8,7 +8,7 @@ "0f0f0f": "0f0f0f", "314a62": "244260", "621841": "71370f", - "70bbb4": "f8d371", + "548e88": "2d60bb", "de62a4": "ffc668", "a4295a": "cc762f" }, @@ -21,7 +21,7 @@ "0f0f0f": "0f0f0f", "314a62": "7396b4", "621841": "7b3c08", - "70bbb4": "70bbb4", + "548e88": "a9c0c6", "de62a4": "ffdf90", "a4295a": "e28c27" }, @@ -34,7 +34,7 @@ "0f0f0f": "0f0f0f", "314a62": "b56f2a", "621841": "5a0a05", - "70bbb4": "f8d371", + "548e88": "e0b544", "de62a4": "e25038", "a4295a": "a62a21" } From 58b7f67ca8faad7fda46e9cb02f47e59ab1c36eb Mon Sep 17 00:00:00 2001 From: Scooom <97370685+Scoooom@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:15:59 -0600 Subject: [PATCH 35/54] [Challenge] Fix flip inverse achievement (#5145) * Actually issue FLIP_INVERSE achivement * Local to remote * Update src/system/achv.ts Fix correctly giving FLIP_INVERSE Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Scooom Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/system/achv.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/system/achv.ts b/src/system/achv.ts index a2777101186..e0c9f0cf052 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -292,7 +292,6 @@ export function getAchievementDescription(localizationKey: string): string { } - export const achvs = { _10K_MONEY: new MoneyAchv("10K_MONEY", "", 10000, "nugget", 10), _100K_MONEY: new MoneyAchv("100K_MONEY", "", 100000, "big_nugget", 25).setSecret(true), @@ -365,7 +364,7 @@ export const achvs = { FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c) => c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)), INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, (c) => c instanceof InverseBattleChallenge && c.value > 0), FLIP_STATS: new ChallengeAchv("FLIP_STATS", "", "FLIP_STATS.description", "dubious_disc", 100, (c) => c instanceof FlipStatChallenge && c.value > 0), - FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.every(c => [ Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT ].includes(c.id) && c.value > 0)).setSecret(), + FLIP_INVERSE: new ChallengeAchv("FLIP_INVERSE", "", "FLIP_INVERSE.description", "cracked_pot", 100, (c) => c instanceof FlipStatChallenge && c.value > 0 && globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)).setSecret(), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; From ce30897c0a7dc593402b5372ffaaba7cc5d9e9a9 Mon Sep 17 00:00:00 2001 From: Unicornpowerstar Date: Mon, 20 Jan 2025 00:48:07 +0100 Subject: [PATCH 36/54] [Sprite] Update Primal Kyogre Backsprite (#5156) --- public/images/pokemon/exp/back/382-primal.png | Bin 14063 -> 16319 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/pokemon/exp/back/382-primal.png b/public/images/pokemon/exp/back/382-primal.png index 55c73cf877e6c93e144da8257dd8c7f70529a121..7bf840bcf24f07a53e50bc2e9df0a1f70af26824 100644 GIT binary patch literal 16319 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 E51hCr8UO$Q From f551c51413f948a74db28cf90e4a702a5b1a46cd Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Mon, 20 Jan 2025 18:01:42 +0100 Subject: [PATCH 37/54] [UI/UX] Adding options to see mons with only one or only two cost reductions (#5045) --- src/ui/dropdown.ts | 12 +++++++++++- src/ui/starter-select-ui-handler.ts | 13 ++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index ec124312e14..d8ba88d3484 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -7,7 +7,9 @@ export enum DropDownState { ON = 0, OFF = 1, EXCLUDE = 2, - UNLOCKABLE = 3 + UNLOCKABLE = 3, + ONE = 4, + TWO = 5 } export enum DropDownType { @@ -55,6 +57,8 @@ export class DropDownOption extends Phaser.GameObjects.Container { private offColor = 0x272727; private excludeColor = 0xff5555; private unlockableColor = 0xffff00; + private oneColor = 0x33bbff; + private twoColor = 0x33bbff; constructor(val: any, labels: DropDownLabel | DropDownLabel[]) { super(globalScene); @@ -126,6 +130,12 @@ export class DropDownOption extends Phaser.GameObjects.Container { case DropDownState.UNLOCKABLE: this.toggle.setTint(this.unlockableColor); break; + case DropDownState.ONE: + this.toggle.setTint(this.oneColor); + break; + case DropDownState.TWO: + this.toggle.setTint(this.twoColor); + break; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 40325d24af7..d99eb35cf4c 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -450,6 +450,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const costReductionLabels = [ new DropDownLabel(i18next.t("filterBar:costReduction"), undefined, DropDownState.OFF), new DropDownLabel(i18next.t("filterBar:costReductionUnlocked"), undefined, DropDownState.ON), + new DropDownLabel(i18next.t("filterBar:costReductionUnlockedOne"), undefined, DropDownState.ONE), + new DropDownLabel(i18next.t("filterBar:costReductionUnlockedTwo"), undefined, DropDownState.TWO), new DropDownLabel(i18next.t("filterBar:costReductionUnlockable"), undefined, DropDownState.UNLOCKABLE), new DropDownLabel(i18next.t("filterBar:costReductionLocked"), undefined, DropDownState.EXCLUDE), ]; @@ -2585,13 +2587,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); // Cost Reduction Filter - const isCostReduced = starterData.valueReduction > 0; + const isCostReducedByOne = starterData.valueReduction === 1; + const isCostReducedByTwo = starterData.valueReduction === 2; const isCostReductionUnlockable = this.isValueReductionAvailable(container.species.speciesId); const fitsCostReduction = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(unlocks => { if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ON) { - return isCostReduced; + return isCostReducedByOne || isCostReducedByTwo; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ONE) { + return isCostReducedByOne; + } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.TWO) { + return isCostReducedByTwo; } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.EXCLUDE) { - return isStarterProgressable && !isCostReduced; + return isStarterProgressable && !(isCostReducedByOne || isCostReducedByTwo); } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.UNLOCKABLE) { return isCostReductionUnlockable; } else if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.OFF) { From d495c487163e8f1cce131ee950f29db836b06363 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:11:58 -0800 Subject: [PATCH 38/54] [i18n] Update locales submodule --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index 7bfcbccb9b8..e07ab625f20 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 7bfcbccb9b8192b1059ca7c4c7e7d24901cf579d +Subproject commit e07ab625f2080afe36b61fad291b0ec5eff4000c From d85aedbdfc87a59840f0cdad2f781761d212197a Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:12:58 -0800 Subject: [PATCH 39/54] [Bug] Prevent pokemon with 0 HP from being statused (#5137) * [Bug] Prevent pokemon with 0 HP from being statused * Update test * Move check to `trySetStatus()` and update test --- src/field/pokemon.ts | 3 ++ ...s-effect.test.ts => status_effect.test.ts} | 38 +++++++++++++++++++ src/utils.ts | 10 ++--- 3 files changed, 46 insertions(+), 5 deletions(-) rename src/test/data/{status-effect.test.ts => status_effect.test.ts} (92%) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a833facd2f8..a4b8603cbb0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3606,6 +3606,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.canSetStatus(effect, asPhase, false, sourcePokemon)) { return false; } + if (this.isFainted() && effect !== StatusEffect.FAINT) { + return false; + } /** * If this Pokemon falls asleep or freezes in the middle of a multi-hit attack, diff --git a/src/test/data/status-effect.test.ts b/src/test/data/status_effect.test.ts similarity index 92% rename from src/test/data/status-effect.test.ts rename to src/test/data/status_effect.test.ts index 4831e8de5de..7948549b8e8 100644 --- a/src/test/data/status-effect.test.ts +++ b/src/test/data/status_effect.test.ts @@ -400,4 +400,42 @@ describe("Status Effects", () => { expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.SUCCESS); }); }); + + describe("Behavior", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.NUZZLE) + .enemyLevel(2000); + }); + + it("should not inflict a 0 HP mon with a status", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + const player = game.scene.getPlayerPokemon()!; + player.hp = 0; + + expect(player.trySetStatus(StatusEffect.BURN)).toBe(false); + expect(player.status?.effect).not.toBe(StatusEffect.BURN); + }); + }); }); diff --git a/src/utils.ts b/src/utils.ts index be0aec84ecd..2235fb69633 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -349,14 +349,14 @@ export class IntegerHolder extends NumberHolder { } } -/** @deprecated Use {@linkcode NumberHolder}*/ -export class FixedInt extends IntegerHolder { - constructor(value: integer) { - super(value); +export class FixedInt { + public readonly value: number; + + constructor(value: number) { + this.value = value; } } -/** @deprecated */ export function fixedInt(value: integer): integer { return new FixedInt(value) as unknown as integer; } From 39283e373b0ad35bf9e2e2a36da30f9f0f134f4e Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:43:55 -0500 Subject: [PATCH 40/54] [UI/UX] Add sort by caught/hatched count to starter select (#5161) --- src/ui/dropdown.ts | 4 +++- src/ui/starter-select-ui-handler.ts | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index d8ba88d3484..8c318b29d64 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -29,7 +29,9 @@ export enum SortCriteria { COST = 1, CANDY = 2, IV = 3, - NAME = 4 + NAME = 4, + CAUGHT = 5, + HATCHED = 6 } export class DropDownLabel { diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index d99eb35cf4c..dd427802083 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -502,7 +502,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { new DropDownOption(SortCriteria.COST, new DropDownLabel(i18next.t("filterBar:sortByCost"))), new DropDownOption(SortCriteria.CANDY, new DropDownLabel(i18next.t("filterBar:sortByCandies"))), new DropDownOption(SortCriteria.IV, new DropDownLabel(i18next.t("filterBar:sortByIVs"))), - new DropDownOption(SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName"))) + new DropDownOption(SortCriteria.NAME, new DropDownLabel(i18next.t("filterBar:sortByName"))), + new DropDownOption(SortCriteria.CAUGHT, new DropDownLabel(i18next.t("filterBar:sortByNumCaught"))), + new DropDownOption(SortCriteria.HATCHED, new DropDownLabel(i18next.t("filterBar:sortByNumHatched"))) ]; this.filterBar.addFilter(DropDownColumn.SORT, i18next.t("filterBar:sortFilter"), new DropDown(0, 0, sortOptions, this.updateStarters, DropDownType.SINGLE)); this.filterBarContainer.add(this.filterBar); @@ -2698,6 +2700,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return (avgIVsA - avgIVsB) * -sort.dir; case SortCriteria.NAME: return a.species.name.localeCompare(b.species.name) * -sort.dir; + case SortCriteria.CAUGHT: + return (globalScene.gameData.dexData[a.species.speciesId].caughtCount - globalScene.gameData.dexData[b.species.speciesId].caughtCount) * -sort.dir; + case SortCriteria.HATCHED: + return (globalScene.gameData.dexData[a.species.speciesId].hatchedCount - globalScene.gameData.dexData[b.species.speciesId].hatchedCount) * -sort.dir; } return 0; }); From 93fc779350dca66e446e60211510a99c85efac50 Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:34:18 -0600 Subject: [PATCH 41/54] [Bug] Fix G-Max Urshifus having wrong movesets (#5162) --- src/data/balance/pokemon-level-moves.ts | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/data/balance/pokemon-level-moves.ts b/src/data/balance/pokemon-level-moves.ts index 8e28300eb8a..8fe61da35c0 100644 --- a/src/data/balance/pokemon-level-moves.ts +++ b/src/data/balance/pokemon-level-moves.ts @@ -19718,6 +19718,44 @@ export const pokemonFormLevelMoves: PokemonSpeciesFormLevelMoves = { [ 48, Moves.CLOSE_COMBAT ], [ 52, Moves.FOCUS_PUNCH ], ], + 2: [ + [ EVOLVE_MOVE, Moves.WICKED_BLOW ], + [ 1, Moves.LEER ], + [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.ENDURE ], + [ 1, Moves.ROCK_SMASH ], + [ 1, Moves.SUCKER_PUNCH ], + [ 12, Moves.AERIAL_ACE ], + [ 16, Moves.SCARY_FACE ], + [ 20, Moves.HEADBUTT ], + [ 24, Moves.BRICK_BREAK ], + [ 28, Moves.DETECT ], + [ 32, Moves.BULK_UP ], + [ 36, Moves.IRON_HEAD ], + [ 40, Moves.DYNAMIC_PUNCH ], + [ 44, Moves.COUNTER ], + [ 48, Moves.CLOSE_COMBAT ], + [ 52, Moves.FOCUS_PUNCH ], + ], + 3: [ + [ EVOLVE_MOVE, Moves.SURGING_STRIKES ], + [ 1, Moves.LEER ], + [ 1, Moves.FOCUS_ENERGY ], + [ 1, Moves.ENDURE ], + [ 1, Moves.ROCK_SMASH ], + [ 1, Moves.AQUA_JET ], + [ 12, Moves.AERIAL_ACE ], + [ 16, Moves.SCARY_FACE ], + [ 20, Moves.HEADBUTT ], + [ 24, Moves.BRICK_BREAK ], + [ 28, Moves.DETECT ], + [ 32, Moves.BULK_UP ], + [ 36, Moves.IRON_HEAD ], + [ 40, Moves.DYNAMIC_PUNCH ], + [ 44, Moves.COUNTER ], + [ 48, Moves.CLOSE_COMBAT ], + [ 52, Moves.FOCUS_PUNCH ], + ], }, [Species.CALYREX]: { 1: [ From 271e51be582134c5be30c292ebad99a8ed6c4c45 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:40:05 -0500 Subject: [PATCH 42/54] [Balance] [Feature] Match evil teams to monogen challenge (#4850) * Match evil teams to monogen challenge * Remove outdated comment * Remove BBL adjustment --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/battle.ts | 61 +++++++++++++++++++++++++++---------------- src/data/challenge.ts | 55 +++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index b1196bb0139..287a981f83d 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -24,9 +24,26 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; export enum ClassicFixedBossWaves { - // TODO: other fixed wave battles should be added here + TOWN_YOUNGSTER = 5, + RIVAL_1 = 8, + RIVAL_2 = 25, + EVIL_GRUNT_1 = 35, + RIVAL_3 = 55, + EVIL_GRUNT_2 = 62, + EVIL_GRUNT_3 = 64, + EVIL_ADMIN_1 = 66, + RIVAL_4 = 95, + EVIL_GRUNT_4 = 112, + EVIL_ADMIN_2 = 114, EVIL_BOSS_1 = 115, + RIVAL_5 = 145, EVIL_BOSS_2 = 165, + ELITE_FOUR_1 = 182, + ELITE_FOUR_2 = 184, + ELITE_FOUR_3 = 186, + ELITE_FOUR_4 = 188, + CHAMPION = 190, + RIVAL_6 = 195, } export enum BattleType { @@ -500,7 +517,7 @@ export class FixedBattleConfig { * @param seedOffset the seed offset to use for the random generation of the trainer * @returns the generated trainer */ -function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc { +export function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], randomGender: boolean = false, seedOffset: number = 0): GetTrainerFunc { return () => { const rand = Utils.randSeedInt(trainerPool.length); const trainerTypes: TrainerType[] = []; @@ -544,51 +561,51 @@ export interface FixedBattleConfigs { * Champion on 190 */ export const classicFixedBattles: FixedBattleConfigs = { - [5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.TOWN_YOUNGSTER]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.YOUNGSTER, Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), - [8]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)), - [25]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_2, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), - [35]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.EVIL_GRUNT_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), - [55]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_3, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }), - [62]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_GRUNT_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), - [64]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_GRUNT_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), - [66]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_ADMIN_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true)), - [95]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_4, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), - [112]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_GRUNT_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_GRUNT, TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT, TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT, TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ], true)), - [114]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_ADMIN_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], TrainerType.FABA, TrainerType.PLUMERIA, TrainerType.OLEANA, [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]], true, 1)), - [ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_BOSS_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.MAXIE, TrainerType.ARCHIE, TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, TrainerType.LUSAMINE, TrainerType.GUZMA, TrainerType.ROSE, TrainerType.PENNY ])) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), - [145]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_5]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_5, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), - [ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(35) + [ClassicFixedBossWaves.EVIL_BOSS_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.MAXIE_2, TrainerType.ARCHIE_2, TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, TrainerType.LUSAMINE_2, TrainerType.GUZMA_2, TrainerType.ROSE_2, TrainerType.PENNY_2 ])) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }), - [182]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.ELITE_FOUR_1]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, [ TrainerType.HALA, TrainerType.MOLAYNE ], TrainerType.MARNIE_ELITE, TrainerType.RIKA, TrainerType.CRISPIN ])), - [184]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) + [ClassicFixedBossWaves.ELITE_FOUR_2]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY, TrainerType.AMARYS ])), - [186]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) + [ClassicFixedBossWaves.ELITE_FOUR_3]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, [ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ], TrainerType.LARRY_ELITE, TrainerType.LACEY ])), - [188]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) + [ClassicFixedBossWaves.ELITE_FOUR_4]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL, TrainerType.DRAYTON ])), - [190]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(182) + [ClassicFixedBossWaves.CHAMPION]: new FixedBattleConfig().setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.ELITE_FOUR_1) .setGetTrainerFunc(getRandomTrainerFunc([ TrainerType.BLUE, [ TrainerType.RED, TrainerType.LANCE_CHAMPION ], [ TrainerType.STEVEN, TrainerType.WALLACE ], TrainerType.CYNTHIA, [ TrainerType.ALDER, TrainerType.IRIS ], TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, [ TrainerType.GEETA, TrainerType.NEMONA ], TrainerType.KIERAN ])), - [195]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) + [ClassicFixedBossWaves.RIVAL_6]: new FixedBattleConfig().setBattleType(BattleType.TRAINER) .setGetTrainerFunc(() => new Trainer(TrainerType.RIVAL_6, globalScene.gameData.gender === PlayerGender.MALE ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT)) .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], allowLuckUpgrades: false }) }; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index a01ceab8aa3..fab5196601c 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -8,7 +8,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import type { FixedBattleConfig } from "#app/battle"; -import { BattleType } from "#app/battle"; +import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import type { GameMode } from "#app/game-mode"; import { Type } from "#enums/type"; @@ -20,6 +20,7 @@ import type { Moves } from "#enums/moves"; import { TypeColor, TypeShadow } from "#enums/color"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { ModifierTier } from "#app/modifier/modifier-tier"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -464,30 +465,64 @@ export class SingleGenerationChallenge extends Challenge { return false; } - applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { - let trainerTypes: TrainerType[] = []; + applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean { + let trainerTypes: (TrainerType | TrainerType[])[] = []; + const evilTeamWaves: number[] = [ ClassicFixedBossWaves.EVIL_GRUNT_1, ClassicFixedBossWaves.EVIL_GRUNT_2, ClassicFixedBossWaves.EVIL_GRUNT_3, ClassicFixedBossWaves.EVIL_ADMIN_1, ClassicFixedBossWaves.EVIL_GRUNT_4, ClassicFixedBossWaves.EVIL_ADMIN_2, ClassicFixedBossWaves.EVIL_BOSS_1, ClassicFixedBossWaves.EVIL_BOSS_2 ]; + const evilTeamGrunts = [[ TrainerType.ROCKET_GRUNT ], [ TrainerType.ROCKET_GRUNT ], [ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ], [ TrainerType.GALACTIC_GRUNT ], [ TrainerType.PLASMA_GRUNT ], [ TrainerType.FLARE_GRUNT ], [ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ], [ TrainerType.MACRO_GRUNT ], [ TrainerType.STAR_GRUNT ]]; + const evilTeamAdmins = [[ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ], [[ TrainerType.TABITHA, TrainerType.COURTNEY ], [ TrainerType.MATT, TrainerType.SHELLY ]], [ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ], [ TrainerType.ZINZOLIN, TrainerType.ROOD ], [ TrainerType.XEROSIC, TrainerType.BRYONY ], [ TrainerType.FABA, TrainerType.PLUMERIA ], [ TrainerType.OLEANA ], [ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]]; + const evilTeamBosses = [[ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_1 ], [ TrainerType.MAXIE, TrainerType.ARCHIE ], [ TrainerType.CYRUS ], [ TrainerType.GHETSIS ], [ TrainerType.LYSANDRE ], [ TrainerType.LUSAMINE, TrainerType.GUZMA ], [ TrainerType.ROSE ], [ TrainerType.PENNY ]]; + const evilTeamBossRematches = [[ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.ROCKET_BOSS_GIOVANNI_2 ], [ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ], [ TrainerType.CYRUS_2 ], [ TrainerType.GHETSIS_2 ], [ TrainerType.LYSANDRE_2 ], [ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ], [ TrainerType.ROSE_2 ], [ TrainerType.PENNY_2 ]]; switch (waveIndex) { - case 182: + case ClassicFixedBossWaves.EVIL_GRUNT_1: + trainerTypes = evilTeamGrunts[this.value - 1]; + battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)); + return true; + case ClassicFixedBossWaves.EVIL_GRUNT_2: + case ClassicFixedBossWaves.EVIL_GRUNT_3: + case ClassicFixedBossWaves.EVIL_GRUNT_4: + trainerTypes = evilTeamGrunts[this.value - 1]; + break; + case ClassicFixedBossWaves.EVIL_ADMIN_1: + case ClassicFixedBossWaves.EVIL_ADMIN_2: + trainerTypes = evilTeamAdmins[this.value - 1]; + break; + case ClassicFixedBossWaves.EVIL_BOSS_1: + trainerTypes = evilTeamBosses[this.value - 1]; + battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); + return true; + case ClassicFixedBossWaves.EVIL_BOSS_2: + trainerTypes = evilTeamBossRematches[this.value - 1]; + battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)) + .setCustomModifierRewards({ guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.ULTRA ], allowLuckUpgrades: false }); + return true; + case ClassicFixedBossWaves.ELITE_FOUR_1: trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; break; - case 184: + case ClassicFixedBossWaves.ELITE_FOUR_2: trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; break; - case 186: + case ClassicFixedBossWaves.ELITE_FOUR_3: trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; break; - case 188: + case ClassicFixedBossWaves.ELITE_FOUR_4: trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; break; - case 190: + case ClassicFixedBossWaves.CHAMPION: trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; break; } if (trainerTypes.length === 0) { return false; - } else { - battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(trainerTypes[this.value - 1], TrainerVariant.DEFAULT)); + } else if (evilTeamWaves.includes(waveIndex)) { + battleConfig.setBattleType(BattleType.TRAINER).setSeedOffsetWave(ClassicFixedBossWaves.EVIL_GRUNT_1).setGetTrainerFunc(getRandomTrainerFunc(trainerTypes, true)); return true; + } else if (waveIndex >= ClassicFixedBossWaves.ELITE_FOUR_1 && waveIndex <= ClassicFixedBossWaves.CHAMPION) { + const ttypes = trainerTypes as TrainerType[]; + battleConfig.setBattleType(BattleType.TRAINER).setGetTrainerFunc(() => new Trainer(ttypes[this.value - 1], TrainerVariant.DEFAULT)); + return true; + } else { + return false; } } From d10b7c05f73887902391a8cf7bfff56756b30373 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:42:27 +0100 Subject: [PATCH 43/54] [Refactor] Descriptions for evolution and form change requirements (#5147) * Adding extensions of the SpeciesEvolutionCondition class * Commented out logs * Introducing descriptions for form changes; new class SpeciesFormChangeAbilityTrigger which is functionally identical to SpeciesFormChangeManualTrigger but has appropriate description * Fix in description of compound trigger * Commenting out logs * Small fixes, clearing logs, implementing suggestions * Apply suggestions from code review Trigger functions are declared with no arguments when not needed. Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Removed exports for new classes --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/data/ability.ts | 12 +- src/data/balance/pokemon-evolutions.ts | 498 +++++++++++++----- src/data/battler-tags.ts | 10 +- .../the-winstrate-challenge-encounter.ts | 4 +- src/data/pokemon-forms.ts | 108 ++-- src/plugins/i18n.ts | 1 + 6 files changed, 433 insertions(+), 200 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 0e8b3c2392d..5e5231176b5 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -16,7 +16,7 @@ import type { ArenaTrapTag } from "./arena-tag"; import { ArenaTagSide } from "./arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; -import { SpeciesFormChangeManualTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms"; import i18next from "i18next"; import type { Localizable } from "#app/interfaces/locales"; import { Command } from "../ui/command-ui-handler"; @@ -232,7 +232,7 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr { applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex && !simulated) { - return globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + return globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } return false; @@ -1875,7 +1875,7 @@ export class PostVictoryFormChangeAbAttr extends PostVictoryAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } return true; } @@ -2306,7 +2306,7 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr { applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { - return simulated || globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + return simulated || globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } return false; @@ -2734,7 +2734,7 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } return true; } @@ -3704,7 +3704,7 @@ export class PostTurnFormChangeAbAttr extends PostTurnAbAttr { const formIndex = this.formFunc(pokemon); if (formIndex !== pokemon.formIndex) { if (!simulated) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger, false); } return true; diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index bf34b5122dc..a8fe3b5f4ab 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -12,6 +12,8 @@ import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { speciesStarterCosts } from "./starters"; +import i18next from "i18next"; export enum SpeciesWildEvolutionDelay { @@ -120,17 +122,214 @@ export class FusionSpeciesFormEvolution extends SpeciesFormEvolution { export class SpeciesEvolutionCondition { public predicate: EvolutionConditionPredicate; - public enforceFunc: EvolutionConditionEnforceFunc | undefined; + public enforceFunc?: EvolutionConditionEnforceFunc; + public description: string; constructor(predicate: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { this.predicate = predicate; this.enforceFunc = enforceFunc; + this.description = ""; } } -export class SpeciesFriendshipEvolutionCondition extends SpeciesEvolutionCondition { - constructor(friendshipAmount: integer, predicate?: EvolutionConditionPredicate, enforceFunc?: EvolutionConditionEnforceFunc) { - super(p => p.friendship >= friendshipAmount && (!predicate || predicate(p)), enforceFunc); +class GenderEvolutionCondition extends SpeciesEvolutionCondition { + public gender: Gender; + constructor(gender: Gender) { + super(p => p.gender === gender, p => p.gender = gender); + this.gender = gender; + this.description = i18next.t("pokemonEvolutions:gender", { gender: i18next.t(`pokemonEvolutions:${Gender[gender]}`) }); + } +} + +class TimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { + public timesOfDay: TimeOfDay[]; + constructor(tod: "day" | "night") { + if (tod === "day") { + super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY); + this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; + } else if (tod === "night") { + super(() => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT); + this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; + } else { + super(() => false); + this.timesOfDay = []; + } + this.description = i18next.t("pokemonEvolutions:timeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); + } +} + +class MoveEvolutionCondition extends SpeciesEvolutionCondition { + public move: Moves; + constructor(move: Moves) { + super(p => p.moveset.filter(m => m?.moveId === move).length > 0); + this.move = move; + const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); + } +} + +class FriendshipEvolutionCondition extends SpeciesEvolutionCondition { + public amount: integer; + constructor(amount: number) { + super(p => p.friendship >= amount); + this.amount = amount; + this.description = i18next.t("pokemonEvolutions:friendship"); + } +} + +class FriendshipTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { + public amount: integer; + public timesOfDay: TimeOfDay[]; + constructor(amount: number, tod: "day" | "night") { + if (tod === "day") { + super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); + this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; + } else if (tod === "night") { + super(p => p.friendship >= amount && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); + this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; + } else { + super(p => false); + this.timesOfDay = []; + } + this.amount = amount; + this.description = i18next.t("pokemonEvolutions:friendshipTimeOfDay", { tod: i18next.t(`pokemonEvolutions:${tod}`) }); + } +} + +class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { + public amount: integer; + public type: Type; + constructor(amount: number, type: Type) { + super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type)); + this.amount = amount; + this.type = type; + this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) }); + } +} + +class ShedinjaEvolutionCondition extends SpeciesEvolutionCondition { + constructor() { + super(() => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0); + this.description = i18next.t("pokemonEvolutions:shedinja"); + } +} + +class PartyTypeEvolutionCondition extends SpeciesEvolutionCondition { + public type: Type; + constructor(type: Type) { + super(() => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1)); + this.type = type; + this.description = i18next.t("pokemonEvolutions:partyType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) }); + } +} + +class CaughtEvolutionCondition extends SpeciesEvolutionCondition { + public species: Species; + constructor(species: Species) { + super(() => !!globalScene.gameData.dexData[species].caughtAttr); + this.species = species; + this.description = i18next.t("pokemonEvolutions:caught", { species: i18next.t(`pokemon:${Species[this.species].toLowerCase()}`) }); + } +} + +class WeatherEvolutionCondition extends SpeciesEvolutionCondition { + public weatherTypes: WeatherType[]; + constructor(weatherTypes: WeatherType[]) { + super(() => weatherTypes.indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1); + this.weatherTypes = weatherTypes; + } +} + +class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { + public type: Type; + constructor(type: Type) { + super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0); + this.type = type; + this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${Type[this.type]}`) }); + } +} + +class TreasureEvolutionCondition extends SpeciesEvolutionCondition { + constructor() { + super(p => p.evoCounter + + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier + || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9); + this.description = i18next.t("pokemonEvolutions:treasure"); + } +} + +class TyrogueEvolutionCondition extends SpeciesEvolutionCondition { + public move: Moves; + constructor(move: Moves) { + super(p => + p.getMoveset(true).find(m => m && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(m?.moveId))?.moveId === move); + this.move = move; + const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); + } +} + +class NatureEvolutionCondition extends SpeciesEvolutionCondition { + public natures: Nature[]; + constructor(natures: Nature[]) { + super(p => natures.indexOf(p.getNature()) > -1); + this.natures = natures; + this.description = i18next.t("pokemonEvolutions:nature"); + } +} + +class MoveTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { + public move: Moves; + public timesOfDay: TimeOfDay[]; + constructor(move: Moves, tod: "day" | "night") { + if (tod === "day") { + super(p => p.moveset.filter(m => m?.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); + this.move = move; + this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; + } else if (tod === "night") { + super(p => p.moveset.filter(m => m?.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); + this.move = move; + this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; + } else { + super(() => false); + this.timesOfDay = []; + } + const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + this.description = i18next.t("pokemonEvolutions:moveTimeOfDay", { move: i18next.t(`move:${moveKey}.name`), tod: i18next.t(`pokemonEvolutions:${tod}`) }); + } +} + +class BiomeEvolutionCondition extends SpeciesEvolutionCondition { + public biomes: Biome[]; + constructor(biomes: Biome[]) { + super(() => biomes.filter(b => b === globalScene.arena.biomeType).length > 0); + this.biomes = biomes; + this.description = i18next.t("pokemonEvolutions:biome"); + } +} + +class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { + constructor() { + super(p => { + let ret = false; + if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) { + globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + } + return ret; + }); + const moveKey = Moves[Moves.HYPER_DRILL].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); + } +} + +class TandemausEvolutionCondition extends SpeciesEvolutionCondition { + constructor() { + super(p => { + let ret = false; + globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); + return ret; + }); } } @@ -267,8 +466,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ELECTRODE, 30, null, null) ], [Species.CUBONE]: [ - new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.MAROWAK, 28, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.ALOLA_MAROWAK, 28, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.MAROWAK, 28, null, new TimeOfDayEvolutionCondition("day")) ], [Species.TYROGUE]: [ /** @@ -277,19 +476,13 @@ export const pokemonEvolutions: PokemonEvolutions = { * If Tyrogue knows multiple of these moves, its evolution is based on * the first qualifying move in its moveset. */ - new SpeciesEvolution(Species.HITMONLEE, 20, null, new SpeciesEvolutionCondition(p => - p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.LOW_SWEEP - )), - new SpeciesEvolution(Species.HITMONCHAN, 20, null, new SpeciesEvolutionCondition(p => - p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.MACH_PUNCH - )), - new SpeciesEvolution(Species.HITMONTOP, 20, null, new SpeciesEvolutionCondition(p => - p.getMoveset(true).find(move => move && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(move?.moveId))?.moveId === Moves.RAPID_SPIN - )), + new SpeciesEvolution(Species.HITMONLEE, 20, null, new TyrogueEvolutionCondition(Moves.LOW_SWEEP)), + new SpeciesEvolution(Species.HITMONCHAN, 20, null, new TyrogueEvolutionCondition(Moves.MACH_PUNCH)), + new SpeciesEvolution(Species.HITMONTOP, 20, null, new TyrogueEvolutionCondition(Moves.RAPID_SPIN)), ], [Species.KOFFING]: [ - new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.WEEZING, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.GALAR_WEEZING, 35, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.WEEZING, 35, null, new TimeOfDayEvolutionCondition("day")) ], [Species.RHYHORN]: [ new SpeciesEvolution(Species.RHYDON, 42, null, null) @@ -334,8 +527,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.QUILAVA, 14, null, null) ], [Species.QUILAVA]: [ - new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.TYPHLOSION, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.TYPHLOSION, 36, null, new TimeOfDayEvolutionCondition("day")) ], [Species.TOTODILE]: [ new SpeciesEvolution(Species.CROCONAW, 18, null, null) @@ -437,8 +630,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.LINOONE, 20, null, null) ], [Species.WURMPLE]: [ - new SpeciesEvolution(Species.SILCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)), - new SpeciesEvolution(Species.CASCOON, 7, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.SILCOON, 7, null, new TimeOfDayEvolutionCondition("day")), + new SpeciesEvolution(Species.CASCOON, 7, null, new TimeOfDayEvolutionCondition("night")) ], [Species.SILCOON]: [ new SpeciesEvolution(Species.BEAUTIFLY, 10, null, null) @@ -462,8 +655,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.KIRLIA, 20, null, null) ], [Species.KIRLIA]: [ - new SpeciesEvolution(Species.GARDEVOIR, 30, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)), - new SpeciesEvolution(Species.GALLADE, 30, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE)) + new SpeciesEvolution(Species.GARDEVOIR, 30, null, new GenderEvolutionCondition(Gender.FEMALE)), + new SpeciesEvolution(Species.GALLADE, 30, null, new GenderEvolutionCondition(Gender.MALE)) ], [Species.SURSKIT]: [ new SpeciesEvolution(Species.MASQUERAIN, 22, null, null) @@ -479,7 +672,7 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [Species.NINCADA]: [ new SpeciesEvolution(Species.NINJASK, 20, null, null), - new SpeciesEvolution(Species.SHEDINJA, 20, null, new SpeciesEvolutionCondition(p => globalScene.getPlayerParty().length < 6 && globalScene.pokeballCounts[PokeballType.POKEBALL] > 0)) + new SpeciesEvolution(Species.SHEDINJA, 20, null, new ShedinjaEvolutionCondition()) ], [Species.WHISMUR]: [ new SpeciesEvolution(Species.LOUDRED, 20, null, null) @@ -551,8 +744,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DUSCLOPS, 37, null, null) ], [Species.SNORUNT]: [ - new SpeciesEvolution(Species.GLALIE, 42, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE)), - new SpeciesEvolution(Species.FROSLASS, 42, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)) + new SpeciesEvolution(Species.GLALIE, 42, null, new GenderEvolutionCondition(Gender.MALE)), + new SpeciesEvolution(Species.FROSLASS, 42, null, new GenderEvolutionCondition(Gender.FEMALE)) ], [Species.SPHEAL]: [ new SpeciesEvolution(Species.SEALEO, 32, null, null) @@ -615,11 +808,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.BASTIODON, 30, null, null) ], [Species.BURMY]: [ - new SpeciesEvolution(Species.MOTHIM, 20, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE)), - new SpeciesEvolution(Species.WORMADAM, 20, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)) + new SpeciesEvolution(Species.MOTHIM, 20, null, new GenderEvolutionCondition(Gender.MALE)), + new SpeciesEvolution(Species.WORMADAM, 20, null, new GenderEvolutionCondition(Gender.FEMALE)) ], [Species.COMBEE]: [ - new SpeciesEvolution(Species.VESPIQUEN, 21, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)) + new SpeciesEvolution(Species.VESPIQUEN, 21, null, new GenderEvolutionCondition(Gender.FEMALE)) ], [Species.BUIZEL]: [ new SpeciesEvolution(Species.FLOATZEL, 26, null, null) @@ -661,7 +854,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.LUMINEON, 31, null, null) ], [Species.MANTYKE]: [ - new SpeciesEvolution(Species.MANTINE, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.REMORAID].caughtAttr), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.MANTINE, 32, null, new CaughtEvolutionCondition(Species.REMORAID), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.SNOVER]: [ new SpeciesEvolution(Species.ABOMASNOW, 40, null, null) @@ -682,8 +875,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DEWOTT, 17, null, null) ], [Species.DEWOTT]: [ - new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.SAMUROTT, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.SAMUROTT, 36, null, new TimeOfDayEvolutionCondition("day")) ], [Species.PATRAT]: [ new SpeciesEvolution(Species.WATCHOG, 20, null, null) @@ -833,8 +1026,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.KINGAMBIT, 1, EvolutionItem.LEADERS_CREST, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.RUFFLET]: [ - new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.BRAVIARY, 54, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.BRAVIARY, 54, null, new TimeOfDayEvolutionCondition("day")) ], [Species.VULLABY]: [ new SpeciesEvolution(Species.MANDIBUZZ, 54, null, null) @@ -891,11 +1084,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GOGOAT, 32, null, null) ], [Species.PANCHAM]: [ - new SpeciesEvolution(Species.PANGORO, 32, null, new SpeciesEvolutionCondition(p => !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(Type.DARK) > -1)), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.PANGORO, 32, null, new PartyTypeEvolutionCondition(Type.DARK), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.ESPURR]: [ - new SpeciesFormEvolution(Species.MEOWSTIC, "", "female", 25, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)), - new SpeciesFormEvolution(Species.MEOWSTIC, "", "", 25, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE)) + new SpeciesFormEvolution(Species.MEOWSTIC, "", "female", 25, null, new GenderEvolutionCondition(Gender.FEMALE)), + new SpeciesFormEvolution(Species.MEOWSTIC, "", "", 25, null, new GenderEvolutionCondition(Gender.MALE)) ], [Species.HONEDGE]: [ new SpeciesEvolution(Species.DOUBLADE, 35, null, null) @@ -913,21 +1106,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CLAWITZER, 37, null, null) ], [Species.TYRUNT]: [ - new SpeciesEvolution(Species.TYRANTRUM, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.TYRANTRUM, 39, null, new TimeOfDayEvolutionCondition("day")) ], [Species.AMAURA]: [ - new SpeciesEvolution(Species.AURORUS, 39, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.AURORUS, 39, null, new TimeOfDayEvolutionCondition("night")) ], [Species.GOOMY]: [ - new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.SLIGGOO, 40, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.SLIGGOO, 40, null, new TimeOfDayEvolutionCondition("day")) ], [Species.SLIGGOO]: [ - new SpeciesEvolution(Species.GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) ], [Species.BERGMITE]: [ - new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.AVALUGG, 37, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_AVALUGG, 37, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.AVALUGG, 37, null, new TimeOfDayEvolutionCondition("day")) ], [Species.NOIBAT]: [ new SpeciesEvolution(Species.NOIVERN, 48, null, null) @@ -936,8 +1129,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DARTRIX, 17, null, null) ], [Species.DARTRIX]: [ - new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), - new SpeciesEvolution(Species.DECIDUEYE, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.HISUI_DECIDUEYE, 36, null, new TimeOfDayEvolutionCondition("night")), + new SpeciesEvolution(Species.DECIDUEYE, 34, null, new TimeOfDayEvolutionCondition("day")) ], [Species.LITTEN]: [ new SpeciesEvolution(Species.TORRACAT, 17, null, null) @@ -958,7 +1151,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.TOUCANNON, 28, null, null) ], [Species.YUNGOOS]: [ - new SpeciesEvolution(Species.GUMSHOOS, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.GUMSHOOS, 20, null, new TimeOfDayEvolutionCondition("day")) ], [Species.GRUBBIN]: [ new SpeciesEvolution(Species.CHARJABUG, 20, null, null) @@ -976,13 +1169,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ARAQUANID, 22, null, null) ], [Species.FOMANTIS]: [ - new SpeciesEvolution(Species.LURANTIS, 34, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)) + new SpeciesEvolution(Species.LURANTIS, 34, null, new TimeOfDayEvolutionCondition("day")) ], [Species.MORELULL]: [ new SpeciesEvolution(Species.SHIINOTIC, 24, null, null) ], [Species.SALANDIT]: [ - new SpeciesEvolution(Species.SALAZZLE, 33, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)) + new SpeciesEvolution(Species.SALAZZLE, 33, null, new GenderEvolutionCondition(Gender.FEMALE)) ], [Species.STUFFUL]: [ new SpeciesEvolution(Species.BEWEAR, 27, null, null) @@ -1013,7 +1206,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.MELMETAL, 48, null, null) ], [Species.ALOLA_RATTATA]: [ - new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.ALOLA_RATICATE, 20, null, new TimeOfDayEvolutionCondition("night")) ], [Species.ALOLA_DIGLETT]: [ new SpeciesEvolution(Species.ALOLA_DUGTRIO, 26, null, null) @@ -1086,7 +1279,8 @@ export const pokemonEvolutions: PokemonEvolutions = { ], [Species.TOXEL]: [ new SpeciesFormEvolution(Species.TOXTRICITY, "", "lowkey", 30, null, - new SpeciesEvolutionCondition(p => [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ].indexOf(p.getNature()) > -1)), + new NatureEvolutionCondition([ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]) + ), new SpeciesFormEvolution(Species.TOXTRICITY, "", "amped", 30, null, null) ], [Species.SIZZLIPEDE]: [ @@ -1136,7 +1330,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GALAR_LINOONE, 20, null, null) ], [Species.GALAR_LINOONE]: [ - new SpeciesEvolution(Species.OBSTAGOON, 35, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.OBSTAGOON, 35, null, new TimeOfDayEvolutionCondition("night")) ], [Species.GALAR_YAMASK]: [ new SpeciesEvolution(Species.RUNERIGUS, 34, null, null) @@ -1145,7 +1339,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.HISUI_ZOROARK, 30, null, null) ], [Species.HISUI_SLIGGOO]: [ - new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new SpeciesEvolutionCondition(p => [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ].indexOf(globalScene.arena.weather?.weatherType || WeatherType.NONE) > -1), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.HISUI_GOODRA, 50, null, new WeatherEvolutionCondition([ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]), SpeciesWildEvolutionDelay.LONG) ], [Species.SPRIGATITO]: [ new SpeciesEvolution(Species.FLORAGATO, 16, null, null) @@ -1166,8 +1360,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.QUAQUAVAL, 36, null, null) ], [Species.LECHONK]: [ - new SpeciesFormEvolution(Species.OINKOLOGNE, "", "female", 18, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE)), - new SpeciesFormEvolution(Species.OINKOLOGNE, "", "", 18, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE)) + new SpeciesFormEvolution(Species.OINKOLOGNE, "", "female", 18, null, new GenderEvolutionCondition(Gender.FEMALE)), + new SpeciesFormEvolution(Species.OINKOLOGNE, "", "", 18, null, new GenderEvolutionCondition(Gender.MALE)) ], [Species.TAROUNTULA]: [ new SpeciesEvolution(Species.SPIDOPS, 15, null, null) @@ -1182,11 +1376,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.PAWMOT, 32, null, null) ], [Species.TANDEMAUS]: [ - new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new SpeciesEvolutionCondition(p => { - let ret = false; - globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); - return ret; - })), + new SpeciesFormEvolution(Species.MAUSHOLD, "", "three", 25, null, new TandemausEvolutionCondition()), new SpeciesEvolution(Species.MAUSHOLD, 25, null, null) ], [Species.FIDOUGH]: [ @@ -1244,7 +1434,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GLIMMORA, 35, null, null) ], [Species.GREAVARD]: [ - new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)) + new SpeciesEvolution(Species.HOUNDSTONE, 30, null, new TimeOfDayEvolutionCondition("night")) ], [Species.FRIGIBAX]: [ new SpeciesEvolution(Species.ARCTIBAX, 35, null, null) @@ -1301,21 +1491,21 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.EXEGGUTOR, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.TANGELA]: [ - new SpeciesEvolution(Species.TANGROWTH, 34, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.TANGROWTH, 34, null, new MoveEvolutionCondition(Moves.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) ], [Species.LICKITUNG]: [ - new SpeciesEvolution(Species.LICKILICKY, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ROLLOUT).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.LICKILICKY, 32, null, new MoveEvolutionCondition(Moves.ROLLOUT), SpeciesWildEvolutionDelay.LONG) ], [Species.STARYU]: [ new SpeciesEvolution(Species.STARMIE, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.EEVEE]: [ - new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => !!p.getMoveset().find(m => m?.getMove().type === Type.FAIRY)), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.SYLVEON, "", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, Type.FAIRY), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.SYLVEON, "partner", "", 1, null, new FriendshipMoveTypeEvolutionCondition(120, Type.FAIRY), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ESPEON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ESPEON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.UMBREON, "", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.UMBREON, "partner", "", 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "night"), SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.VAPOREON, "", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.VAPOREON, "partner", "", 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.JOLTEON, "", "", 1, EvolutionItem.THUNDER_STONE, null, SpeciesWildEvolutionDelay.LONG), @@ -1331,13 +1521,13 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.TOGEKISS, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.AIPOM]: [ - new SpeciesEvolution(Species.AMBIPOM, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DOUBLE_HIT).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.AMBIPOM, 32, null, new MoveEvolutionCondition(Moves.DOUBLE_HIT), SpeciesWildEvolutionDelay.LONG) ], [Species.SUNKERN]: [ new SpeciesEvolution(Species.SUNFLORA, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.YANMA]: [ - new SpeciesEvolution(Species.YANMEGA, 33, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.YANMEGA, 33, null, new MoveEvolutionCondition(Moves.ANCIENT_POWER), SpeciesWildEvolutionDelay.LONG) ], [Species.MURKROW]: [ new SpeciesEvolution(Species.HONCHKROW, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1346,32 +1536,26 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.MISMAGIUS, 1, EvolutionItem.DUSK_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.GIRAFARIG]: [ - new SpeciesEvolution(Species.FARIGIRAF, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TWIN_BEAM).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.FARIGIRAF, 32, null, new MoveEvolutionCondition(Moves.TWIN_BEAM), SpeciesWildEvolutionDelay.LONG) ], [Species.DUNSPARCE]: [ - new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new SpeciesEvolutionCondition(p => { - let ret = false; - if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) { - globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); - } - return ret; - }), SpeciesWildEvolutionDelay.LONG), - new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(Species.DUDUNSPARCE, "", "three-segment", 32, null, new DunsparceEvolutionCondition(), SpeciesWildEvolutionDelay.LONG), + new SpeciesEvolution(Species.DUDUNSPARCE, 32, null, new MoveEvolutionCondition(Moves.HYPER_DRILL), SpeciesWildEvolutionDelay.LONG) ], [Species.GLIGAR]: [ - new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor fang at night*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.GLISCOR, 1, EvolutionItem.RAZOR_FANG, new TimeOfDayEvolutionCondition("night") /* Razor fang at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SNEASEL]: [ - new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT /* Razor claw at night*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.WEAVILE, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("night") /* Razor claw at night*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.URSARING]: [ new SpeciesEvolution(Species.URSALUNA, 1, EvolutionItem.PEAT_BLOCK, null, SpeciesWildEvolutionDelay.VERY_LONG) //Ursaring does not evolve into Bloodmoon Ursaluna ], [Species.PILOSWINE]: [ - new SpeciesEvolution(Species.MAMOSWINE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.ANCIENT_POWER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.MAMOSWINE, 1, null, new MoveEvolutionCondition(Moves.ANCIENT_POWER), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.STANTLER]: [ - new SpeciesEvolution(Species.WYRDEER, 25, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.PSYSHIELD_BASH).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.WYRDEER, 25, null, new MoveEvolutionCondition(Moves.PSYSHIELD_BASH), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.LOMBRE]: [ new SpeciesEvolution(Species.LUDICOLO, 1, EvolutionItem.WATER_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1389,11 +1573,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ROSERADE, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.BONSLY]: [ - new SpeciesEvolution(Species.SUDOWOODO, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.SUDOWOODO, 1, null, new MoveEvolutionCondition(Moves.MIMIC), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.MIME_JR]: [ - new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)), SpeciesWildEvolutionDelay.MEDIUM), - new SpeciesEvolution(Species.MR_MIME, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.MIMIC).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.GALAR_MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(Moves.MIMIC, "night"), SpeciesWildEvolutionDelay.MEDIUM), + new SpeciesEvolution(Species.MR_MIME, 1, null, new MoveTimeOfDayEvolutionCondition(Moves.MIMIC, "day"), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.PANSAGE]: [ new SpeciesEvolution(Species.SIMISAGE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1415,8 +1599,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.LILLIGANT, 1, EvolutionItem.SUN_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.BASCULIN]: [ - new SpeciesFormEvolution(Species.BASCULEGION, "white-striped", "female", 40, null, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(Species.BASCULEGION, "white-striped", "male", 40, null, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(Species.BASCULEGION, "white-striped", "female", 40, null, new GenderEvolutionCondition(Gender.FEMALE), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(Species.BASCULEGION, "white-striped", "male", 40, null, new GenderEvolutionCondition(Gender.MALE), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.MINCCINO]: [ new SpeciesEvolution(Species.CINCCINO, 1, EvolutionItem.SHINY_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1443,15 +1627,15 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CRABOMINABLE, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.ROCKRUFF]: [ - new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))), - new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)), - new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0))) + new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, null), + new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new TimeOfDayEvolutionCondition("day")), + new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new TimeOfDayEvolutionCondition("night")) ], [Species.STEENEE]: [ - new SpeciesEvolution(Species.TSAREENA, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.STOMP).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.TSAREENA, 28, null, new MoveEvolutionCondition(Moves.STOMP), SpeciesWildEvolutionDelay.LONG) ], [Species.POIPOLE]: [ - new SpeciesEvolution(Species.NAGANADEL, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_PULSE).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.NAGANADEL, 1, null, new MoveEvolutionCondition(Moves.DRAGON_PULSE), SpeciesWildEvolutionDelay.LONG) ], [Species.ALOLA_SANDSHREW]: [ new SpeciesEvolution(Species.ALOLA_SANDSLASH, 1, EvolutionItem.ICE_STONE, null, SpeciesWildEvolutionDelay.LONG) @@ -1465,22 +1649,40 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.APPLETUN, 1, EvolutionItem.SWEET_APPLE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.CLOBBOPUS]: [ - new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.TAUNT).length > 0)/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) + new SpeciesEvolution(Species.GRAPPLOCT, 35, null, new MoveEvolutionCondition(Moves.TAUNT)/*Once Taunt is implemented, change evo level to 1 and delay to LONG*/) ], [Species.SINISTEA]: [ new SpeciesFormEvolution(Species.POLTEAGEIST, "phony", "phony", 1, EvolutionItem.CRACKED_POT, null, SpeciesWildEvolutionDelay.LONG), new SpeciesFormEvolution(Species.POLTEAGEIST, "antique", "antique", 1, EvolutionItem.CHIPPED_POT, null, SpeciesWildEvolutionDelay.LONG) ], [Species.MILCERY]: [ - new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TOWN || globalScene.arena.biomeType === Biome.PLAINS || globalScene.arena.biomeType === Biome.GRASS || globalScene.arena.biomeType === Biome.TALL_GRASS || globalScene.arena.biomeType === Biome.METROPOLIS), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.BADLANDS || globalScene.arena.biomeType === Biome.VOLCANO || globalScene.arena.biomeType === Biome.GRAVEYARD || globalScene.arena.biomeType === Biome.FACTORY || globalScene.arena.biomeType === Biome.SLUM), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.FOREST || globalScene.arena.biomeType === Biome.SWAMP || globalScene.arena.biomeType === Biome.MEADOW || globalScene.arena.biomeType === Biome.JUNGLE), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.SEA || globalScene.arena.biomeType === Biome.BEACH || globalScene.arena.biomeType === Biome.LAKE || globalScene.arena.biomeType === Biome.SEABED), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.DESERT || globalScene.arena.biomeType === Biome.POWER_PLANT || globalScene.arena.biomeType === Biome.DOJO || globalScene.arena.biomeType === Biome.RUINS || globalScene.arena.biomeType === Biome.CONSTRUCTION_SITE), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.MOUNTAIN || globalScene.arena.biomeType === Biome.CAVE || globalScene.arena.biomeType === Biome.ICE_CAVE || globalScene.arena.biomeType === Biome.FAIRY_CAVE || globalScene.arena.biomeType === Biome.SNOWY_FOREST), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.WASTELAND || globalScene.arena.biomeType === Biome.LABORATORY), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.TEMPLE || globalScene.arena.biomeType === Biome.ISLAND), SpeciesWildEvolutionDelay.LONG), - new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, new SpeciesEvolutionCondition(p => globalScene.arena.biomeType === Biome.ABYSS || globalScene.arena.biomeType === Biome.SPACE || globalScene.arena.biomeType === Biome.END), SpeciesWildEvolutionDelay.LONG) + new SpeciesFormEvolution(Species.ALCREMIE, "", "vanilla-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.TOWN, Biome.PLAINS, Biome.GRASS, Biome.TALL_GRASS, Biome.METROPOLIS ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.BADLANDS, Biome.VOLCANO, Biome.GRAVEYARD, Biome.FACTORY, Biome.SLUM ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "matcha-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.FOREST, Biome.SWAMP, Biome.MEADOW, Biome.JUNGLE ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "mint-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.SEA, Biome.BEACH, Biome.LAKE, Biome.SEABED ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "lemon-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.DESERT, Biome.POWER_PLANT, Biome.DOJO, Biome.RUINS, Biome.CONSTRUCTION_SITE ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "salted-cream", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.MOUNTAIN, Biome.CAVE, Biome.ICE_CAVE, Biome.FAIRY_CAVE, Biome.SNOWY_FOREST ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "ruby-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.WASTELAND, Biome.LABORATORY ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "caramel-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.TEMPLE, Biome.ISLAND ]), + SpeciesWildEvolutionDelay.LONG), + new SpeciesFormEvolution(Species.ALCREMIE, "", "rainbow-swirl", 1, EvolutionItem.STRAWBERRY_SWEET, + new BiomeEvolutionCondition([ Biome.ABYSS, Biome.SPACE, Biome.END ]), + SpeciesWildEvolutionDelay.LONG) ], [Species.DURALUDON]: [ new SpeciesFormEvolution(Species.ARCHALUDON, "", "", 1, EvolutionItem.METAL_ALLOY, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1499,10 +1701,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.HISUI_ELECTRODE, 1, EvolutionItem.LEAF_STONE, null, SpeciesWildEvolutionDelay.LONG) ], [Species.HISUI_QWILFISH]: [ - new SpeciesEvolution(Species.OVERQWIL, 28, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.BARB_BARRAGE).length > 0), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.OVERQWIL, 28, null, new MoveEvolutionCondition(Moves.BARB_BARRAGE), SpeciesWildEvolutionDelay.LONG) ], [Species.HISUI_SNEASEL]: [ - new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new SpeciesEvolutionCondition(p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY /* Razor claw at day*/), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.SNEASLER, 1, EvolutionItem.RAZOR_CLAW, new TimeOfDayEvolutionCondition("day") /* Razor claw at day*/, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.CHARCADET]: [ new SpeciesEvolution(Species.ARMAROUGE, 1, EvolutionItem.AUSPICIOUS_ARMOR, null, SpeciesWildEvolutionDelay.LONG), @@ -1522,7 +1724,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(Species.SINISTCHA, "artisan", "masterpiece", 1, EvolutionItem.MASTERPIECE_TEACUP, null, SpeciesWildEvolutionDelay.LONG) ], [Species.DIPPLIN]: [ - new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.DRAGON_CHEER).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.HYDRAPPLE, 1, null, new MoveEvolutionCondition(Moves.DRAGON_CHEER), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.KADABRA]: [ new SpeciesEvolution(Species.ALAKAZAM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1537,9 +1739,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.GENGAR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.ONIX]: [ - new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( - p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0), - SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.STEELIX, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(Type.STEEL), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.RHYDON]: [ new SpeciesEvolution(Species.RHYPERIOR, 1, EvolutionItem.PROTECTOR, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1548,9 +1748,7 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.KINGDRA, 1, EvolutionItem.DRAGON_SCALE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SCYTHER]: [ - new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition( - p => p.moveset.filter(m => m?.getMove().type === Type.STEEL).length > 0), - SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(Species.SCIZOR, 1, EvolutionItem.LINKING_CORD, new MoveTypeEvolutionCondition(Type.STEEL), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesEvolution(Species.KLEAVOR, 1, EvolutionItem.BLACK_AUGURITE, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.ELECTABUZZ]: [ @@ -1572,8 +1770,8 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.DUSKNOIR, 1, EvolutionItem.REAPER_CLOTH, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.CLAMPERL]: [ - new SpeciesEvolution(Species.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.gender === Gender.MALE, p => p.gender = Gender.MALE /* Deep Sea Tooth */), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesEvolution(Species.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => p.gender === Gender.FEMALE, p => p.gender = Gender.FEMALE /* Deep Sea Scale */), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.HUNTAIL, 1, EvolutionItem.LINKING_CORD, new GenderEvolutionCondition(Gender.MALE /* Deep Sea Tooth */), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesEvolution(Species.GOREBYSS, 1, EvolutionItem.LINKING_CORD, new GenderEvolutionCondition(Gender.FEMALE /* Deep Sea Scale */), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.BOLDORE]: [ new SpeciesEvolution(Species.GIGALITH, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1582,10 +1780,10 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.CONKELDURR, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.KARRABLAST]: [ - new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.SHELMET].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.ESCAVALIER, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(Species.SHELMET), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SHELMET]: [ - new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new SpeciesEvolutionCondition(p => !!globalScene.gameData.dexData[Species.KARRABLAST].caughtAttr), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.ACCELGOR, 1, EvolutionItem.LINKING_CORD, new CaughtEvolutionCondition(Species.KARRABLAST), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.SPRITZEE]: [ new SpeciesEvolution(Species.AROMATISSE, 1, EvolutionItem.SACHET, null, SpeciesWildEvolutionDelay.VERY_LONG) @@ -1603,72 +1801,66 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesEvolution(Species.ALOLA_GOLEM, 1, EvolutionItem.LINKING_CORD, null, SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.PRIMEAPE]: [ - new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new SpeciesEvolutionCondition(p => p.moveset.filter(m => m?.moveId === Moves.RAGE_FIST).length > 0), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.ANNIHILAPE, 35, null, new MoveEvolutionCondition(Moves.RAGE_FIST), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.GOLBAT]: [ - new SpeciesEvolution(Species.CROBAT, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesEvolution(Species.CROBAT, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.VERY_LONG) ], [Species.CHANSEY]: [ - new SpeciesEvolution(Species.BLISSEY, 1, null, new SpeciesFriendshipEvolutionCondition(200), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.BLISSEY, 1, null, new FriendshipEvolutionCondition(200), SpeciesWildEvolutionDelay.LONG) ], [Species.PICHU]: [ - new SpeciesFormEvolution(Species.PIKACHU, "spiky", "partner", 1, null, new SpeciesFriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), - new SpeciesFormEvolution(Species.PIKACHU, "", "", 1, null, new SpeciesFriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(Species.PIKACHU, "spiky", "partner", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), + new SpeciesFormEvolution(Species.PIKACHU, "", "", 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.SHORT), ], [Species.CLEFFA]: [ - new SpeciesEvolution(Species.CLEFAIRY, 1, null, new SpeciesFriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.CLEFAIRY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) ], [Species.IGGLYBUFF]: [ - new SpeciesEvolution(Species.JIGGLYPUFF, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.JIGGLYPUFF, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) ], [Species.TOGEPI]: [ - new SpeciesEvolution(Species.TOGETIC, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.TOGETIC, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) ], [Species.AZURILL]: [ - new SpeciesEvolution(Species.MARILL, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.MARILL, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.SHORT) ], [Species.BUDEW]: [ - new SpeciesEvolution(Species.ROSELIA, 1, null, new SpeciesFriendshipEvolutionCondition(70, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.ROSELIA, 1, null, new FriendshipTimeOfDayEvolutionCondition(70, "day"), SpeciesWildEvolutionDelay.SHORT) ], [Species.BUNEARY]: [ - new SpeciesEvolution(Species.LOPUNNY, 1, null, new SpeciesFriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.LOPUNNY, 1, null, new FriendshipEvolutionCondition(70), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.CHINGLING]: [ - new SpeciesEvolution(Species.CHIMECHO, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.CHIMECHO, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.HAPPINY]: [ - new SpeciesEvolution(Species.CHANSEY, 1, null, new SpeciesFriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) + new SpeciesEvolution(Species.CHANSEY, 1, null, new FriendshipEvolutionCondition(160), SpeciesWildEvolutionDelay.SHORT) ], [Species.MUNCHLAX]: [ - new SpeciesEvolution(Species.SNORLAX, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.SNORLAX, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) ], [Species.RIOLU]: [ - new SpeciesEvolution(Species.LUCARIO, 1, null, new SpeciesFriendshipEvolutionCondition(120, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.LUCARIO, 1, null, new FriendshipTimeOfDayEvolutionCondition(120, "day"), SpeciesWildEvolutionDelay.LONG) ], [Species.WOOBAT]: [ - new SpeciesEvolution(Species.SWOOBAT, 1, null, new SpeciesFriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.SWOOBAT, 1, null, new FriendshipEvolutionCondition(90), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.SWADLOON]: [ - new SpeciesEvolution(Species.LEAVANNY, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.LEAVANNY, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) ], [Species.TYPE_NULL]: [ - new SpeciesEvolution(Species.SILVALLY, 1, null, new SpeciesFriendshipEvolutionCondition(100), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.SILVALLY, 1, null, new FriendshipEvolutionCondition(100), SpeciesWildEvolutionDelay.LONG) ], [Species.ALOLA_MEOWTH]: [ - new SpeciesEvolution(Species.ALOLA_PERSIAN, 1, null, new SpeciesFriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) + new SpeciesEvolution(Species.ALOLA_PERSIAN, 1, null, new FriendshipEvolutionCondition(120), SpeciesWildEvolutionDelay.LONG) ], [Species.SNOM]: [ - new SpeciesEvolution(Species.FROSMOTH, 1, null, new SpeciesFriendshipEvolutionCondition(90, p => globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT), SpeciesWildEvolutionDelay.MEDIUM) + new SpeciesEvolution(Species.FROSMOTH, 1, null, new FriendshipTimeOfDayEvolutionCondition(90, "night"), SpeciesWildEvolutionDelay.MEDIUM) ], [Species.GIMMIGHOUL]: [ - new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter - + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), - new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter - + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + globalScene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) + new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG), + new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new TreasureEvolutionCondition(), SpeciesWildEvolutionDelay.VERY_LONG) ] }; @@ -1691,3 +1883,19 @@ export function initPokemonPrevolutions(): void { } }); } + + +// TODO: This may cause funny business for double starters such as Pichu/Pikachu +export const pokemonStarters: PokemonPrevolutions = {}; + +export function initPokemonStarters(): void { + const starterKeys = Object.keys(pokemonPrevolutions); + starterKeys.forEach(pk => { + const prevolution = pokemonPrevolutions[pk]; + if (speciesStarterCosts.hasOwnProperty(prevolution)) { + pokemonStarters[pk] = prevolution; + } else { + pokemonStarters[pk] = pokemonPrevolutions[prevolution]; + } + }); +} diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 3a58ff4a99d..4c68de5abc5 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -18,7 +18,7 @@ import { MoveFlags, StatusCategoryOnAllyAttr } from "#app/data/move"; -import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { Type } from "#enums/type"; @@ -2149,7 +2149,7 @@ export class FormBlockDamageTag extends BattlerTag { super.onAdd(pokemon); if (pokemon.formIndex !== 0) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } } @@ -2161,7 +2161,7 @@ export class FormBlockDamageTag extends BattlerTag { onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } } /** Provides the additional weather-based effects of the Ice Face ability */ @@ -2361,12 +2361,12 @@ export class GulpMissileTag extends BattlerTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } } diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index d0a44504763..ca6b384cfbb 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -16,7 +16,7 @@ import { Nature } from "#enums/nature"; import { Type } from "#enums/type"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; -import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; @@ -198,7 +198,7 @@ function endTrainerBattleAndShowDialogue(): Promise { // Only trigger form change when Eiscue is in Noice form // Hardcoded Eiscue for now in case it is fused with another pokemon if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) { - globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeAbilityTrigger); } pokemon.resetBattleData(); diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index b1c3db47768..035cd6f1369 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -212,6 +212,8 @@ export class SpeciesFormChangeCondition { } export abstract class SpeciesFormChangeTrigger { + public description: string = ""; + canChange(pokemon: Pokemon): boolean { return true; } @@ -222,16 +224,19 @@ export abstract class SpeciesFormChangeTrigger { } export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger { - canChange(pokemon: Pokemon): boolean { - return true; - } +} + +export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { + public description: string = i18next.t("pokemonEvolutions:Forms.ability"); } export class SpeciesFormChangeCompoundTrigger { + public description: string = ""; public triggers: SpeciesFormChangeTrigger[]; constructor(...triggers: SpeciesFormChangeTrigger[]) { this.triggers = triggers; + this.description = this.triggers.filter(trigger => trigger?.description?.length > 0).map(trigger => trigger.description).join(", "); } canChange(pokemon: Pokemon): boolean { @@ -257,6 +262,9 @@ export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { super(); this.item = item; this.active = active; + this.description = this.active ? + i18next.t("pokemonEvolutions:Forms.item", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) }) : + i18next.t("pokemonEvolutions:Forms.deactivateItem", { item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`) }); } canChange(pokemon: Pokemon): boolean { @@ -270,6 +278,7 @@ export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger constructor(...timesOfDay: TimeOfDay[]) { super(); this.timesOfDay = timesOfDay; + this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); } canChange(pokemon: Pokemon): boolean { @@ -283,6 +292,7 @@ export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { constructor(active: boolean = false) { super(); this.active = active; + this.description = this.active ? i18next.t("pokemonEvolutions:Forms.enter") : i18next.t("pokemonEvolutions:Forms.leave"); } canChange(pokemon: Pokemon): boolean { @@ -301,6 +311,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg } this.statusEffects = statusEffects; this.invert = invert; + this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); } canChange(pokemon: Pokemon): boolean { @@ -316,6 +327,8 @@ export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigge super(); this.move = move; this.known = known; + const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string; + this.description = i18next.t("pokemonEvolutions:Forms.moveLearned", { move: i18next.t(`move:${moveKey}.name`) }); } canChange(pokemon: Pokemon): boolean { @@ -335,6 +348,8 @@ export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrig } export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.preMove"); + canChange(pokemon: Pokemon): boolean { const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; return !!command?.move && this.movePredicate(command.move.move) === this.used; @@ -342,6 +357,8 @@ export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigge } export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.postMove"); + canChange(pokemon: Pokemon): boolean { return pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used; } @@ -367,6 +384,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { constructor(formKey: string) { super(); this.formKey = formKey; + this.description = ""; } canChange(pokemon: Pokemon): boolean { @@ -386,6 +404,7 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { constructor(teraType: Type) { super(); this.teraType = teraType; + this.description = i18next.t("pokemonEvolutions:Forms.tera", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } /** @@ -404,6 +423,8 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { * @extends SpeciesFormChangeTrigger */ export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { + description = i18next.t("pokemonEvolutions:Forms.teraLapse"); + canChange(pokemon: Pokemon): boolean { return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id); } @@ -424,6 +445,7 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { super(); this.ability = ability; this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weather"); } /** @@ -457,6 +479,7 @@ export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChange super(); this.ability = ability; this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert"); } /** @@ -751,8 +774,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.ARCEUS, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.PIXIE_PLATE)) ], [Species.DARMANITAN]: [ - new SpeciesFormChange(Species.DARMANITAN, "", "zen", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.DARMANITAN, "zen", "", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.DARMANITAN, "zen", "", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.GARBODOR]: [ new SpeciesFormChange(Species.GARBODOR, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) @@ -785,12 +808,12 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.GENESECT, "", "douse", new SpeciesFormChangeItemTrigger(FormChangeItem.DOUSE_DRIVE)) ], [Species.GRENINJA]: [ - new SpeciesFormChange(Species.GRENINJA, "battle-bond", "ash", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.GRENINJA, "ash", "battle-bond", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.GRENINJA, "battle-bond", "ash", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.GRENINJA, "ash", "battle-bond", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.PALAFIN] : [ - new SpeciesFormChange(Species.PALAFIN, "zero", "hero", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.PALAFIN, "hero", "zero", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.PALAFIN, "zero", "hero", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.PALAFIN, "hero", "zero", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.AEGISLASH]: [ new SpeciesFormChange(Species.AEGISLASH, "blade", "shield", new SpeciesFormChangePreMoveTrigger(Moves.KINGS_SHIELD), true, new SpeciesFormChangeCondition(p => p.hasAbility(Abilities.STANCE_CHANGE))), @@ -802,10 +825,10 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.XERNEAS, "active", "neutral", new SpeciesFormChangeActiveTrigger(false), true) ], [Species.ZYGARDE]: [ - new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.DIANCIE]: [ new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE)) @@ -814,8 +837,8 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.HOOPA, "", "unbound", new SpeciesFormChangeItemTrigger(FormChangeItem.PRISON_BOTTLE)) ], [Species.WISHIWASHI]: [ - new SpeciesFormChange(Species.WISHIWASHI, "", "school", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.WISHIWASHI, "school", "", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.WISHIWASHI, "", "school", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.WISHIWASHI, "school", "", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.SILVALLY]: [ new SpeciesFormChange(Species.SILVALLY, "normal", "fighting", new SpeciesFormChangeItemTrigger(FormChangeItem.FIGHTING_MEMORY)), @@ -837,24 +860,24 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.SILVALLY, "normal", "fairy", new SpeciesFormChangeItemTrigger(FormChangeItem.FAIRY_MEMORY)) ], [Species.MINIOR]: [ - new SpeciesFormChange(Species.MINIOR, "red-meteor", "red", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "red", "red-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "orange-meteor", "orange", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "orange", "orange-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "yellow-meteor", "yellow", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "yellow", "yellow-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "green-meteor", "green", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "green", "green-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "blue-meteor", "blue", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "blue", "blue-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "indigo-meteor", "indigo", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "indigo", "indigo-meteor", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "violet-meteor", "violet", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MINIOR, "violet", "violet-meteor", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.MINIOR, "red-meteor", "red", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "red", "red-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "orange-meteor", "orange", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "orange", "orange-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "yellow-meteor", "yellow", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "yellow", "yellow-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "green-meteor", "green", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "green", "green-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "blue-meteor", "blue", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "blue", "blue-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "indigo-meteor", "indigo", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "indigo", "indigo-meteor", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "violet-meteor", "violet", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MINIOR, "violet", "violet-meteor", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.MIMIKYU]: [ - new SpeciesFormChange(Species.MIMIKYU, "disguised", "busted", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MIMIKYU, "busted", "disguised", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.MIMIKYU, "disguised", "busted", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MIMIKYU, "busted", "disguised", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.NECROZMA]: [ new SpeciesFormChange(Species.NECROZMA, "", "dawn-wings", new SpeciesFormChangeItemTrigger(FormChangeItem.N_LUNARIZER), false, getSpeciesDependentFormChangeCondition(Species.LUNALA)), @@ -896,10 +919,10 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.SANDACONDA, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) ], [Species.CRAMORANT]: [ - new SpeciesFormChange(Species.CRAMORANT, "", "gulping", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() >= .5)), - new SpeciesFormChange(Species.CRAMORANT, "", "gorging", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() < .5)), - new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeManualTrigger, true), - new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeManualTrigger, true), + new SpeciesFormChange(Species.CRAMORANT, "", "gulping", new SpeciesFormChangeAbilityTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() >= .5)), + new SpeciesFormChange(Species.CRAMORANT, "", "gorging", new SpeciesFormChangeAbilityTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() < .5)), + new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeAbilityTrigger, true), + new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeAbilityTrigger, true), new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeActiveTrigger(false), true), new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeActiveTrigger(false), true) ], @@ -930,12 +953,12 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.ALCREMIE, "rainbow-swirl", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) ], [Species.EISCUE]: [ - new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.MORPEKO]: [ - new SpeciesFormChange(Species.MORPEKO, "full-belly", "hangry", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.MORPEKO, "hangry", "full-belly", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.MORPEKO, "full-belly", "hangry", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.MORPEKO, "hangry", "full-belly", new SpeciesFormChangeAbilityTrigger(), true) ], [Species.COPPERAJAH]: [ new SpeciesFormChange(Species.COPPERAJAH, "", SpeciesFormKey.GIGANTAMAX, new SpeciesFormChangeItemTrigger(FormChangeItem.MAX_MUSHROOMS)) @@ -978,13 +1001,13 @@ export const pokemonFormChanges: PokemonFormChanges = { new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.ROCK)) ], [Species.TERAPAGOS]: [ - new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true), new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(Type.STELLAR)), new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.STELLAR)) ], [Species.GALAR_DARMANITAN]: [ - new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.GALAR_DARMANITAN, "zen", "", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), + new SpeciesFormChange(Species.GALAR_DARMANITAN, "zen", "", new SpeciesFormChangeAbilityTrigger(), true) ], }; @@ -1002,3 +1025,4 @@ export function initPokemonForms() { formChanges.push(...newFormChanges); }); } + diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 845739dfcac..cc798bc8585 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -204,6 +204,7 @@ export async function initI18n(): Promise { "nature", "pokeball", "pokemon", + "pokemonEvolutions", "pokemonForm", "pokemonInfo", "pokemonInfoContainer", From 134c6e928f9b1bbbb055367181a3a7bb3bac5a95 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:15:48 +0100 Subject: [PATCH 44/54] [UI/UX] New version of the 0 (zero) of pokemon-emerald-pro font (#5167) --- public/fonts/pokemon-emerald-pro.ttf | Bin 93816 -> 93788 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index e4ee49dbff37948936b7770f697ab143b9f434d5..758130172c76eb3ddd06ced11258f00e8b92671e 100644 GIT binary patch delta 2255 zcmW+&aY)(?8vbQwX6DMw%$b>)bIzGFGqbW^W@cq(W#*chIWse7u3WQbYUWyNX4{B} zi0F&hH@b-Mi+%B3gme+RT|~s_y3s{+5fS6N5nV(?cis2G^S&=U@H~G$&&6)o?rsOG6(qz`8|{nDhgGEnnV4elc6i2Cr}Pl z2DL(mp>xnx=nnKc3=>8R1H)`#p0Me#wXh=?1jc|VVP4n>Y!0>sI}N9WmxNox-QiQ= zf$*>4`{5UGESv?egqz?#_y_m~{0M##fsFtoq!GplXT*dnVliSf;s}95un}T}5#d9u zAdVv8k&H-Qq&m_bIS{!Nd5B~nl}HmZfLx72MbV=4QMRa+sEw!-6cj~ADN$xrKWY*+ zi&{c$qQ2jv-7?);j7CTEqBYUp=(*^<7-$STrZUD6Gaj=PbAX1R$!H26P6S9@JKujuf%uY$MB!=7qIdaDcdRE zDFB5?VNinFlDd|9s|kXGL(!q#x!H+ zKJq?z-*SKA{+IjbOd?ak>|>5ISC|KBh%|PZBCRcLB<)k$Aq&OIVd+>-)*Nf^0sH~e z^+5H&`C#_JHXFg_vrX&~_A2`6nc@cDctzl%S`-{gPKhGo;TdD*qu*6fk&+3fY~{TxgV zFQ+!gku#dJnDZs)C>NPa%ms4|x#PLP++zV;z!fM2eS#^$g5W@KnMcVJ=e6Zc1CTGAdb=Y)LMQ$;Bnb#$tc*eDQMex8kc3YKfx6R^pm0 z`BJi1a$SlpWtWOdHKp#-_obgocco}4T?$H-(l)7IIxAh1{$7SDW0z^m{ACMe8)auQ zs*ER-%Pg|DvVd%>9A3^T*OlAL$I6$=_vJ7-T`rY-0$XrK#$w8m^kHTB$lwpcNd2QsGw2D2}R0)q-kOwY_?zdain{ zdP|8?3YA)=Q#qzwP<~aO)(~q-YLqpOn(3O=noAW)B~p1*i>esdfd_%j!+_k@~a_QirOe*0Jlf zbscphbsy`3b?bG9b(b2FhM@s9dW}~zs#(%}(d^d)^@MtEy|TWeezJbGey9FgOVBd4 zpte%0(OR{`+9mB_1Ehi8plIl77;9K+IMHEsQk_p1d<=aodTe++`FQ8?g`T39=}j*E zuzp_uS-;;1Yh*M^8_kU)je*8p1H`~E$P9MFTf>53y9sEbG|8K+O(RW#rq4~=O(#aI zk!)lcON?rx$>=c78H2_><7G3knb$0Bwl$A8e{5cEK54!%VNEoX(4;nXn1)RO(}ro! zbk%}wVYGlPsup+4e2Z(x3^%jPTC?9gZ~kgNe?oX7dE$OD^W?ySvWP4;%O}fmE1{L& zYHl59U26SigXP3joVgir_UJA)Xzqqtv$QyV0G9!W;*ttV_eTA&mGU_pYPkF?J~R1{+s={ z6V^%U1UuE81Dy+<2VKZ6ZkM`ixa)J*j~AF1!WY&Tqc4IlP8?W=%+c={c1${E9m|d_ z$C2Zr8`;h1mUf%Fece;t!S40$-R|S=3n$8ncWRvT&K2jL3+lqU*e;c;%QfaYcEjB+ zvRmMmyNzzId)mG1KIuXBka`$Bf*xg$rN`4V(G%#|_CP#%567eMm_4JOMbElt$8*vP z>BaX7dkww*-r3$QFVvgkReL+U6W$f?$xFyf_DkJM@5`B&$9?oZQ{P12YTv~x;j6Y+ z?_d4)YOkN!Ki+@wy7IN*wMX@O`t{e>7X$PG-N3}a{u}HY&6}Awi*I({ocnM-mQUi- z`s}`G-sUfF@p*Qv6*Ku>mZpi-twNzF0 delta 2260 zcmX9=e@NO78a6XCD`#eAW@TnSX3ktcOwH_*nOT{cnKd)BW@hHhHP_6Unwe{6wsm7f z#BSF`L_|d2*f%1=ixImnBDxq65wVLHBch8gVvIQNdoMiC^FHqj&-?FtbQE%X6oLf> zfj}T+fFK|cpD(TY%ZI;y0R`zDZ?V-0=D-TpuJ6@^(qzjZ5OD|;lysnNYf-1R#sBlI zivmeu9>U%(r$nhhps>FiyPlbrSik-O3KCM@eowkT2?7Nj{ShBX|Fh=H*XDcd|A4~- zcLH@Rj>Z2^|MlW;mfvVWBXM>lm>8d12jEh84}1c? z3%?G>gtNl=;ks~l_;~nM_*DceLLFg_@JH-L;v?yi-I317-N>WJTLcQhK&TNG#Cya7 zVimE4I7a-uN55yjw}~Vm`A9u-47r9pk3vNOQK~3U)Lhh7)FlduqM^j79@Kl(3hFZ2 zj*I3+o1$l<_s}pj2i=NxqZiTpG2j?tj4;L&Ga0iLbB9U8G+=y~70g*II+h=6iuJ}$ z$NFOruqZ4CtHq9CH?TKxgg8c=Ce9r<6Sp0Aj>F;TI5p0No5g*NhsKlRMe)}7x%gu| z63@V^@bB=O_{#)TLT-Z8o?uKEPw*#PCn6IWiPA)C;#A^(50ElP`!cBAUo1YKd=%E5w@=a*8y?km64Hkg}MvlX9B! zlY}5qNC3$|@{v|ahp7>%*{QNrTk1sWdg`|{bQ(X+l4f5>+ertf)6-??p7i8#ILS2R4Wf@Y+7X=}9ehnR-`I2aC()5>|rS>$YRE{kAA*+tSKN6~!IQPCBE z0uliMpa+J4S>OoxRg5Sm7fXr_#g5_+#s1@@@t_t^szp98;K$W3tvTCF1NCXz; ziufX>$SV3E@{3NYG1c5^W3{__wtB1jLW~wO#B%W)@uv8D4ZMb2BdC$r=xUrb<2Aby zkR)58mP|-CC6~3>TCTlTUprJgRl8cdTYD=dNV!tA)F)k$Ue{6Ugms!acinW|THSu# zi3~3j%M3EFY*w}*`&JLCr_?vptLr`WOZ9sVum);_q~T4&X2abh`Xl8d_oL-UCvvzv zS1y&C*}xSi#Bu{t4-c!X`62IxBbu{H7t!nktxjv!dbDfW9qoAstb@|Q?~r#mJLWn*b!>Os>YzH3j;<5ybUK%AO6S)d>CQXh zoup1yr>IlY>FZqYw4doQdXC2da~^jy3k+F!_CcwVf(xG*Q073K-^7xPUo zx|iB3>ecm*_iprFT5uMgMQ53^e6`%YB)$~Cw7r~px%2YYO0p`f@2ykT1?#GH+j?TX z?t}K>`dEGPK1<(3-(ufR-(la6z8f3VhPP$d^tN@|uI=28vXkt9U1J}z&)RPs7>Awa z5IU5O9>~`&s?Mes#aK|4sjVzrX*~iF9T-xz1Ln#W~~LbRIg-oVNqW zfs6t1fN5ZIV0GZcg>qH6bS{@`-nHwx9YhWSgT}$J!Ii-qH^Xgq&%5_tL0^eqIbMBy z_2t$15PN9uHT1RWwdwVnhSy84zj>e@hR5ic_goBLYj*mpHb8U;qBqt?;I(Vfxr-`vFC zga7pJ+W6}HyquWKpuC*u|4U3%z^6wAY;;akaG>`C4gdhSTqhUsbHIKu#~-i&5abBZ jT>y?C;LiUC0uvBuU^wmf;k2Nu(%`@k@w-DGbQJVoc!ykm From 9c29cdc63dd2e5a5d680c20ee069e3bd0463046a Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Fri, 24 Jan 2025 11:18:21 +1100 Subject: [PATCH 45/54] [Bug] Fix miniblackhole (#5169) --- src/modifier/modifier.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 37f88deea7f..c51fa129efe 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3183,12 +3183,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { break; } } - const randItemIndex = pokemon.randSeedInt(itemModifiers.length); - const randItem = itemModifiers[randItemIndex]; + const randItemIndex = pokemon.randSeedInt(tierItemModifiers.length); + const randItem = tierItemModifiers[randItemIndex]; heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { if (success) { transferredModifierTypes.push(randItem.type); - itemModifiers.splice(randItemIndex, 1); + tierItemModifiers.splice(randItemIndex, 1); } })); } From 747656e8df60333889b0a2794549cc0108d1ee3f Mon Sep 17 00:00:00 2001 From: Bertie690 <136088738+Bertie690@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:47:22 -0500 Subject: [PATCH 46/54] [Test] Added learn move utility function & level cap overrides (#5058) * Added learn move utility function * Added utility functions * add another test * Update overrides.ts * Update moveHelper.ts * Update overridesHelper.ts --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas --- src/battle-scene.ts | 6 +- src/field/pokemon.ts | 9 +- src/overrides.ts | 5 +- src/test/phases/learn-move-phase.test.ts | 117 ++++++++++++++++++++-- src/test/utils/helpers/moveHelper.ts | 45 ++++++++- src/test/utils/helpers/overridesHelper.ts | 20 ++++ 6 files changed, 187 insertions(+), 15 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 65ec6a844ee..39c09a31ceb 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1843,8 +1843,10 @@ export default class BattleScene extends SceneBase { this.currentBattle.battleScore += Math.ceil(scoreIncrease); } - getMaxExpLevel(ignoreLevelCap?: boolean): integer { - if (ignoreLevelCap) { + getMaxExpLevel(ignoreLevelCap: boolean = false): integer { + if (Overrides.LEVEL_CAP_OVERRIDE > 0) { + return Overrides.LEVEL_CAP_OVERRIDE; + } else if (ignoreLevelCap || Overrides.LEVEL_CAP_OVERRIDE < 0) { return Number.MAX_SAFE_INTEGER; } const waveIndex = Math.ceil((this.currentBattle?.waveIndex || 1) / 10) * 10; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a4b8603cbb0..a9c270ec09e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2391,8 +2391,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.battleInfo.toggleFlyout(visible); } - addExp(exp: integer) { - const maxExpLevel = globalScene.getMaxExpLevel(); + /** + * Adds experience to this PlayerPokemon, subject to wave based level caps. + * @param exp The amount of experience to add + * @param ignoreLevelCap Whether to ignore level caps when adding experience (defaults to false) + */ + addExp(exp: integer, ignoreLevelCap: boolean = false) { + const maxExpLevel = globalScene.getMaxExpLevel(ignoreLevelCap); const initialExp = this.exp; this.exp += exp; while (this.level < maxExpLevel && this.exp >= getLevelTotalExp(this.level + 1, this.species.growthRate)) { diff --git a/src/overrides.ts b/src/overrides.ts index 1f8601b7659..4b1f4b280eb 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -63,8 +63,11 @@ class DefaultOverrides { readonly STARTING_WAVE_OVERRIDE: number = 0; readonly STARTING_BIOME_OVERRIDE: Biome = Biome.TOWN; readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; - /** Multiplies XP gained by this value including 0. Set to null to ignore the override */ + /** Multiplies XP gained by this value including 0. Set to null to ignore the override. */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; + /** Sets the level cap to this number during experience gain calculations. Set to `0` to disable override & use normal wave-based level caps, + or any negative number to set it to 9 quadrillion (effectively disabling it). */ + readonly LEVEL_CAP_OVERRIDE: number = 0; readonly NEVER_CRIT_OVERRIDE: boolean = false; /** default 1000 */ readonly STARTING_MONEY_OVERRIDE: number = 0; diff --git a/src/test/phases/learn-move-phase.test.ts b/src/test/phases/learn-move-phase.test.ts index c4fa0e8bf45..3a3d111f551 100644 --- a/src/test/phases/learn-move-phase.test.ts +++ b/src/test/phases/learn-move-phase.test.ts @@ -4,6 +4,8 @@ import GameManager from "#test/utils/gameManager"; import { Species } from "#enums/species"; import { Moves } from "#enums/moves"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; +import { Mode } from "#app/ui/ui"; +import { Button } from "#app/enums/buttons"; describe("Learn Move Phase", () => { let phaserGame: Phaser.Game; @@ -26,7 +28,7 @@ describe("Learn Move Phase", () => { it("If Pokemon has less than 4 moves, its newest move will be added to the lowest empty index", async () => { game.override.moveset([ Moves.SPLASH ]); - await game.startBattle([ Species.BULBASAUR ]); + await game.classicMode.startBattle([ Species.BULBASAUR ]); const pokemon = game.scene.getPlayerPokemon()!; const newMovePos = pokemon?.getMoveset().length; game.move.select(Moves.SPLASH); @@ -36,12 +38,113 @@ describe("Learn Move Phase", () => { const levelReq = levelMove[0]; const levelMoveId = levelMove[1]; expect(pokemon.level).toBeGreaterThanOrEqual(levelReq); - expect(pokemon?.getMoveset()[newMovePos]?.moveId).toBe(levelMoveId); + expect(pokemon?.moveset[newMovePos]?.moveId).toBe(levelMoveId); + }); + + it("If a pokemon has 4 move slots filled, the chosen move will be deleted and replaced", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + const bulbasaur = game.scene.getPlayerPokemon()!; + const prevMoveset = [ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]; + const moveSlotNum = 3; + + game.move.changeMoveset(bulbasaur, prevMoveset); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + + // queue up inputs to confirm dialog boxes + game.onNextPrompt("LearnMovePhase", Mode.CONFIRM, () => { + game.scene.ui.processInput(Button.ACTION); + }); + game.onNextPrompt("LearnMovePhase", Mode.SUMMARY, () => { + for (let x = 0; x < moveSlotNum; x++) { + game.scene.ui.processInput(Button.DOWN); + } + game.scene.ui.processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(LearnMovePhase); + + const levelMove = bulbasaur.getLevelMoves(5)[0]; + const levelReq = levelMove[0]; + const levelMoveId = levelMove[1]; + expect(bulbasaur.level).toBeGreaterThanOrEqual(levelReq); + // Check each of mr mime's moveslots to make sure the changed move (and ONLY the changed move) is different + bulbasaur.getMoveset().forEach((move, index) => { + const expectedMove: Moves = (index === moveSlotNum ? levelMoveId : prevMoveset[index]); + expect(move?.moveId).toBe(expectedMove); + }); + }); + + it("selecting the newly deleted move will reject it and keep old moveset", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + const bulbasaur = game.scene.getPlayerPokemon()!; + const prevMoveset = [ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]; + + game.move.changeMoveset(bulbasaur, [ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + + // queue up inputs to confirm dialog boxes + game.onNextPrompt("LearnMovePhase", Mode.CONFIRM, () => { + game.scene.ui.processInput(Button.ACTION); + }); + game.onNextPrompt("LearnMovePhase", Mode.SUMMARY, () => { + for (let x = 0; x < 4; x++) { + game.scene.ui.processInput(Button.DOWN); // moves down 4 times to the 5th move slot + } + game.scene.ui.processInput(Button.ACTION); + }); + game.onNextPrompt("LearnMovePhase", Mode.CONFIRM, () => { + game.scene.ui.processInput(Button.ACTION); + }); + await game.phaseInterceptor.to(LearnMovePhase); + + const levelReq = bulbasaur.getLevelMoves(5)[0][0]; + expect(bulbasaur.level).toBeGreaterThanOrEqual(levelReq); + expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual(prevMoveset); + }); + + it("macro should add moves in free slots normally", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + const bulbasaur = game.scene.getPlayerPokemon()!; + + game.move.changeMoveset(bulbasaur, [ Moves.SPLASH, Moves.ABSORB, Moves.ACID ]); + game.move.select(Moves.SPLASH); + await game.move.learnMove(Moves.SACRED_FIRE, 0, 1); + expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.SACRED_FIRE ]); + + }); + + it("macro should replace moves", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + const bulbasaur = game.scene.getPlayerPokemon()!; + + game.move.changeMoveset(bulbasaur, [ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]); + game.move.select(Moves.SPLASH); + await game.move.learnMove(Moves.SACRED_FIRE, 0, 1); + expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ Moves.SPLASH, Moves.SACRED_FIRE, Moves.ACID, Moves.VINE_WHIP ]); + + }); + + it("macro should allow for cancelling move learning", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR ]); + const bulbasaur = game.scene.getPlayerPokemon()!; + + game.move.changeMoveset(bulbasaur, [ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]); + game.move.select(Moves.SPLASH); + await game.move.learnMove(Moves.SACRED_FIRE, 0, 4); + expect(bulbasaur.getMoveset().map(m => m?.moveId)).toEqual([ Moves.SPLASH, Moves.ABSORB, Moves.ACID, Moves.VINE_WHIP ]); + + }); + + it("macro works on off-field party members", async () => { + await game.classicMode.startBattle([ Species.BULBASAUR, Species.SQUIRTLE ]); + const squirtle = game.scene.getPlayerParty()[1]!; + + game.move.changeMoveset(squirtle, [ Moves.SPLASH, Moves.WATER_GUN, Moves.FREEZE_DRY, Moves.GROWL ]); + game.move.select(Moves.TACKLE); + await game.move.learnMove(Moves.SHELL_SMASH, 1, 0); + expect(squirtle.getMoveset().map(m => m?.moveId)).toEqual([ Moves.SHELL_SMASH, Moves.WATER_GUN, Moves.FREEZE_DRY, Moves.GROWL ]); + }); - /** - * Future Tests: - * If a Pokemon has four moves, the user can specify an old move to be forgotten and a new move will take its place. - * If a Pokemon has four moves, the user can reject the new move, keeping the moveset the same. - */ }); diff --git a/src/test/utils/helpers/moveHelper.ts b/src/test/utils/helpers/moveHelper.ts index 4b2069ee881..ad39755b556 100644 --- a/src/test/utils/helpers/moveHelper.ts +++ b/src/test/utils/helpers/moveHelper.ts @@ -1,8 +1,10 @@ import type { BattlerIndex } from "#app/battle"; +import { Button } from "#app/enums/buttons"; import type Pokemon from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon"; import Overrides from "#app/overrides"; import type { CommandPhase } from "#app/phases/command-phase"; +import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -75,9 +77,10 @@ export class MoveHelper extends GameManagerHelper { } /** - * Used when the normal moveset override can't be used (such as when it's necessary to check updated properties of the moveset). - * @param pokemon - The pokemon being modified - * @param moveset - The moveset to use + * Changes a pokemon's moveset to the given move(s). + * Used when the normal moveset override can't be used (such as when it's necessary to check or update properties of the moveset). + * @param pokemon - The {@linkcode Pokemon} being modified + * @param moveset - The {@linkcode Moves} (single or array) to change the Pokemon's moveset to */ public changeMoveset(pokemon: Pokemon, moveset: Moves | Moves[]): void { if (!Array.isArray(moveset)) { @@ -90,4 +93,40 @@ export class MoveHelper extends GameManagerHelper { const movesetStr = moveset.map((moveId) => Moves[moveId]).join(", "); console.log(`Pokemon ${pokemon.species.name}'s moveset manually set to ${movesetStr} (=[${moveset.join(", ")}])!`); } + + /** + * Simulates learning a move for a player pokemon. + * @param move The {@linkcode Moves} being learnt + * @param partyIndex The party position of the {@linkcode PlayerPokemon} learning the move (defaults to 0) + * @param moveSlotIndex The INDEX (0-4) of the move slot to replace if existent move slots are full; + * defaults to 0 (first slot) and 4 aborts the procedure + * @returns a promise that resolves once the move has been successfully learnt + */ + public async learnMove(move: Moves | integer, partyIndex: integer = 0, moveSlotIndex: integer = 0) { + return new Promise(async (resolve, reject) => { + this.game.scene.pushPhase(new LearnMovePhase(partyIndex, move)); + + // if slots are full, queue up inputs to replace existing moves + if (this.game.scene.getPlayerParty()[partyIndex].moveset.filter(m => m).length === 4) { + this.game.onNextPrompt("LearnMovePhase", Mode.CONFIRM, () => { + this.game.scene.ui.processInput(Button.ACTION); // "Should a move be forgotten and replaced with XXX?" + }); + this.game.onNextPrompt("LearnMovePhase", Mode.SUMMARY, () => { + for (let x = 0; x < (moveSlotIndex ?? 0); x++) { + this.game.scene.ui.processInput(Button.DOWN); // Scrolling in summary pane to move position + } + this.game.scene.ui.processInput(Button.ACTION); + if (moveSlotIndex === 4) { + this.game.onNextPrompt("LearnMovePhase", Mode.CONFIRM, () => { + this.game.scene.ui.processInput(Button.ACTION); // "Give up on learning XXX?" + }); + } + }); + } + + await this.game.phaseInterceptor.to(LearnMovePhase).catch(e => reject(e)); + resolve(); + }); + } + } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 9af811561b7..15815c96691 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -71,6 +71,26 @@ export class OverridesHelper extends GameManagerHelper { return this; } + /** + * Override the wave level cap + * @param cap the level cap value to set; 0 uses normal level caps and negative values + * disable it completely + * @returns `this` + */ + public levelCap(cap: number): this { + vi.spyOn(Overrides, "LEVEL_CAP_OVERRIDE", "get").mockReturnValue(cap); + let capStr: string; + if (cap > 0) { + capStr = `Level cap set to ${cap}!`; + } else if (cap < 0) { + capStr = "Level cap disabled!"; + } else { + capStr = "Level cap reset to default value for wave."; + } + this.log(capStr); + return this; + } + /** * Override the player (pokemon) starting held items * @param items the items to hold From 8685ec3c3c12113a409bf834b88af0034f6a76a5 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:59:24 -0500 Subject: [PATCH 47/54] [Misc] Rework some aspects of timed events (#5099) * Refactor timed event changes * Use getWeather function * Add mystery encounter tier change/disabling to timed events * Event luck boost, event encounter helper function * Events without shiny boost shouldn't give shiny charm * globalScene -> this in battle scene class * Change event pools --- src/battle-scene.ts | 5 +- .../encounters/berries-abound-encounter.ts | 20 +- .../encounters/delibirdy-encounter.ts | 2 +- .../encounters/fight-or-flight-encounter.ts | 20 +- .../encounters/uncommon-breed-encounter.ts | 23 +- .../utils/encounter-phase-utils.ts | 42 ++- src/data/weather.ts | 4 +- src/field/pokemon.ts | 43 ++- src/modifier/modifier-type.ts | 5 +- src/phases/trainer-victory-phase.ts | 8 +- src/test/utils/mocks/mockTimedEventManager.ts | 5 +- src/timed-event-manager.ts | 247 +++++++++++++++--- 12 files changed, 319 insertions(+), 105 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 39c09a31ceb..f0aeb68e277 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3390,7 +3390,8 @@ export default class BattleScene extends SceneBase { const previousEncounter = this.mysteryEncounterSaveData.encounteredEvents.length > 0 ? this.mysteryEncounterSaveData.encounteredEvents[this.mysteryEncounterSaveData.encounteredEvents.length - 1].type : null; - const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? []; + const disabledEncounters = this.eventManager.getEventMysteryEncountersDisabled(); + const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType)?.filter(enc => !disabledEncounters.includes(enc)) ?? []; // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available while (availableEncounters.length === 0 && tier !== null) { availableEncounters = biomeMysteryEncounters @@ -3399,7 +3400,7 @@ export default class BattleScene extends SceneBase { if (!encounterCandidate) { return false; } - if (encounterCandidate.encounterTier !== tier) { + if (this.eventManager.getMysteryEncounterTierForEvent(encounterType, encounterCandidate.encounterTier) !== tier) { return false; } const disallowedGameModes = encounterCandidate.disallowedGameModes; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 20179f4c618..550d9bd4f13 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -4,6 +4,7 @@ import type { import { generateModifierType, generateModifierTypeOption, + getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, @@ -11,17 +12,15 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type"; import { - getPartyLuckValue, ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds, } from "#app/modifier/modifier-type"; -import { randSeedInt, randSeedItem } from "#app/utils"; +import { randSeedInt } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; @@ -31,7 +30,6 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { TrainerSlot } from "#app/data/trainer-config"; import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; import { BerryModifier } from "#app/modifier/modifier"; @@ -40,8 +38,6 @@ import { BerryType } from "#enums/berry-type"; import { PERMANENT_STATS, Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/berriesAbound"; @@ -69,20 +65,12 @@ export const BerriesAboundEncounter: MysteryEncounter = // Calculate boss mon const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); - let bossSpecies: PokemonSpecies; - if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); - bossSpecies = getPokemonSpecies( levelSpecies ); - } else { - bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); - } - const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); + const bossPokemon = getRandomEncounterSpecies(level, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); const config: EnemyPartyConfig = { pokemonConfigs: [{ level: level, - species: bossSpecies, + species: bossPokemon.species, dataSource: new PokemonData(bossPokemon), isBoss: true }], diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index d710dffab8c..f382f130540 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -41,7 +41,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; const doEventReward = () => { - const event_buff = globalScene.eventManager.activeEvent()?.delibirdyBuff ?? []; + const event_buff = globalScene.eventManager.getDelibirdyBuff(); if (event_buff.length > 0) { const candidates = event_buff.filter((c => { const mtype = generateModifierType(modifierTypes[c]); diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index fc6f163010b..1667a15e7c9 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -2,6 +2,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { + getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, @@ -9,12 +10,10 @@ import { } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon } from "#app/field/pokemon"; import { ModifierTier } from "#app/modifier/modifier-tier"; import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import { - getPartyLuckValue, getPlayerModifierTypeOptions, ModifierPoolType, regenerateModifierPoolThresholds, @@ -26,16 +25,13 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { TrainerSlot } from "#app/data/trainer-config"; import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; import { BattlerTagType } from "#enums/battler-tag-type"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { randSeedInt, randSeedItem } from "#app/utils"; +import { randSeedInt } from "#app/utils"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fightOrFlight"; @@ -63,20 +59,12 @@ export const FightOrFlightEncounter: MysteryEncounter = // Calculate boss mon const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER); - let bossSpecies: PokemonSpecies; - if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); - bossSpecies = getPokemonSpecies( levelSpecies ); - } else { - bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); - } - const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true); + const bossPokemon = getRandomEncounterSpecies(level, true); encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender()); const config: EnemyPartyConfig = { pokemonConfigs: [{ level: level, - species: bossSpecies, + species: bossPokemon.species, dataSource: new PokemonData(bossPokemon), isBoss: true, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 39297ab95b6..6e6381888f1 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -1,10 +1,10 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { getRandomEncounterSpecies, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import type Pokemon from "#app/field/pokemon"; -import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; -import { getPartyLuckValue } from "#app/modifier/modifier-type"; +import type { EnemyPokemon } from "#app/field/pokemon"; +import { PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { globalScene } from "#app/global-scene"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; @@ -12,10 +12,9 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { TrainerSlot } from "#app/data/trainer-config"; import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; -import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils"; +import { isNullOrUndefined, randSeedInt } from "#app/utils"; import type { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; import { SelfStatusMove } from "#app/data/move"; @@ -26,8 +25,6 @@ import { BerryModifier } from "#app/modifier/modifier"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; -import type PokemonSpecies from "#app/data/pokemon-species"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/uncommonBreed"; @@ -56,15 +53,7 @@ export const UncommonBreedEncounter: MysteryEncounter = // Calculate boss mon // Level equal to 2 below highest party member const level = getHighestLevelPlayerPokemon(false, true).level - 2; - let species: PokemonSpecies; - if (globalScene.eventManager.isEventActive() && globalScene.eventManager.activeEvent()?.uncommonBreedEncounters && randSeedInt(2) === 1) { - const eventEncounter = randSeedItem(globalScene.eventManager.activeEvent()!.uncommonBreedEncounters!); - const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, eventEncounter.allowEvolution ?? false, true, globalScene.gameMode); - species = getPokemonSpecies( levelSpecies ); - } else { - species = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), true); - } - const pokemon = new EnemyPokemon(species, level, TrainerSlot.NONE, true); + const pokemon = getRandomEncounterSpecies(level, true, true); // Pokemon will always have one of its egg moves in its moveset const eggMoves = pokemon.getEggMoves(); @@ -92,7 +81,7 @@ export const UncommonBreedEncounter: MysteryEncounter = const config: EnemyPartyConfig = { pokemonConfigs: [{ level: level, - species: species, + species: pokemon.species, dataSource: new PokemonData(pokemon), isBoss: false, tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ], diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 8768fb06b37..a144b1ac5c5 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -6,9 +6,9 @@ import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } fro import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import type { AiType, PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; -import { FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; +import { EnemyPokemon, FieldPosition, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; -import { ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { getPartyLuckValue, ModifierPoolType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import type PokemonData from "#app/system/pokemon-data"; import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -16,7 +16,7 @@ import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler" import { PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; import * as Utils from "#app/utils"; -import { isNullOrUndefined } from "#app/utils"; +import { isNullOrUndefined, randSeedInt, randSeedItem } from "#app/utils"; import type { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; import type { TrainerType } from "#enums/trainer-type"; @@ -45,6 +45,7 @@ import { PartyExpPhase } from "#app/phases/party-exp-phase"; import type { Variant } from "#app/data/variant"; import { StatusEffect } from "#enums/status-effect"; import { globalScene } from "#app/global-scene"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -874,6 +875,41 @@ export function handleMysteryEncounterTurnStartEffects(): boolean { return false; } +/** + * Helper function for encounters such as {@linkcode UncommonBreedEncounter} which call for a random species including event encounters. + * If the mon is from the event encounter list, it will do an extra shiny roll. + * @param level the level of the mon, which differs between MEs + * @param isBoss whether the mon should be a Boss + * @param rerollHidden whether the mon should get an extra roll for Hidden Ability + * @returns {@linkcode EnemyPokemon} for the requested encounter + */ +export function getRandomEncounterSpecies(level: number, isBoss: boolean = false, rerollHidden: boolean = false): EnemyPokemon { + let bossSpecies: PokemonSpecies; + let isEventEncounter = false; + const eventEncounters = globalScene.eventManager.getEventEncounters(); + + if (eventEncounters.length > 0 && randSeedInt(2) === 1) { + const eventEncounter = randSeedItem(eventEncounters); + const levelSpecies = getPokemonSpecies(eventEncounter.species).getWildSpeciesForLevel(level, !isNullOrUndefined(eventEncounter.blockEvolution), isBoss, globalScene.gameMode); + isEventEncounter = true; + bossSpecies = getPokemonSpecies(levelSpecies); + } else { + bossSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, level, 0, getPartyLuckValue(globalScene.getPlayerParty()), isBoss); + } + const ret = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, isBoss); + + //Reroll shiny for event encounters + if (isEventEncounter && !ret.shiny) { + ret.trySetShinySeed(); + } + //Reroll hidden ability + if (rerollHidden && ret.abilityIndex !== 2 && ret.species.abilityHidden) { + ret.tryRerollHiddenAbilitySeed(); + } + + return ret; +} + /** * TODO: remove once encounter spawn rate is finalized * Just a helper function to calculate aggregate stats for MEs in a Classic run diff --git a/src/data/weather.ts b/src/data/weather.ts index d971f726543..7bba698f9b4 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -375,8 +375,8 @@ export function getRandomWeatherType(arena: Arena): WeatherType { break; } - if (arena.biomeType === Biome.TOWN && globalScene.eventManager.isEventActive() && (globalScene.eventManager.activeEvent()?.weather?.length ?? 0) > 0) { - globalScene.eventManager.activeEvent()?.weather?.map(w => weatherPool.push(w)); + if (arena.biomeType === Biome.TOWN && globalScene.eventManager.isEventActive()) { + globalScene.eventManager.getWeather()?.map(w => weatherPool.push(w)); } if (weatherPool.length > 1) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a9c270ec09e..731d5c8fbe7 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -10,7 +10,7 @@ import type Move from "#app/data/move"; import { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move"; import type { PokemonSpeciesForm } from "#app/data/pokemon-species"; import { default as PokemonSpecies, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; -import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import type { Constructor } from "#app/utils"; import { isNullOrUndefined, randSeedInt, type nil } from "#app/utils"; @@ -1955,7 +1955,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm) * @returns true if the Pokemon has been set as a shiny, false otherwise */ - trySetShiny(thresholdOverride?: integer): boolean { + trySetShiny(thresholdOverride?: number): boolean { // Shiny Pokemon should not spawn in the end biome in endless if (globalScene.gameMode.isEndless && globalScene.arena.biomeType === Biome.END) { return false; @@ -1967,7 +1967,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const E = globalScene.gameData.trainerId ^ globalScene.gameData.secretId; const F = rand1 ^ rand2; - const shinyThreshold = new Utils.IntegerHolder(BASE_SHINY_CHANCE); + const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE); if (thresholdOverride === undefined) { if (globalScene.eventManager.isEventActive()) { shinyThreshold.value *= globalScene.eventManager.getShinyMultiplier(); @@ -2058,6 +2058,38 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + /** + * Function that tries to set a Pokemon to have its hidden ability based on seed, if it exists. + * For manual use only, usually to roll a Pokemon's hidden ability chance a second time. + * + * The base hidden ability odds are {@linkcode BASE_HIDDEN_ABILITY_CHANCE} / `65536` + * @param thresholdOverride number that is divided by `2^16` (`65536`) to get the HA chance, overrides {@linkcode haThreshold} if set (bypassing HA rate modifiers such as Ability Charm) + * @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Ability Charm to {@linkcode thresholdOverride} + * @returns `true` if the Pokemon has been set to have its hidden ability, `false` otherwise + */ + public tryRerollHiddenAbilitySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean { + if (!this.species.abilityHidden) { + return false; + } + const haThreshold = new Utils.NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); + if (thresholdOverride === undefined || applyModifiersToOverride) { + if (thresholdOverride !== undefined && applyModifiersToOverride) { + haThreshold.value = thresholdOverride; + } + if (!this.hasTrainer()) { + globalScene.applyModifiers(HiddenAbilityRateBoosterModifier, true, haThreshold); + } + } else { + haThreshold.value = thresholdOverride; + } + + if (randSeedInt(65536) < haThreshold.value) { + this.abilityIndex = 2; + } + + return this.abilityIndex === 2; + } + public generateFusionSpecies(forStarter?: boolean): void { const hiddenAbilityChance = new Utils.NumberHolder(BASE_HIDDEN_ABILITY_CHANCE); if (!this.hasTrainer()) { @@ -4328,10 +4360,7 @@ export class PlayerPokemon extends Pokemon { ].filter(d => !!d); const amount = new Utils.NumberHolder(friendship); globalScene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); - let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; - if (globalScene.eventManager.isEventActive()) { - candyFriendshipMultiplier *= globalScene.eventManager.getFriendshipMultiplier(); - } + const candyFriendshipMultiplier = globalScene.eventManager.getClassicFriendshipMultiplier(); const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (globalScene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1))); // Add friendship to this PlayerPokemon diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 9c844596abc..f531e96d641 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2537,9 +2537,10 @@ export function getPartyLuckValue(party: Pokemon[]): integer { }, 0, globalScene.seed); return DailyLuck.value; } - const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() : 0) + const eventSpecies = globalScene.eventManager.getEventLuckBoostedSpecies(); + const luck = Phaser.Math.Clamp(party.map(p => p.isAllowedInBattle() ? p.getLuck() + (eventSpecies.includes(p.species.speciesId) ? 1 : 0) : 0) .reduce((total: integer, value: integer) => total += value, 0), 0, 14); - return luck ?? 0; + return Math.min(globalScene.eventManager.getEventLuckBoost() + (luck ?? 0), 14); } export function getLuckString(luckValue: integer): string { diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 3fa7209751a..9722e9c4197 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -29,7 +29,7 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } - if (globalScene.eventManager.isEventActive()) { + if (globalScene.eventManager.getShinyMultiplier() > 1) { //If a shiny boosting event is active for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); } @@ -39,7 +39,11 @@ export class TrainerVictoryPhase extends BattlePhase { // Validate Voucher for boss trainers if (vouchers.hasOwnProperty(TrainerType[trainerType])) { if (!globalScene.validateVoucher(vouchers[TrainerType[trainerType]]) && globalScene.currentBattle.trainer?.config.isBoss) { - globalScene.unshiftPhase(new ModifierRewardPhase([ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); + if (globalScene.eventManager.getUpgradeUnlockedVouchers()) { + globalScene.unshiftPhase(new ModifierRewardPhase([ modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); + } else { + globalScene.unshiftPhase(new ModifierRewardPhase([ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType])); + } } } // Breeders in Space achievement diff --git a/src/test/utils/mocks/mockTimedEventManager.ts b/src/test/utils/mocks/mockTimedEventManager.ts index b44729996a7..10f32fd4c8b 100644 --- a/src/test/utils/mocks/mockTimedEventManager.ts +++ b/src/test/utils/mocks/mockTimedEventManager.ts @@ -1,3 +1,4 @@ +import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "#app/data/balance/starters"; import { TimedEventManager } from "#app/timed-event-manager"; /** Mock TimedEventManager so that ongoing events don't impact tests */ @@ -8,8 +9,8 @@ export class MockTimedEventManager extends TimedEventManager { override isEventActive(): boolean { return false; } - override getFriendshipMultiplier(): number { - return 1; + override getClassicFriendshipMultiplier(): number { + return CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; } override getShinyMultiplier(): number { return 1; diff --git a/src/timed-event-manager.ts b/src/timed-event-manager.ts index 216900e8f58..8633460d98c 100644 --- a/src/timed-event-manager.ts +++ b/src/timed-event-manager.ts @@ -1,14 +1,19 @@ import { globalScene } from "#app/global-scene"; import { TextStyle, addTextObject } from "#app/ui/text"; import type { nil } from "#app/utils"; +import { isNullOrUndefined } from "#app/utils"; import i18next from "i18next"; import { Species } from "#enums/species"; import type { WeatherPoolEntry } from "#app/data/weather"; import { WeatherType } from "#enums/weather-type"; +import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER } from "./data/balance/starters"; +import { MysteryEncounterType } from "./enums/mystery-encounter-type"; +import { MysteryEncounterTier } from "./enums/mystery-encounter-tier"; export enum EventType { SHINY, - NO_TIMER_DISPLAY + NO_TIMER_DISPLAY, + LUCK } interface EventBanner { @@ -21,19 +26,29 @@ interface EventBanner { interface EventEncounter { species: Species; - allowEvolution?: boolean; + blockEvolution?: boolean; +} + +interface EventMysteryEncounterTier { + mysteryEncounter: MysteryEncounterType; + tier?: MysteryEncounterTier; + disable?: boolean; } interface TimedEvent extends EventBanner { name: string; eventType: EventType; shinyMultiplier?: number; - friendshipMultiplier?: number; + classicFriendshipMultiplier?: number; + luckBoost?: number; + upgradeUnlockedVouchers?: boolean; startDate: Date; endDate: Date; - uncommonBreedEncounters?: EventEncounter[]; + eventEncounters?: EventEncounter[]; delibirdyBuff?: string[]; weather?: WeatherPoolEntry[]; + mysteryEncounterTierChanges?: EventMysteryEncounterTier[]; + luckBoostedSpecies?: Species[]; } const timedEvents: TimedEvent[] = [ @@ -41,36 +56,94 @@ const timedEvents: TimedEvent[] = [ name: "Winter Holiday Update", eventType: EventType.SHINY, shinyMultiplier: 2, - friendshipMultiplier: 1, + upgradeUnlockedVouchers: true, startDate: new Date(Date.UTC(2024, 11, 21, 0)), endDate: new Date(Date.UTC(2025, 0, 4, 0)), bannerKey: "winter_holidays2024-event-", scale: 0.21, availableLangs: [ "en", "de", "it", "fr", "ja", "ko", "es-ES", "pt-BR", "zh-CN" ], - uncommonBreedEncounters: [ - { species: Species.GIMMIGHOUL }, + eventEncounters: [ + { species: Species.GIMMIGHOUL, blockEvolution: true }, { species: Species.DELIBIRD }, - { species: Species.STANTLER, allowEvolution: true }, - { species: Species.CYNDAQUIL, allowEvolution: true }, - { species: Species.PIPLUP, allowEvolution: true }, - { species: Species.CHESPIN, allowEvolution: true }, - { species: Species.BALTOY, allowEvolution: true }, - { species: Species.SNOVER, allowEvolution: true }, - { species: Species.CHINGLING, allowEvolution: true }, - { species: Species.LITWICK, allowEvolution: true }, - { species: Species.CUBCHOO, allowEvolution: true }, - { species: Species.SWIRLIX, allowEvolution: true }, - { species: Species.AMAURA, allowEvolution: true }, - { species: Species.MUDBRAY, allowEvolution: true }, - { species: Species.ROLYCOLY, allowEvolution: true }, - { species: Species.MILCERY, allowEvolution: true }, - { species: Species.SMOLIV, allowEvolution: true }, - { species: Species.ALOLA_VULPIX, allowEvolution: true }, - { species: Species.GALAR_DARUMAKA, allowEvolution: true }, + { species: Species.STANTLER }, + { species: Species.CYNDAQUIL }, + { species: Species.PIPLUP }, + { species: Species.CHESPIN }, + { species: Species.BALTOY }, + { species: Species.SNOVER }, + { species: Species.CHINGLING }, + { species: Species.LITWICK }, + { species: Species.CUBCHOO }, + { species: Species.SWIRLIX }, + { species: Species.AMAURA }, + { species: Species.MUDBRAY }, + { species: Species.ROLYCOLY }, + { species: Species.MILCERY }, + { species: Species.SMOLIV }, + { species: Species.ALOLA_VULPIX }, + { species: Species.GALAR_DARUMAKA }, { species: Species.IRON_BUNDLE } ], delibirdyBuff: [ "CATCHING_CHARM", "SHINY_CHARM", "ABILITY_CHARM", "EXP_CHARM", "SUPER_EXP_CHARM", "HEALING_CHARM" ], - weather: [{ weatherType: WeatherType.SNOW, weight: 1 }] + weather: [{ weatherType: WeatherType.SNOW, weight: 1 }], + mysteryEncounterTierChanges: [ + { mysteryEncounter: MysteryEncounterType.DELIBIRDY, tier: MysteryEncounterTier.COMMON }, + { mysteryEncounter: MysteryEncounterType.PART_TIMER, disable: true }, + { mysteryEncounter: MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, disable: true }, + { mysteryEncounter: MysteryEncounterType.FIELD_TRIP, disable: true }, + { mysteryEncounter: MysteryEncounterType.DEPARTMENT_STORE_SALE, disable: true } + ] + }, + { + name: "Year of the Snake", + eventType: EventType.LUCK, + luckBoost: 1, + startDate: new Date(Date.UTC(2025, 0, 29, 0)), + endDate: new Date(Date.UTC(2025, 1, 3, 0)), + bannerKey: "yearofthesnakeevent-", + scale: 0.21, + availableLangs: [], + eventEncounters: [ + { species: Species.EKANS }, + { species: Species.ONIX }, + { species: Species.DRATINI }, + { species: Species.CLEFFA }, + { species: Species.UMBREON }, + { species: Species.DUNSPARCE }, + { species: Species.TEDDIURSA }, + { species: Species.SEVIPER }, + { species: Species.LUNATONE }, + { species: Species.CHINGLING }, + { species: Species.SNIVY }, + { species: Species.DARUMAKA }, + { species: Species.DRAMPA }, + { species: Species.SILICOBRA }, + { species: Species.BLOODMOON_URSALUNA } + ], + luckBoostedSpecies: [ + Species.EKANS, Species.ARBOK, + Species.ONIX, Species.STEELIX, + Species.DRATINI, Species.DRAGONAIR, Species.DRAGONITE, + Species.CLEFFA, Species.CLEFAIRY, Species.CLEFABLE, + Species.UMBREON, + Species.DUNSPARCE, Species.DUDUNSPARCE, + Species.TEDDIURSA, Species.URSARING, Species.URSALUNA, + Species.SEVIPER, + Species.LUNATONE, + Species.RAYQUAZA, + Species.CHINGLING, Species.CHIMECHO, + Species.CRESSELIA, + Species.DARKRAI, + Species.SNIVY, Species.SERVINE, Species.SERPERIOR, + Species.DARUMAKA, Species.DARMANITAN, + Species.ZYGARDE, + Species.DRAMPA, + Species.LUNALA, + Species.BLACEPHALON, + Species.SILICOBRA, Species.SANDACONDA, + Species.ROARING_MOON, + Species.BLOODMOON_URSALUNA + ] } ]; @@ -97,16 +170,6 @@ export class TimedEventManager { return activeEvents.length > 0; } - getFriendshipMultiplier(): number { - let multiplier = 1; - const friendshipEvents = timedEvents.filter((te) => this.isActive(te)); - friendshipEvents.forEach((fe) => { - multiplier *= fe.friendshipMultiplier ?? 1; - }); - - return multiplier; - } - getShinyMultiplier(): number { let multiplier = 1; const shinyEvents = timedEvents.filter((te) => te.eventType === EventType.SHINY && this.isActive(te)); @@ -120,6 +183,120 @@ export class TimedEventManager { getEventBannerFilename(): string { return timedEvents.find((te: TimedEvent) => this.isActive(te))?.bannerKey ?? ""; } + + getEventEncounters(): EventEncounter[] { + const ret: EventEncounter[] = []; + timedEvents.filter((te) => this.isActive(te)).map((te) => { + if (!isNullOrUndefined(te.eventEncounters)) { + ret.push(...te.eventEncounters); + } + }); + return ret; + } + + /** + * For events that change the classic candy friendship multiplier + * @returns The highest classic friendship multiplier among the active events, or the default CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER + */ + getClassicFriendshipMultiplier(): number { + let multiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER; + const classicFriendshipEvents = timedEvents.filter((te) => this.isActive(te)); + classicFriendshipEvents.forEach((fe) => { + if (!isNullOrUndefined(fe.classicFriendshipMultiplier) && fe.classicFriendshipMultiplier > multiplier) { + multiplier = fe.classicFriendshipMultiplier; + } + }); + return multiplier; + } + + /** + * For events where defeated bosses (Gym Leaders, E4 etc) give out Voucher Plus even if they were defeated before + * @returns Whether vouchers should be upgraded + */ + getUpgradeUnlockedVouchers(): boolean { + return timedEvents.some((te) => this.isActive(te) && (te.upgradeUnlockedVouchers ?? false)); + } + + /** + * For events where Delibirdy gives extra items + * @returns list of ids of {@linkcode ModifierType}s that Delibirdy hands out as a bonus + */ + getDelibirdyBuff(): string[] { + const ret: string[] = []; + timedEvents.filter((te) => this.isActive(te)).map((te) => { + if (!isNullOrUndefined(te.delibirdyBuff)) { + ret.push(...te.delibirdyBuff); + } + }); + return ret; + } + + /** + * For events where there's a set weather for town biome (other biomes are hard) + * @returns Event weathers for town + */ + getWeather(): WeatherPoolEntry[] { + const ret: WeatherPoolEntry[] = []; + timedEvents.filter((te) => this.isActive(te)).map((te) => { + if (!isNullOrUndefined(te.weather)) { + ret.push(...te.weather); + } + }); + return ret; + } + + getAllMysteryEncounterChanges(): EventMysteryEncounterTier[] { + const ret: EventMysteryEncounterTier[] = []; + timedEvents.filter((te) => this.isActive(te)).map((te) => { + if (!isNullOrUndefined(te.mysteryEncounterTierChanges)) { + ret.push(...te.mysteryEncounterTierChanges); + } + }); + return ret; + } + + getEventMysteryEncountersDisabled(): MysteryEncounterType[] { + const ret: MysteryEncounterType[] = []; + timedEvents.filter((te) => this.isActive(te) && !isNullOrUndefined(te.mysteryEncounterTierChanges)).map((te) => { + te.mysteryEncounterTierChanges?.map((metc) => { + if (metc.disable) { + ret.push(metc.mysteryEncounter); + } + }); + }); + return ret; + } + + getMysteryEncounterTierForEvent(encounterType: MysteryEncounterType, normal: MysteryEncounterTier): MysteryEncounterTier { + let ret = normal; + timedEvents.filter((te) => this.isActive(te) && !isNullOrUndefined(te.mysteryEncounterTierChanges)).map((te) => { + te.mysteryEncounterTierChanges?.map((metc) => { + if (metc.mysteryEncounter === encounterType) { + ret = metc.tier ?? normal; + } + }); + }); + return ret; + } + + getEventLuckBoost(): number { + let ret = 0; + const luckEvents = timedEvents.filter((te) => this.isActive(te) && !isNullOrUndefined(te.luckBoost)); + luckEvents.forEach((le) => { + ret += le.luckBoost!; + }); + return ret; + } + + getEventLuckBoostedSpecies(): Species[] { + const ret: Species[] = []; + timedEvents.filter((te) => this.isActive(te)).map((te) => { + if (!isNullOrUndefined(te.luckBoostedSpecies)) { + ret.push(...te.luckBoostedSpecies.filter(s => !ret.includes(s))); + } + }); + return ret; + } } export class TimedEventDisplay extends Phaser.GameObjects.Container { From 996ce5d986a77d171dd2af786666469389ef3b44 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:06:57 -0800 Subject: [PATCH 48/54] [Misc] Update version to 1.5.0 (#5175) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52cc628872a..9531244a42d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.4.3", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.4.3", + "version": "1.5.0", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index e52c5928da7..9d8adb8c1c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.4.3", + "version": "1.5.0", "type": "module", "scripts": { "start": "vite", From 706a23238caa82427448754d89cf2b8c8350e03d Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 25 Jan 2025 17:29:09 -0500 Subject: [PATCH 49/54] Revert "[Bug] Fix miniblackhole (#5169)" (#5183) This reverts commit 9c29cdc63dd2e5a5d680c20ee069e3bd0463046a. --- src/modifier/modifier.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index c51fa129efe..37f88deea7f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3183,12 +3183,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { break; } } - const randItemIndex = pokemon.randSeedInt(tierItemModifiers.length); - const randItem = tierItemModifiers[randItemIndex]; + const randItemIndex = pokemon.randSeedInt(itemModifiers.length); + const randItem = itemModifiers[randItemIndex]; heldItemTransferPromises.push(globalScene.tryTransferHeldItemModifier(randItem, pokemon, false).then(success => { if (success) { transferredModifierTypes.push(randItem.type); - tierItemModifiers.splice(randItemIndex, 1); + itemModifiers.splice(randItemIndex, 1); } })); } From f1c06a5476a9ad819e9ef15d4ef5bee36143e771 Mon Sep 17 00:00:00 2001 From: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:58:21 -0500 Subject: [PATCH 50/54] [Sprite] Fix Lugia backsprite being cut off (#5144) --- public/images/pokemon/back/249.json | 3224 +++++---------------- public/images/pokemon/back/249.png | Bin 25978 -> 37468 bytes public/images/pokemon/back/shiny/249.json | 3224 +++++---------------- public/images/pokemon/back/shiny/249.png | Bin 25981 -> 37468 bytes 4 files changed, 1370 insertions(+), 5078 deletions(-) diff --git a/public/images/pokemon/back/249.json b/public/images/pokemon/back/249.json index 52784b825f8..7f126e8c62c 100644 --- a/public/images/pokemon/back/249.json +++ b/public/images/pokemon/back/249.json @@ -1,2540 +1,686 @@ -{ - "textures": [ - { - "image": "249.png", - "format": "RGBA8888", - "size": { - "w": 610, - "h": 610 - }, - "scale": 1, - "frames": [ - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0106.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 25, - "y": 5, - "w": 99, - "h": 88 - }, - "frame": { - "x": 230, - "y": 137, - "w": 99, - "h": 88 - } - }, - { - "filename": "0107.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 5, - "w": 99, - "h": 88 - }, - "frame": { - "x": 329, - "y": 141, - "w": 99, - "h": 88 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0115.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 26, - "y": 0, - "w": 84, - "h": 97 - }, - "frame": { - "x": 526, - "y": 141, - "w": 84, - "h": 97 - } - }, - { - "filename": "0105.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 27, - "y": 4, - "w": 96, - "h": 89 - }, - "frame": { - "x": 0, - "y": 216, - "w": 96, - "h": 89 - } - }, - { - "filename": "0108.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 25, - "y": 4, - "w": 96, - "h": 89 - }, - "frame": { - "x": 96, - "y": 222, - "w": 96, - "h": 89 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 7, - "w": 95, - "h": 90 - }, - "frame": { - "x": 290, - "y": 229, - "w": 95, - "h": 90 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 7, - "w": 95, - "h": 90 - }, - "frame": { - "x": 290, - "y": 229, - "w": 95, - "h": 90 - } - }, - { - "filename": "0109.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 4, - "w": 95, - "h": 90 - }, - "frame": { - "x": 385, - "y": 230, - "w": 95, - "h": 90 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 6, - "w": 95, - "h": 91 - }, - "frame": { - "x": 95, - "y": 311, - "w": 95, - "h": 91 - } - }, - { - "filename": "0103.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 32, - "y": 2, - "w": 92, - "h": 91 - }, - "frame": { - "x": 190, - "y": 315, - "w": 92, - "h": 91 - } - }, - { - "filename": "0104.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 32, - "y": 2, - "w": 92, - "h": 91 - }, - "frame": { - "x": 190, - "y": 315, - "w": 92, - "h": 91 - } - }, - { - "filename": "0097.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 16, - "y": 5, - "w": 101, - "h": 91 - }, - "frame": { - "x": 282, - "y": 319, - "w": 101, - "h": 91 - } - }, - { - "filename": "0096.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 5, - "w": 94, - "h": 92 - }, - "frame": { - "x": 383, - "y": 320, - "w": 94, - "h": 92 - } - }, - { - "filename": "0098.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 16, - "y": 4, - "w": 100, - "h": 92 - }, - "frame": { - "x": 477, - "y": 329, - "w": 100, - "h": 92 - } - }, - { - "filename": "0099.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 94, - "h": 92 - }, - "frame": { - "x": 0, - "y": 396, - "w": 94, - "h": 92 - } - }, - { - "filename": "0101.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 29, - "y": 2, - "w": 91, - "h": 92 - }, - "frame": { - "x": 94, - "y": 402, - "w": 91, - "h": 92 - } - }, - { - "filename": "0102.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 29, - "y": 2, - "w": 91, - "h": 92 - }, - "frame": { - "x": 94, - "y": 402, - "w": 91, - "h": 92 - } - }, - { - "filename": "0110.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 2, - "w": 92, - "h": 92 - }, - "frame": { - "x": 185, - "y": 406, - "w": 92, - "h": 92 - } - }, - { - "filename": "0120.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 6, - "w": 96, - "h": 92 - }, - "frame": { - "x": 277, - "y": 410, - "w": 96, - "h": 92 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0100.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 2, - "w": 92, - "h": 93 - }, - "frame": { - "x": 465, - "y": 421, - "w": 92, - "h": 93 - } - }, - { - "filename": "0119.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 4, - "w": 93, - "h": 94 - }, - "frame": { - "x": 0, - "y": 488, - "w": 93, - "h": 94 - } - }, - { - "filename": "0111.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 1, - "w": 90, - "h": 95 - }, - "frame": { - "x": 93, - "y": 494, - "w": 90, - "h": 95 - } - }, - { - "filename": "0112.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 1, - "w": 90, - "h": 95 - }, - "frame": { - "x": 93, - "y": 494, - "w": 90, - "h": 95 - } - }, - { - "filename": "0118.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 3, - "w": 93, - "h": 95 - }, - "frame": { - "x": 183, - "y": 498, - "w": 93, - "h": 95 - } - }, - { - "filename": "0113.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 0, - "w": 86, - "h": 96 - }, - "frame": { - "x": 276, - "y": 502, - "w": 86, - "h": 96 - } - }, - { - "filename": "0114.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 0, - "w": 86, - "h": 96 - }, - "frame": { - "x": 276, - "y": 502, - "w": 86, - "h": 96 - } - }, - { - "filename": "0116.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 1, - "w": 90, - "h": 96 - }, - "frame": { - "x": 362, - "y": 505, - "w": 90, - "h": 96 - } - }, - { - "filename": "0117.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 2, - "w": 90, - "h": 96 - }, - "frame": { - "x": 452, - "y": 514, - "w": 90, - "h": 96 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:d1fca3cca7f334c5072e6d9eac438f12:ad4656a8607db0b15f07d4c8860395b7:25c89a8ec37b43392b53a70993acdff3$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0047.png", + "frame": { "x": 0, "y": 161, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 9, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0048.png", + "frame": { "x": 374, "y": 220, "w": 93, "h": 89 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 10, "w": 93, "h": 89 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0049.png", + "frame": { "x": 832, "y": 275, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 8, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0050.png", + "frame": { "x": 282, "y": 169, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 9, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0051.png", + "frame": { "x": 191, "y": 132, "w": 91, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 8, "w": 91, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0052.png", + "frame": { "x": 0, "y": 71, "w": 98, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 8, "w": 98, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0053.png", + "frame": { "x": 829, "y": 0, "w": 97, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 6, "w": 97, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0054.png", + "frame": { "x": 92, "y": 222, "w": 90, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 6, "w": 90, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0055.png", + "frame": { "x": 926, "y": 0, "w": 89, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 5, "w": 89, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0056.png", + "frame": { "x": 90, "y": 313, "w": 88, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 5, "w": 88, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0057.png", + "frame": { "x": 0, "y": 251, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 6, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0058.png", + "frame": { "x": 743, "y": 271, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 33, "y": 4, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0059.png", + "frame": { "x": 98, "y": 132, "w": 93, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 31, "y": 5, "w": 93, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0060.png", + "frame": { "x": 182, "y": 223, "w": 93, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 28, "y": 7, "w": 93, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0061.png", + "frame": { "x": 793, "y": 92, "w": 97, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 8, "w": 97, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0062.png", + "frame": { "x": 476, "y": 135, "w": 97, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 9, "w": 97, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0063.png", + "frame": { "x": 560, "y": 225, "w": 93, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 7, "w": 93, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0064.png", + "frame": { "x": 467, "y": 221, "w": 93, "h": 89 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 93, "h": 89 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0065.png", + "frame": { "x": 275, "y": 259, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 25, "y": 5, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0066.png", + "frame": { "x": 921, "y": 275, "w": 87, "h": 93 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 4, "w": 87, "h": 93 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0067.png", + "frame": { "x": 536, "y": 313, "w": 83, "h": 95 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 2, "w": 83, "h": 95 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0068.png", + "frame": { "x": 182, "y": 311, "w": 84, "h": 96 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 1, "w": 84, "h": 96 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0069.png", + "frame": { "x": 0, "y": 341, "w": 80, "h": 97 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 80, "h": 97 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0070.png", + "frame": { "x": 619, "y": 332, "w": 81, "h": 96 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 27, "y": 2, "w": 81, "h": 96 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0071.png", + "frame": { "x": 364, "y": 309, "w": 86, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 4, "w": 86, "h": 94 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0072.png", + "frame": { "x": 450, "y": 310, "w": 86, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 25, "y": 5, "w": 86, "h": 94 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0073.png", + "frame": { "x": 757, "y": 179, "w": 90, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 90, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0074.png", + "frame": { "x": 847, "y": 183, "w": 90, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0075.png", + "frame": { "x": 890, "y": 92, "w": 92, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.9.2-x64", + "image": "249.png", + "format": "I8", + "size": { "w": 1015, "h": 438 }, + "scale": "1" + } } diff --git a/public/images/pokemon/back/249.png b/public/images/pokemon/back/249.png index e08ce36180406aee3f800b75612b66033f1f5305..181c7076e5dbd19018df07f82a5128017a10d63a 100644 GIT binary patch literal 37468 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 z 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 literal 25978 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 diff --git a/public/images/pokemon/back/shiny/249.json b/public/images/pokemon/back/shiny/249.json index 7512f163c15..7f126e8c62c 100644 --- a/public/images/pokemon/back/shiny/249.json +++ b/public/images/pokemon/back/shiny/249.json @@ -1,2540 +1,686 @@ -{ - "textures": [ - { - "image": "249.png", - "format": "RGBA8888", - "size": { - "w": 610, - "h": 610 - }, - "scale": 1, - "frames": [ - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 36, - "w": 143, - "h": 64 - }, - "frame": { - "x": 0, - "y": 0, - "w": 143, - "h": 64 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 35, - "w": 146, - "h": 65 - }, - "frame": { - "x": 143, - "y": 0, - "w": 146, - "h": 65 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 30, - "w": 132, - "h": 67 - }, - "frame": { - "x": 289, - "y": 0, - "w": 132, - "h": 67 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 28, - "w": 140, - "h": 68 - }, - "frame": { - "x": 421, - "y": 0, - "w": 140, - "h": 68 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 2, - "y": 30, - "w": 138, - "h": 70 - }, - "frame": { - "x": 0, - "y": 64, - "w": 138, - "h": 70 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 0, - "y": 30, - "w": 146, - "h": 70 - }, - "frame": { - "x": 138, - "y": 65, - "w": 146, - "h": 70 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 10, - "y": 28, - "w": 130, - "h": 70 - }, - "frame": { - "x": 284, - "y": 67, - "w": 130, - "h": 70 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 1, - "y": 26, - "w": 145, - "h": 73 - }, - "frame": { - "x": 414, - "y": 68, - "w": 145, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 7, - "y": 13, - "w": 124, - "h": 82 - }, - "frame": { - "x": 0, - "y": 134, - "w": 124, - "h": 82 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 17, - "y": 11, - "w": 106, - "h": 87 - }, - "frame": { - "x": 124, - "y": 135, - "w": 106, - "h": 87 - } - }, - { - "filename": "0106.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 25, - "y": 5, - "w": 99, - "h": 88 - }, - "frame": { - "x": 230, - "y": 137, - "w": 99, - "h": 88 - } - }, - { - "filename": "0107.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 5, - "w": 99, - "h": 88 - }, - "frame": { - "x": 329, - "y": 141, - "w": 99, - "h": 88 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 6, - "w": 98, - "h": 89 - }, - "frame": { - "x": 428, - "y": 141, - "w": 98, - "h": 89 - } - }, - { - "filename": "0115.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 26, - "y": 0, - "w": 84, - "h": 97 - }, - "frame": { - "x": 526, - "y": 141, - "w": 84, - "h": 97 - } - }, - { - "filename": "0105.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 27, - "y": 4, - "w": 96, - "h": 89 - }, - "frame": { - "x": 0, - "y": 216, - "w": 96, - "h": 89 - } - }, - { - "filename": "0108.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 25, - "y": 4, - "w": 96, - "h": 89 - }, - "frame": { - "x": 96, - "y": 222, - "w": 96, - "h": 89 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 20, - "y": 7, - "w": 98, - "h": 90 - }, - "frame": { - "x": 192, - "y": 225, - "w": 98, - "h": 90 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 7, - "w": 95, - "h": 90 - }, - "frame": { - "x": 290, - "y": 229, - "w": 95, - "h": 90 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 7, - "w": 95, - "h": 90 - }, - "frame": { - "x": 290, - "y": 229, - "w": 95, - "h": 90 - } - }, - { - "filename": "0109.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 4, - "w": 95, - "h": 90 - }, - "frame": { - "x": 385, - "y": 230, - "w": 95, - "h": 90 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 4, - "w": 95, - "h": 91 - }, - "frame": { - "x": 480, - "y": 238, - "w": 95, - "h": 91 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 5, - "w": 95, - "h": 91 - }, - "frame": { - "x": 0, - "y": 305, - "w": 95, - "h": 91 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 6, - "w": 95, - "h": 91 - }, - "frame": { - "x": 95, - "y": 311, - "w": 95, - "h": 91 - } - }, - { - "filename": "0103.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 32, - "y": 2, - "w": 92, - "h": 91 - }, - "frame": { - "x": 190, - "y": 315, - "w": 92, - "h": 91 - } - }, - { - "filename": "0104.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 32, - "y": 2, - "w": 92, - "h": 91 - }, - "frame": { - "x": 190, - "y": 315, - "w": 92, - "h": 91 - } - }, - { - "filename": "0097.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 16, - "y": 5, - "w": 101, - "h": 91 - }, - "frame": { - "x": 282, - "y": 319, - "w": 101, - "h": 91 - } - }, - { - "filename": "0096.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 5, - "w": 94, - "h": 92 - }, - "frame": { - "x": 383, - "y": 320, - "w": 94, - "h": 92 - } - }, - { - "filename": "0098.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 16, - "y": 4, - "w": 100, - "h": 92 - }, - "frame": { - "x": 477, - "y": 329, - "w": 100, - "h": 92 - } - }, - { - "filename": "0099.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 94, - "h": 92 - }, - "frame": { - "x": 0, - "y": 396, - "w": 94, - "h": 92 - } - }, - { - "filename": "0101.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 29, - "y": 2, - "w": 91, - "h": 92 - }, - "frame": { - "x": 94, - "y": 402, - "w": 91, - "h": 92 - } - }, - { - "filename": "0102.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 29, - "y": 2, - "w": 91, - "h": 92 - }, - "frame": { - "x": 94, - "y": 402, - "w": 91, - "h": 92 - } - }, - { - "filename": "0110.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 2, - "w": 92, - "h": 92 - }, - "frame": { - "x": 185, - "y": 406, - "w": 92, - "h": 92 - } - }, - { - "filename": "0120.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 6, - "w": 96, - "h": 92 - }, - "frame": { - "x": 277, - "y": 410, - "w": 96, - "h": 92 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 3, - "w": 92, - "h": 93 - }, - "frame": { - "x": 373, - "y": 412, - "w": 92, - "h": 93 - } - }, - { - "filename": "0100.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 2, - "w": 92, - "h": 93 - }, - "frame": { - "x": 465, - "y": 421, - "w": 92, - "h": 93 - } - }, - { - "filename": "0119.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 4, - "w": 93, - "h": 94 - }, - "frame": { - "x": 0, - "y": 488, - "w": 93, - "h": 94 - } - }, - { - "filename": "0111.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 1, - "w": 90, - "h": 95 - }, - "frame": { - "x": 93, - "y": 494, - "w": 90, - "h": 95 - } - }, - { - "filename": "0112.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 1, - "w": 90, - "h": 95 - }, - "frame": { - "x": 93, - "y": 494, - "w": 90, - "h": 95 - } - }, - { - "filename": "0118.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 22, - "y": 3, - "w": 93, - "h": 95 - }, - "frame": { - "x": 183, - "y": 498, - "w": 93, - "h": 95 - } - }, - { - "filename": "0113.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 0, - "w": 86, - "h": 96 - }, - "frame": { - "x": 276, - "y": 502, - "w": 86, - "h": 96 - } - }, - { - "filename": "0114.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 0, - "w": 86, - "h": 96 - }, - "frame": { - "x": 276, - "y": 502, - "w": 86, - "h": 96 - } - }, - { - "filename": "0116.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 23, - "y": 1, - "w": 90, - "h": 96 - }, - "frame": { - "x": 362, - "y": 505, - "w": 90, - "h": 96 - } - }, - { - "filename": "0117.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 146, - "h": 100 - }, - "spriteSourceSize": { - "x": 24, - "y": 2, - "w": 90, - "h": 96 - }, - "frame": { - "x": 452, - "y": 514, - "w": 90, - "h": 96 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:d7da4020739cd4d93722302e0d6f99c6:d519731f31735f11bbba940400858c46:25c89a8ec37b43392b53a70993acdff3$" - } +{ "frames": [ + { + "filename": "0001.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0002.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0003.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0004.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0005.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0006.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0007.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0008.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0009.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0010.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0011.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0012.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0013.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0014.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0015.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0016.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0017.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0018.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0019.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0020.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0021.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0022.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0023.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0024.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0025.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0026.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0027.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0028.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0029.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0030.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0031.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0032.png", + "frame": { "x": 690, "y": 66, "w": 103, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 18, "y": 14, "w": 103, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0033.png", + "frame": { "x": 554, "y": 0, "w": 136, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 3, "y": 33, "w": 136, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0034.png", + "frame": { "x": 143, "y": 69, "w": 141, "h": 63 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 40, "w": 141, "h": 63 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0035.png", + "frame": { "x": 409, "y": 0, "w": 145, "h": 65 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 38, "w": 145, "h": 65 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0036.png", + "frame": { "x": 143, "y": 0, "w": 144, "h": 69 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 1, "y": 33, "w": 144, "h": 69 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0037.png", + "frame": { "x": 0, "y": 0, "w": 143, "h": 71 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 2, "y": 30, "w": 143, "h": 71 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0038.png", + "frame": { "x": 409, "y": 65, "w": 131, "h": 68 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 32, "w": 131, "h": 68 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0039.png", + "frame": { "x": 540, "y": 69, "w": 134, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 7, "y": 33, "w": 134, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0040.png", + "frame": { "x": 690, "y": 0, "w": 139, "h": 66 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 0, "y": 32, "w": 139, "h": 66 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0041.png", + "frame": { "x": 287, "y": 0, "w": 122, "h": 81 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 8, "y": 16, "w": 122, "h": 81 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0042.png", + "frame": { "x": 380, "y": 133, "w": 96, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 10, "w": 96, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0043.png", + "frame": { "x": 573, "y": 135, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0044.png", + "frame": { "x": 653, "y": 242, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0045.png", + "frame": { "x": 665, "y": 152, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 100 + }, + { + "filename": "0046.png", + "frame": { "x": 284, "y": 81, "w": 96, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 21, "y": 11, "w": 96, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0047.png", + "frame": { "x": 0, "y": 161, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 9, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0048.png", + "frame": { "x": 374, "y": 220, "w": 93, "h": 89 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 10, "w": 93, "h": 89 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0049.png", + "frame": { "x": 832, "y": 275, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 8, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0050.png", + "frame": { "x": 282, "y": 169, "w": 92, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 9, "w": 92, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0051.png", + "frame": { "x": 191, "y": 132, "w": 91, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 8, "w": 91, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0052.png", + "frame": { "x": 0, "y": 71, "w": 98, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 8, "w": 98, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0053.png", + "frame": { "x": 829, "y": 0, "w": 97, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 17, "y": 6, "w": 97, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0054.png", + "frame": { "x": 92, "y": 222, "w": 90, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 6, "w": 90, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0055.png", + "frame": { "x": 926, "y": 0, "w": 89, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 5, "w": 89, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0056.png", + "frame": { "x": 90, "y": 313, "w": 88, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 5, "w": 88, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0057.png", + "frame": { "x": 0, "y": 251, "w": 90, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 6, "w": 90, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0058.png", + "frame": { "x": 743, "y": 271, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 33, "y": 4, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0059.png", + "frame": { "x": 98, "y": 132, "w": 93, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 31, "y": 5, "w": 93, "h": 90 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0060.png", + "frame": { "x": 182, "y": 223, "w": 93, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 28, "y": 7, "w": 93, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0061.png", + "frame": { "x": 793, "y": 92, "w": 97, "h": 87 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 8, "w": 97, "h": 87 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0062.png", + "frame": { "x": 476, "y": 135, "w": 97, "h": 86 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 9, "w": 97, "h": 86 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0063.png", + "frame": { "x": 560, "y": 225, "w": 93, "h": 88 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 7, "w": 93, "h": 88 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0064.png", + "frame": { "x": 467, "y": 221, "w": 93, "h": 89 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 93, "h": 89 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0065.png", + "frame": { "x": 275, "y": 259, "w": 89, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 25, "y": 5, "w": 89, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0066.png", + "frame": { "x": 921, "y": 275, "w": 87, "h": 93 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 4, "w": 87, "h": 93 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + }, + { + "filename": "0067.png", + "frame": { "x": 536, "y": 313, "w": 83, "h": 95 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 2, "w": 83, "h": 95 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0068.png", + "frame": { "x": 182, "y": 311, "w": 84, "h": 96 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 1, "w": 84, "h": 96 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0069.png", + "frame": { "x": 0, "y": 341, "w": 80, "h": 97 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 26, "y": 0, "w": 80, "h": 97 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0070.png", + "frame": { "x": 619, "y": 332, "w": 81, "h": 96 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 27, "y": 2, "w": 81, "h": 96 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0071.png", + "frame": { "x": 364, "y": 309, "w": 86, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 4, "w": 86, "h": 94 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 50 + }, + { + "filename": "0072.png", + "frame": { "x": 450, "y": 310, "w": 86, "h": 94 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 25, "y": 5, "w": 86, "h": 94 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 30 + }, + { + "filename": "0073.png", + "frame": { "x": 757, "y": 179, "w": 90, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 7, "w": 90, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 70 + }, + { + "filename": "0074.png", + "frame": { "x": 847, "y": 183, "w": 90, "h": 92 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 24, "y": 7, "w": 90, "h": 92 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 20 + }, + { + "filename": "0075.png", + "frame": { "x": 890, "y": 92, "w": 92, "h": 91 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 23, "y": 8, "w": 92, "h": 91 }, + "sourceSize": { "w": 146, "h": 103 }, + "duration": 80 + } + ], + "meta": { + "app": "https://www.aseprite.org/", + "version": "1.3.9.2-x64", + "image": "249.png", + "format": "I8", + "size": { "w": 1015, "h": 438 }, + "scale": "1" + } } diff --git a/public/images/pokemon/back/shiny/249.png b/public/images/pokemon/back/shiny/249.png index bf37f7246130073e296a1667afdba5f910ed0400..066a38999e04c88620274e43985faec5b3fa0290 100644 GIT binary patch literal 37468 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 z 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 literal 25981 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 Date: Mon, 27 Jan 2025 01:28:03 +0800 Subject: [PATCH 51/54] [UI/UX] Allow viewing egg list from rewards screen (#5181) --- src/ui/menu-ui-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index bb950f328e8..7ea8dbe7a4e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -111,7 +111,7 @@ export default class MenuUiHandler extends MessageUiHandler { render() { const ui = this.getUi(); this.excludedMenus = () => [ - { condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, options: [ MenuOptions.EGG_GACHA, MenuOptions.EGG_LIST ]}, + { condition: globalScene.getCurrentPhase() instanceof SelectModifierPhase, options: [ MenuOptions.EGG_GACHA ]}, { condition: bypassLogin, options: [ MenuOptions.LOG_OUT ]} ]; From 2593d0206cda17ea1a1be4c78670699844fcd17f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sun, 26 Jan 2025 16:12:11 -0800 Subject: [PATCH 52/54] [i18n] Update locales submodule (#5190) --- public/locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales b/public/locales index e07ab625f20..5ef993b95fa 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit e07ab625f2080afe36b61fad291b0ec5eff4000c +Subproject commit 5ef993b95fa8248adc0fb7d9489baccf546bf8e3 From ec68ec2066497aecaf6ce9d93625668e524ed556 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:22:47 +0100 Subject: [PATCH 53/54] [UI/UX] Year of the Snake event banners (#5192) * Update loading-scene.ts * 2025 Lunar Year Banners --- public/images/events/yearofthesnakeevent-de.png | Bin 0 -> 43377 bytes public/images/events/yearofthesnakeevent-en.png | Bin 0 -> 41710 bytes .../images/events/yearofthesnakeevent-es-ES.png | Bin 0 -> 41782 bytes public/images/events/yearofthesnakeevent-fr.png | Bin 0 -> 41820 bytes public/images/events/yearofthesnakeevent-it.png | Bin 0 -> 41648 bytes public/images/events/yearofthesnakeevent-ja.png | Bin 0 -> 41759 bytes public/images/events/yearofthesnakeevent-ko.png | Bin 0 -> 42012 bytes .../images/events/yearofthesnakeevent-pt-BR.png | Bin 0 -> 41711 bytes .../images/events/yearofthesnakeevent-zh-CN.png | Bin 0 -> 43765 bytes src/loading-scene.ts | 4 ++-- 10 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 public/images/events/yearofthesnakeevent-de.png create mode 100644 public/images/events/yearofthesnakeevent-en.png create mode 100644 public/images/events/yearofthesnakeevent-es-ES.png create mode 100644 public/images/events/yearofthesnakeevent-fr.png create mode 100644 public/images/events/yearofthesnakeevent-it.png create mode 100644 public/images/events/yearofthesnakeevent-ja.png create mode 100644 public/images/events/yearofthesnakeevent-ko.png create mode 100644 public/images/events/yearofthesnakeevent-pt-BR.png create mode 100644 public/images/events/yearofthesnakeevent-zh-CN.png diff --git a/public/images/events/yearofthesnakeevent-de.png b/public/images/events/yearofthesnakeevent-de.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e684bbcd5a6117c8da7787ead7499583f7a217 GIT binary patch literal 43377 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⪙=2gFo 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 literal 0 HcmV?d00001 diff --git a/public/images/events/yearofthesnakeevent-es-ES.png b/public/images/events/yearofthesnakeevent-es-ES.png new file mode 100644 index 0000000000000000000000000000000000000000..76474c475c58d5e79cc76c81337fcbc2185aa3a0 GIT binary patch literal 41782 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 Gv;PAqWUlxC literal 0 HcmV?d00001 diff --git a/public/images/events/yearofthesnakeevent-fr.png b/public/images/events/yearofthesnakeevent-fr.png new file mode 100644 index 0000000000000000000000000000000000000000..88ad3e770ca788d9807f799ffdaa728fff6b0771 GIT binary patch literal 41820 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 OuYXOfjLVH&pZ_1UORH`G literal 0 HcmV?d00001 diff --git a/public/images/events/yearofthesnakeevent-ja.png b/public/images/events/yearofthesnakeevent-ja.png new file mode 100644 index 0000000000000000000000000000000000000000..ba13b5d281f4fd30dec57fae78bffb5ba88c49ee GIT binary patch literal 41759 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 literal 0 HcmV?d00001 diff --git a/public/images/events/yearofthesnakeevent-pt-BR.png b/public/images/events/yearofthesnakeevent-pt-BR.png new file mode 100644 index 0000000000000000000000000000000000000000..a535aec2aa5a13e87a00ed606b90dab962533abf GIT binary patch literal 41711 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 Date: Mon, 27 Jan 2025 17:44:21 -0500 Subject: [PATCH 54/54] [Misc] Charms in any events (#5194) --- src/phases/trainer-victory-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/trainer-victory-phase.ts b/src/phases/trainer-victory-phase.ts index 9722e9c4197..e2617f598da 100644 --- a/src/phases/trainer-victory-phase.ts +++ b/src/phases/trainer-victory-phase.ts @@ -29,7 +29,7 @@ export class TrainerVictoryPhase extends BattlePhase { globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc)); } - if (globalScene.eventManager.getShinyMultiplier() > 1) { //If a shiny boosting event is active + if (globalScene.eventManager.isEventActive()) { for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) { globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc)); }

`jAylrO9!%>SjJh|#40_|9Csl8kuS4*axN%b^Vk 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 diff --git a/public/images/pokemon/female/3.png b/public/images/pokemon/female/3.png index 646157beb3277d6440a3d9622b237ea1303ce776..e21b53dfd573105824c27d9cfae2a39168fce2b3 100644 GIT binary patch literal 34777 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 ^acawz<0gqj$qSpE= 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 U*fD3gxBvhE07*qoM6N<$f;Lqf#{d8T literal 25751 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 diff --git a/public/images/pokemon/shiny/3-gigantamax.png b/public/images/pokemon/shiny/3-gigantamax.png index ee4705de701934c54dc13c57252db981618350cb..acc57a416719c6d40f8d7df424803e6cac31b30d 100644 GIT binary patch delta 1670 zcmV;126_3N4aNIFua4E z!{1r|Ho(hNS585HK6%6viSY)2%95_C3y05Vl3%|EJmw-RlzA0-I*;pm3qW;Af3|~W zvvNt+7{7OER?o#N1-yhrAOd`y1b2@9TRjVFTLFCU(yUG64JymQQZkdi)7O=iJJdh9 z22y|<$~@Z|kc0M*9=MC{=u`Gam0ynWHd=3KA~s3;#wn|ReU~o1C53KK4G8dkk*@)( zBEHtYBFIwywuz#|{L*7Q=zqD&Sn(<0$0s9<6~6-%@#izeU5>jVQilC!eG%Vx7fSkd z*Fy#<;@1G4`d;Kn#pQMzN@d__Zv94{p4k;}(9c8B+)`lFR6b*ne2T@JgMKx%XTZMa zKKMjF8=U8Vzt=b`LVdO~!Nz|;uy(Xo*|Qt_UJA0efKrHj`&<1(Jx^v_04z^^>wWHQW@+?qs{(=Y151-c3R z3Sf^kgqTv^6Fax3qab_$NVQ9mPsE(qQ;kQ#E5KC>F)4sQY%k^Smw@yJkljobp#@l! zTw*Iom*Ap}-T^@MA!R`FhKf)CatR<^5X@-zzz8CG1jOb(PI09lEYC&=AiclV_ z0MSzbyG{_#h4uI6P^CbQg?J6HOJ)fT2!IuVyH7v)dXQW=t>O6`4*+qsrysomz$**t zQ3Q1gk)Gz@S=u9E9LKu)!HOO`VZwCR06hSI4g}zsC_rPH_#9k9&)dGO(!)UtNqIxm zrV)bZr%3wKMPxD1ZkD00#%)83Z{YIU6|O5Hy53YFDkw4_$6F zf6`^?;HsFTLZJarAUvxZiH044ay$Q{7jh5<+)T%UW6I;klz1GcR1g+QogELGMdFcWBh7<{MDKH||q^jpA2 z<#qkCqM`IA7=iyXV8{T?f>(*`AR8Vk9I||+PhYbmW)B}dElI)Vp36X227bkVHvm>& zrk?&(%BnS{hMK_qvJ|RNGwD?$FEbaf)u-d0$NOfkv9|0aL!1X30I1!?&9MXv60bTR z+C3ck>e2v^0f7O*x9l2#1|Wh(t>8tWRLdX(;6e;eBiydb5+E-y z?H&g3TbC#1D0Sflr~O(>Iw*)gZN$A)7(YhjG8LYVk3a8D7&vKEfF|@MQwrw zeK%47|2DA~Y6Z~cHrYGvf)fg8b5R>0khV4A~@W>5%m) zf&8oBCjf0&{&P~rg>eQf6Llb9P5{KDP9bQm2m>>KUJvvajFJPhMFyZ+(f>-I{=Ch< z%TD^DM*5pi2Dgp>j8@&bw82GigQz9D_OQOF6#zNJi0#zWC%_^Altm^@-C_WXyyH`V{ezxZF`58~Xqi0kaR Qa{vGU07*qoM6N<$g4?$S=>Px# delta 1629 zcmV-j2BP`K4V?{;7zqRe0000GwrUHJECR+rKOW}sW4b};}$rnLVQSBgI<-}lfu?#SgPu_wKs|81R#ajUxYE&1x#Xp@m?>KX7H9UC8xA#Fp1N{U%E1X-mOSKj zy<$remx>0TLey-h81}bwv1RK7}>1`F~$T= z)27kBvx^DhP9sL(&z>IeIR@MZ=YMx@r4=4gURS48>0uaErTcA*#Ay_ z#rKyP0HDaAza&qJJv?Z<^zMZLYkq!!XU!tmVZesQ{o76*>o#r#VE~r=1RRp4VAze8 z5(xopxC^A0v9X{s0aQ6xkTSNXAY|dkuwlU9W7s%VIu|CADlFXv_b&N|OH0XJ5m%}5 zLxLn~KF7(+6<3Vxmgq1O$$Y;L&|wEK^v<6Zz~+Dm0SuI)K1$*(KA!FY za1^nysa;qgfjF1~#7f07SDZM8&sWzS@P)Kni-;+o$Fl&y^Lu|FPgflfN_O)IEW1a3 zK^yejaH*7e5KFU`aW0kWK`iCA5rXTxU*hif{b%@!0LpTmC#5lK0IBj0oG6)3UQx~U zEO26I0HI=IgeeSw`9THfcI^QqG4!0lCjb^`;6MUU7A7eH*5pfd?!cJj^w0qRtoKU? zasyx}0YD0DK|Fs^xenr3{ycRJ{(SlJ^7aUTy~!jPrzoP5-xRr!c?XJDM0oh6Wp6dt7!RGH8{ULgik79SFBcso+Aa9B4n^ zb_dPC2QX1#3KKC<43NYqMHkag4rs35z(7U3Efsi1SUi7F-q3->#Nph5w?%DT16JL( z5t%ZLk^%rLa88UFhavC$rf(lDBCG>M0BB%Rii&9b4ib}$?3qSxYs>Mi1Gn{^14$Z2 z#hkE&0|`J?Bd(Ba59&H9qY|J@L%+~Cu6Hp0TUkZ+9V{ORhsJj(4K*zQa<@Y=e!!aK zGO|aotSNsQGbk|cB{_g3-o!5L78g-{jINg$>MWro0l3rw5jlXf&aZ;CW&{E_FzuN0exj|0G4-WwXGQF zKnWr8BS0906kehF2B2JGCd6(ljrd9`^&RkhO%#9KXl?WXJU&zt^j0MJwJC#4 zG?=T4&m%EdRF`5h4^&QcSh~s5O~ZZL3@>wcu|^o!kb4i^KrUI+c3?jA+gBp*2sk#p z)R*4CU3VeTrdd0|pB^5sX0!WukWDgId`R|6Sxd)aZqUxMS$&VwsG4??z5WxR^rF9X z1Widht1||EOZfKN48Ls((9~H|*WQo@c>G(4K<--xgAWE<`E7GYgO7$VN!#CukE6kV bkpJ@+8QCv@w_{sP00000NkvXXu0mjf_DtXT diff --git a/public/images/pokemon/shiny/3.png b/public/images/pokemon/shiny/3.png index fb4545e301966f666930b60ad6ec84c3bfcc5b8a..4afd3847484c849d4526a840dd163220bb8c2206 100644 GIT binary patch literal 34560 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 z z*}H)RXo)wbP7Rc(+a9OQ7i(u436zbyx^IVCJ$27x-;qDCRJ+o+q;~D4(91-TndxW4 z$+(i#k;mV9c%t^?qI0F5(BJ8V8a?Mj4_r7lOEIlLN*ojDuyh`aGR=vId{LY5Fv+@oP` zl9W)PC~yaLCU22(6za?>7KoU@xJwzuqtos#9%LVkdDB0L#*yTx#j4iX+I5A42ifQk zq%h-2o)btY?$NNhfT9}S);f#-MXjg8d)}suSxaSI+%w3$=|?$7(KwzQ-<{;D?4H3dJoNRzs0vp98Xr1hH|GKS2tVhG>;;4e6PS{^(jbcdRCG_YG%BY1g(8VC(hC!BnGmf-tV6?uv=WNy;nfXwp8Ob}A&HPvdvv!lP8Aw;!u~;4l=hn~GZzLq z791f59r5EBdDFA(37fUX9~_n}lAlLRPKaVJiHe*!VJM3Ib*5!@Lrh-J!UlNvDr1et z!1Q|uSy9?}kXyFQTpHxafM=|J7$5iVdeh|Oz`ri6(}tUU0?x8ZHE=={J4mdf7>VMg zx6WP_Q(25Hx?35`G{&aid8tuU+9c(eEi*P&a?IrTp%-jKc6?x&1Fh%juJ58`F^WAT z)=*4D@zSnyKw^%Zl*V>8P{S;N)r&)7VzVh{iVG9fPcy{)jDe_=~-zAy3fGV1sjV%(xHP z&OJi0Nn#7dP!v1rToc7hj>fL;u2SX<-#su^GJd^f{t0e)+#H$jO*E^{_Nz`}6U9Un zTMpO~#YE zoN>k(XPj}y8E2ev#u;avamE>EoN>k(XPj}y8E2ev#u=sfA0yYi_@% literal 25834 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 diff --git a/public/images/pokemon/shiny/female/3.png b/public/images/pokemon/shiny/female/3.png index aeacf461779620dca5b49420e4cd52175056e9a1..ec5a9849e6023dccf527cd2d8cb4abe2ba62bc89 100644 GIT binary patch literal 34586 zcmY&<1yCGK)NK+VxGwH4!JS2eyL*rXcU#=ah9J8*1YK+i5L|-<3$nPoy9N(#;pcm= z{#W&;YN~6x=1O