8m1z{8NwR&-VEmoI-|E
zeW(noFP8)AB7f?#yqH`j$BX$Fu77U-uK*ymR#rb_{An%>#?KHOu1Nq0<2xaLs?Bi-
zW>MjGR1VXhO{SUzfN3f%qhS$@*i<5y$#!5eeSRF2&5x9!Mn+IoC%P|%8OTxlJ9Vlt
zkxSK6L?KWZ7!mNp@U1T>DIjnF`#2nfVCRVyZiP7D4gRTP4N!vCWxSTGbKm-ug$
zDP#hT$z~8ixO4{5lL}|~dMZMHl1MOO`Y_pGU@$xM*DS282QmW0Bh(YS9YKzm~**?;+5f7nw%do%(^
z*1=FgE3!5XhC^X=U_>-o2Zp6m$wU&FMAV_te#GC;rh|P&^!cy-fc5`3gg?CO0TFS)
zgX4Vf4nv~ncgcsY|E&`#Ofs12-%N2NG?qrA=)fpcDi($zVlXhAHiia6Y9mlY9W+e`
zfuih!FEAz^BZ)zMSjT=a0
zQ}=j+Rrqh36#iGKbBF=|I&m_IMj|7L;JJ`dI2Z*7vW-VkF)#`qLqVc(Br2MM`T_i(
z5=Y_*XcPgd^P~U&?}>k>l^}e|FP`8
z?)}~E1RmnoCGg5y7JuHg;KR?`n(7O(#0KwTdYJ4C0Ia`mZoJ1KD80!&+$P!dV%QPX
zqf?iBhJ5o#g??z?s}RX82@Vn;R(p~Z^PfC+?-%cUpkbxrFYI`uQWCVY=GQ2vG)ud~
zJ4z@CA!&7|tR7s}zPcN_On}
z?RA;pD6>*9!`JCKif)vIL@1@gZB?rx`yNCFCAw7@yYByzdTP>dXiC{)8^3w01+BT?
zf3PTP3<*@7EnJoF)p{$f^n~b`YKWv|!!`gY3+~4{+s#D?KRkIL{nk%eCP|F*OnFB{
zllr#jQVNz;?Ws}&De|%q$-;{d-;MPjkyKifH|}3Ksx~8#oYKhk0B(PYx-<)WQ>AyBt0Ab44OOqH4Ssn3tP$g$N-fo}tLDHnvn!Q>9?%yDG4+6us?
z=-1Ans^+$}D1*#vNF7*8QD
zx#$XLZDWDx&bjk@`c@gjB`?vG*hjn9Udxk9Y)+Xu}8*4Ew>BLNN`5pTL3`q=85*ZKDr&5qMGHt
zcAMcoqWML|l|5PAg9#@rlT_p301+n+v;)QN>YQ%`po7E&*Inn
zX9^;zV!5)rtSfV5V(mTQUOB|JjCs*?}nWkOw4C<^P|$)AaVsc-ZOp2v@4-12$mV9f+B
zx{PCfcW9!UiT`@gLe1p8;l{y8I|~WD`-i5|qXERj{FWEFuqmRwYJ8XC_F^%n|I9t_
zX#q?W;fY-@2r4KnmQ0B`LoC)&eBzR@x~yZ7X!sp0=N*N|Wla28Ho8)g8Z^~Uv+cXQRi{ReYdPcmh6L%=mKWljkF}c0soxR
zgFr``JjS2$CigT{XIQLY&ED-;aOE}M^=T{i+IXCX6@=oZ+0S?F+?HLVmLVfbm&knt
zKqtBA9jz*m7b@>mvS%=qxR_4K_?FQ1$!y1@r^<<1y~YrZ7;iQ|)Q{>m8u__)AkMFq
zX{_$3Z}jftco)5{fmJ^F>f?cNOd0!&i?WEX%jAy9gLt8)pr?85O}Xt(rwuIYg#&l$
z{h8vxZTarW7h}4%>A{y`S6%TSGit^wCqj%a&7XO?f)tbzA4^)H9T<8dhSqm;RjOXY
z;>nW>9yp$hbEkFth7IdAc!hQS4_8heKfn6|0DZpUq{hPwuI~cSr#9L_3L&S^O!S10
z<286+abd7(-^pEteNCfL!?}q?Ev(r_-DJw!7gg5_LaLwVw7!w2>6w%jP1Cn51ZscI
zzml=ve#)d^V5cfij6R0>;-jm5!=P}qtf_?jHYjcG*p|!9w%YWu*MeJ*iLZG_7qeta
z@ZCJ)6t2>Sj62Zj?)1>x^bG-%`$WKfAnBHWob%$--$ZuiZ+H;g1G~B*ewD%!lo%J=
z8Otsfp;!-GBXyde7`z&$RM-X0T~(a1B_zA(aP&n_3-9QfOtr16ZwL3Vq7&DDwn=-H
z<*5XYFq<^H!uKy^&%MXoQH6@%_c_<)*#1NqfTV;fd|sozTHK
zowI(*5m!~ir%M-+@uTH${~%0HR-;H!e&yO^<&&azkUN=^yB$RyIY)2E=N7CrS-2#n
zHnd`r4s&seJS8%U>At(BMZd~XvoF@;DY(gSx<5u}jwQWj8VB6F;C13aF+GR)@}j!3
zi+H(PRz|dY?d$aD3-dAq@NXx|piH2d6=
zVC&)L+{=sO&+NjHxemMCd)Sn|5;1@^QUXuxq@7+@B}Ubc9~3N%7RqJc4f-RUZM3_l
zzG~XB*j`d^#nILGK6}5`HGZId_7iPF=oo1sI)P3-U*)@L7X|4iapSh@Y5&n7>n~LS
zYt%PgRe{uOD77q+$2~otvb$|~#ew_OVoj~(G4TEkKcPX)U_LA>V2hU9V$xYnZe@Xo
zu2f>nCS?)Fq@x3R)~+AwIUx@5iGxK^XPbJSg2WFMDEE}&EW^tHE8XVJU4f#nB*Wqd
z`O|LuM4UY}a_R5!O}+Ht8E*wo<;Rn4``VDwS}G9DcV8q)@4&)kN*HVTLaKJCL4Z(N
zKv-e?Q&^RjpbWOyv(fC_qRXCzy+gq{744ng$0w{q^8&W$%bD4qjZ&H|@1pY-sCiLh@P`ek!#rH$CX=+46Kfskf>F=`<$RolP$RMB`d2cduW#4$;>0DBbnjW%s8EC>AcBtXB@CQcpjQytcb}P
zFu!O!`Md8u)7ztt#jd$!AxSBL>+Gu0*Ebski(K%&oir)4&|MPT+pvj@_lwS8fv)m%
z_Hw%HBXjkh=cRWpI7mqJwaTw=gWr**UZa=8IVA21(vlv$2aYCd^(^-9-R)%%w
z?~2vR-KR(?7#kSn1D0zpN%iRnM#TF=jl+@Knm4b!alDouAQPMUzP2~?vhCI3o_W2z
z)8%~Oz1!PWrCEbkeagMfxna8#qh6Q!@;Y1Bsp5;m=ZA1o!N1%&7#B&-%RW_nhw%>X
z{b&RDi;Y!N_HN-T63;SDsvR7)Cl|uaEG(wB;#IN_&35rb^Ih;)uikWNUMur>anUkv
zP_}=QqcRI!ngx{XxznIHXwuOuKLz2PE!{XQHWgyz3(!)Nhvb6_ntW{2&pFS^+wQ7-
zG#C}R%C+W`)OLsI1+JCp0dTsNS~rywrwE2+)&9w!LdN4yO(NY))jBVx$q-jRnkY9gh3v#NU=^E#8Zif#cLKK1o*!{A-}F$c
z0y7s@?NO0EEQk)DUt#zG?vbSg&-e|gpKWktT^*~pa$jz{)VTvCBLRqUqQjz{uc+IBV2ZdcGr5$fa{EaGBez~@yG4AaA
znOPZP*Q(+eJ|u0lE+`;@`3%?QQPa7)w0Ghp`ax#LwomQW)mHbyZtPfdUpD-DMs&N^
zy$uj-5^11(?Z?}ha_gt#H2Nt8s=@*H*kfP96+n|#S03(S3_4X7<3F#?M
zaYl0GYBcE3SnyJ7jBk?o`hqDKLd9V7&YF^6fR5%vyAHXENNdVBo{l1GR0zFfK}U5i
z3|Panz#)d;RphKM4%u4BlLSvC@scRkwqrgnvw+Ech&5`$5(K%Xx>9_7hv|LvkrIi?
zxll8DOMQUnXNlc?G0A5~YGg+C8$~w^_yAQ6_aQ^WeDc5(xi9{@;qX4ccU=H*#ect^
zW3JSPL~DiZ4l`$-z7tr@M_)6nDS!r~cl0%+&?3}|j-SsrCe|(6f4l?0&ToZDopv)6
ziQv(Gb4lM?BG(;aRD1>e5*>btUNbCTH!H_A!B
zjBm`66aTIDNoOa?V_WpsB{k*yK!h;zQj(pW-rR@AtE6*krJB8s*Y1jK|A@YRO=i#;
zp=(ewDYwtnZ|M)K{Kc+#FX*GZR@U})NfhYy-`g3G~dqC(|E6N3tdvijSElXoRbsA!4Oa#gdz
z@pM%&!&Y#(hS=KF2YjiuvEdi@h3?O}sMWjR&tGTBzm(^r9}|Qy$5t-3SlNa>;gxr^
z7y@`dArLy+lZ3GrS}CQFe>c$-LPp+jd*Wv}+gITb&xQ!IrpnL-sbI`A@1@vyM?-KX
zMp}vuzZH#mo>!D8?xV#J1vE7-#uzRs0#ycydVTlqcx^lAcKP@NjcZFoJl?IF9e%h2
zw`xoJ;@>;%`1Mt|Mt1qNa@pW=_{L`5E9BiQml7+Hj{cibT^d_LP5EaIq#duw5;!|z
z)&VTkjKQ){vFLzN7>Nw`S9dnq?xDSr7qmK{Y}WJ6{B=?wPoa%p5{Q0W*Sz;IakcN{zH0Y(FCX=p3jM~Gqzp2Thx0E^#A;x}PI+#6
zbFCX-wet1c(jWezdT8&C#m{r0rU`kO`I^A)5e+roXiUZ3RE;n1+WjlHG6kD~d|__)
zM57(!MbBqrgH;Q+=0gT{Wr^@=9-pTI5ue&tqy;dfH>EOOJg8_3Yjhv0{Ze*WF|kLftVF43ET%$MV`0=(Gs
z{*dVsE5aW+`1^u`jUzqitHUU}_|`cQM=b)}==siGlmQv07`KQiv8Pgr5JC
zA*?d~h-NrpuD14l`(C6Ra&cP-eDV1keoN|L%(SD4B_hyoCNrUPdOGY@>Y5HfN~Yi2|mg;UcOW1$!UA~
zAq?3{a;o$n;Rb0{1ir@PxAZ&qKjZj0a;=QB8wBBkn58!B>aof^4CQUy=0M!q6b!e1
zs!KMqg&Xp`_Ve5QytrOrSE_AgTV{Lx(uycK$MTiCrxup~c`!GzHqJFXbo_q+-*fuj
literal 4467
zcmV-(5sdDMP)Px`6G=otRCt`tU4Kwj*PZ`dd|iK}*^=FMf@w@19aKQ6Lh@$$l@@Ai16bm~240OR
z?a2C^^+$F$8LSE+;i?d48<5y!qG2
z4nAzXdvPj3#3+Rc2s=~dZYcmds_}3t@GJpaU+P4an~(q~L^W0A+@L%MjyL}rC-?0e
z7dIgROx9e`z7WMkldg%z$$k4^0KkJwbF^gP3T1x;6jatKZhE$V39NDGA
zKhBV9V(dK}uXhr9DHLQ4Sg-aUk64^cX4@)IS_wnJH~>IH^DsieI22@w1LgG!*WTsX
zxAH=y(izoLbq*+IprLsf0F*j5%b=zUC}toOj6*0Ghlb{1&-&6bsA5t|5Dh#}!-A{<
zOJ+yMmPN*?1mige_>|I4S>BEFg8qdlnnh!x(f;ERb2lto8mlD>jr&Fo9{?;`{Dcnw
zXxvu|W7kir$?L~^wFIyrJAf%)`NADFTD$|ea_K$!X}Ec3na=7
zQ`4W)l7-VJo-=ntLiFs0hG7^!LI^=mnhyUUgb;&tr|R7eNp~tC-Kn%1zQUJN2vB
znPVNh1R>I^u9+EeZpth#k^ycv+|WLV8<9v^2Rz*LqP3fl60o4<5Csx-3EZHnC$jS_
z0gZM;yBbF)+zqH_(Gh^iP?cjRA8qrxGFkgV6jNJsS)23Pn#<;HShF}zOBQMl4M%pv
zBUkn8hTr%*!$*!>C8OFfwOH`8wG9Q4RnAwx(COaw@VHqyf}*)c`O@L_4IM8$@>F3V<&G
z%ee)jQYNJU0Phm0*m;#^T(@c|rf-=Jz#O4yhO0oB0k`zCGOs7nk@ZKRe<2!g>Lvme
zC$^fq;mY(4TC(usi7n=Cc;(P@S^~Ix@l%HH%Ax1TZ-2U1hksl-^c*qv6lL6g-zhC_
zI%;b_HhV7ioO{dGE(+XZ>9K8I7TI|QK$S&7P@XyfVDZJ0epovmu1hf=t%xrQ+#?*B
zOjkJonmV`60W%ZD5xru@P8HI<;IY|rrD@~!#$!h@t`d0ZsMsk5z=F!iT$a8e^$=WiRhxUairmgoqZO+?z-!pf^;#v1=$->^Di;-)?o@e!38)iIX_(;#Q
zB>bOxp0FT2&l2OKBE2^#T-O1ksmIpdI=zv$?H!w~cNY9HHyd6S^=G>q5$eBpr^G+!(i>x%u3k*w-J^-PwHw=8+FnqlS3HfPjhYtVfJxGYLV#Tj+ziY$#
z`b7YM%x|qg>YQb$uU{mUSv{F81*<0wfI0wx?%%u=kr(l5LcKBlF)s64O*Wo~bR|rk
zvrHJVNKaRa@5p*a;+Rvy+S0;~k4Q9@-b87?p`KSvdW6
zlertFJ@lBCEcCs$$J`AMkq#{ZEH?5D-yzaLj-B42!#@s@4q{aH72STj;l^ZhVwOIU
zius`=tIcvEy=XXT02`B=Q2InF0H9^tX;a2WK4{2ge2>-3#W2k{rr>^llt=>c$B(
zK(R5oX>8=%%;*7o8YxFXM+^X^NVS~uoKgU+A#GEqqO$O#WYg_d0%-uzq?=-6aufbN
zWZyAG91BMPwv-8K<_3|iSAW8F;Ejf{NB-xBlB_l>s~o4<5pk{oe3ppeNAy|ZjfMep
zH+&?Nq$Lac8wSkX@WY`xEdksyYn|cyaHx(P{I3=r{_){Z9WmOT&eHpYMcdO^r0wY}
z>*2OLmXqo6vWye;2UdwSj+PTS^RjJwI?MjNVd-~fp)4>5rQexl<9LWTRq(d9
zr8xhY!^nRIC=1L%{xd*h@7n<22ucNIfjO4F8*CXU3(RqJLOW`CbttL;RN*KAk6TGM
zKOhl&RFO@$TZg=!n=K!_NY6(VS(0y4S)|8zD6^FXr+p!cnXJjBf|gf@%-ygwp?Ak71j?(dfT(Qo%<*WT6aYICUgn*uj5_K7+`u~Q-C=$%q?-N-3BW4H
zWR@TrL|X!-(~v(40DHRGHmf(GX8{X-@`)y+Dzq;|G5yc$d-}fr`N-4vDa-Xdec!)3
z^7Q=&*WS?*z~}G$isAd<+B@W6yPnStKe+Y|G4}4+uK!y!1;yoPjeL5_%c8cMzME~U
z;1gQwx}dS8&8ARLT#mBRB9xUD*^Y_0#+Eh|6qkG2N;BYwl9GH+jyhAZtt>090jL-NYN=J#vu#GK0b3H)z`Vegg_h9AJD_q&NxqHe
z+f*J8$3p)?8aX@WLp4%_?09OztWOLoQf>t_~YAhdx>K&
zSnmHPN0wO5O@1h8oo8P>Y)kQL19br4=*SHZC9SjUb|<+zWp#xCu#Q!tr+U=xwShW3
zl(Y^Z)OlD*0*J;jMW{0c$WyoBv*3DZ#wycMhC0ok{iLJao#ZT?$c};^pcR0q?)=1(
zO*B#I-0WCc`$816d+=x4obMj|nYkMV;#X?PLhpqaBhMTD@)bSL8-Dj64TJph6*Btb
zDINY1yYc}TBe_4+e{Bc=(>trJ*Ve)F;CaeXndd}0BAM^swh+}MJ#M}ayo{Qp$E|l8
z$BkfmXSJt}Os~S9;Jh-b@FzHoEBpzTu?#9YB0YNs23|JbX{3=X001Hpdv(tD<)tSq
z0jtEP4P0qh&@|U@b~3#Re}Z!}VxybhS&fRkTIumhyA#@4~}=5Dwl{!uMi*mB{&&E4=OY10zG6r;%S-6Ur$6TonV)FX0ndqSWzfG6sZ6fP!mgk9NB+_2
zn<;w!(doDS=5BcQT|IY76TfU2zO(NV(sJymxAr99?7M^*wM|EEzi-i06lDSc^2$TV
z$uG0olVJbmhF@$N)QXw;E1b3D^neg^4JEBLH?ryN!sQ^?BtXR1{?@aeRQn
zb%3oQHK1HhepysC=Ajh8XNj0w8O^PttE17}4Re>5Y01LT-HqmMc=(u}-Eh&@4a0Z%
z7$H@f{#WbpF+z-t9ePGj;aCTbF28|_KpCk^I3(}NGPebajLTFaRAW15XB6&*LrJB
z5{BAq&E4?c!Wu1E_+xvG`ES3wo=tQdoclNKKbH2TPhEbO`0{j1{Pa(Ej`5@RM*wED
z1ICdv&E$XIY!~*Ef9I&wM^aew1*p@pmKZ+I{3hX0yhC-`_ex;oH|CNIpZhN<$^vl&
zA#yHoZ121=*ZP8q|C{#`wjB7{I)jB`Pxc5k+{VC^y2J+^GIBhfW(&*Ecx!3832H<7k(80;Q!pm
z(!qTC=Q|KSnhpS%(ba}EesgYZA3TdS{zPmCux9=}5$-*#nSamNLX`aYY%7rD$H)Gm
z_3t11G63MpU$0}u558t|I5Nc#V9-o`A%GA57Wg8g@cF;sKT_`o09=b*XukWthV_B9
zmh4N*3*v02`Kd|TFM>!9t_1`qz}W+)PU4aTTYxwni}2A*%&0aUV%7y*n6@4B0BnE5
z`CG`1XPN;3WebB?A6N@tdT=e$@`3<>zRqT(j40&02#^Im=ZabJvZBWzhlZ&
z0Km7C&0l#kivIPKa37#)9-S@z4T9KnryU$E1pvff82bk8G|8$yb6PB7F8uz0!?Blh
z8Q8gTA%5&zwBc!D{a^jzcjj)G`TYu87Cwr*-_zOj;fvB^A6=ed$#dTSJ^qC2R{L|G
zjOlguziRwHWRS#5j^)tn0000EWmrjOO-%qQ00008000000002eQ
Date: Wed, 16 Oct 2024 14:55:23 -0400
Subject: [PATCH 2/7] [Refactor] Add friendship related constants (#4657)
* Add constants for friendship
* Absolute path in battle-scene.ts
* Address nits
* Apply negative to constant
---
src/battle-scene.ts | 3 ++-
src/data/balance/starters.ts | 6 ++++++
src/field/pokemon.ts | 4 ++--
src/modifier/modifier.ts | 3 ++-
src/phases/faint-phase.ts | 3 ++-
5 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/battle-scene.ts b/src/battle-scene.ts
index 6a70688dbf1..ffba8e98d34 100644
--- a/src/battle-scene.ts
+++ b/src/battle-scene.ts
@@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase";
import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { ExpGainsSpeed } from "#enums/exp-gains-speed";
+import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@@ -3054,7 +3055,7 @@ export default class BattleScene extends SceneBase {
const pId = partyMember.id;
const participated = participantIds.has(pId);
if (participated && pokemonDefeated) {
- partyMember.addFriendship(2);
+ partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) {
machoBraceModifier.stackCount++;
diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts
index 0fadd992309..bf3a1f7ad56 100644
--- a/src/data/balance/starters.ts
+++ b/src/data/balance/starters.ts
@@ -2,6 +2,12 @@ import { Species } from "#enums/species";
export const POKERUS_STARTER_COUNT = 5;
+// #region Friendship constants
+export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
+export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
+export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
+export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
+
/**
* Function to get the cumulative friendship threshold at which a candy is earned
* @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts}
diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts
index f3e9c66ed15..9aaadf29657 100644
--- a/src/field/pokemon.ts
+++ b/src/field/pokemon.ts
@@ -5,7 +5,7 @@ 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, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
-import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
+import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils";
@@ -4082,7 +4082,7 @@ export class PlayerPokemon extends Pokemon {
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
].filter(d => !!d);
const amount = new Utils.IntegerHolder(friendship);
- const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1)));
+ const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1)));
if (amount.value > 0) {
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts
index 689b81be82f..35d1a304461 100644
--- a/src/modifier/modifier.ts
+++ b/src/modifier/modifier.ts
@@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next";
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color";
+import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
export type ModifierPredicate = (modifier: Modifier) => boolean;
@@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier {
playerPokemon.levelExp = 0;
}
- playerPokemon.addFriendship(5);
+ playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY);
playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level));
diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts
index 60dbbbfea0f..95105337f60 100644
--- a/src/phases/faint-phase.ts
+++ b/src/phases/faint-phase.ts
@@ -20,6 +20,7 @@ import { VictoryPhase } from "./victory-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { SwitchType } from "#enums/switch-type";
import { isNullOrUndefined } from "#app/utils";
+import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
export class FaintPhase extends PokemonPhase {
/**
@@ -147,7 +148,7 @@ export class FaintPhase extends PokemonPhase {
pokemon.faintCry(() => {
if (pokemon instanceof PlayerPokemon) {
- pokemon.addFriendship(-10);
+ pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT);
}
pokemon.hideInfo();
this.scene.playSound("se/faint");
From d92d63e81fc80dd08f7631af22316a61cdabe776 Mon Sep 17 00:00:00 2001
From: NightKev <34855794+DayKev@users.noreply.github.com>
Date: Wed, 16 Oct 2024 13:10:19 -0700
Subject: [PATCH 3/7] [Misc] Restore info comment that was accidentally removed
(#4674)
---
.../mystery-encounters/encounters/safari-zone-encounter.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts
index 01dc29f9821..0ee3c57b0a2 100644
--- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts
+++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts
@@ -304,6 +304,11 @@ async function summonSafariPokemon(scene: BattleScene) {
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
+ // TODO: If we await showEncounterText here, then the text will display without
+ // the wild Pokemon on screen, but if we don't await it, then the text never
+ // 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);
if (ivScannerModifier) {
scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)));
From afebecd43c5bced779736dd51ec5369be8810afb Mon Sep 17 00:00:00 2001
From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com>
Date: Wed, 16 Oct 2024 17:16:10 -0500
Subject: [PATCH 4/7] [P2 Bug] Fix pool entry for Jynx not using baby species
(#4675)
---
.../encounters/the-expert-pokemon-breeder-encounter.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 7bba603728b..945e7ee188d 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
@@ -61,7 +61,7 @@ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
[ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ],
- [ Species.JYNX ],
+ [ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ],
[ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ],
From 85b8ca6467bc0a79e0eabcbb81ef7c677b327241 Mon Sep 17 00:00:00 2001
From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com>
Date: Wed, 16 Oct 2024 19:48:28 -0400
Subject: [PATCH 5/7] [Dev] Bump Game Version, Overhaul Version Migration
(#4388)
* Bump Version, Remove "Outdated" Message
* Fix `src/ui/ui.ts`
* Fix `src/system/game-data.ts`
* Clean Up & Organize Version Migration
* Rename Methods & Session Migration Adjustment
* Collapse Version Migrators to Single File as Arrays
* Address NITs
* Restructure Migration Initialization
* Fix Spacing, Increment to v1.6.0
* Revert Back to v1.1.0
* Add `gameVersion` to Mocked Game
* Add More Documentation
---------
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
---
package-lock.json | 4 +-
package.json | 4 +-
src/phases/outdated-phase.ts | 13 --
src/system/game-data.ts | 19 +-
src/system/version-converter.ts | 157 ---------------
.../version_migration/version_converter.ts | 182 ++++++++++++++++++
.../version_migration/versions/v1_0_4.ts | 135 +++++++++++++
src/test/utils/gameWrapper.ts | 2 +
src/ui/outdated-modal-ui-handler.ts | 47 -----
src/ui/ui.ts | 4 -
10 files changed, 329 insertions(+), 238 deletions(-)
delete mode 100644 src/phases/outdated-phase.ts
delete mode 100644 src/system/version-converter.ts
create mode 100644 src/system/version_migration/version_converter.ts
create mode 100644 src/system/version_migration/versions/v1_0_4.ts
delete mode 100644 src/ui/outdated-modal-ui-handler.ts
diff --git a/package-lock.json b/package-lock.json
index ee2708b38f5..be946306471 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pokemon-rogue-battle",
- "version": "1.0.4",
+ "version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pokemon-rogue-battle",
- "version": "1.0.4",
+ "version": "1.1.0",
"hasInstallScript": true,
"dependencies": {
"@material/material-color-utilities": "^0.2.7",
diff --git a/package.json b/package.json
index d8d7f5e8db8..a31296d1644 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "pokemon-rogue-battle",
"private": true,
- "version": "1.0.4",
+ "version": "1.1.0",
"type": "module",
"scripts": {
"start": "vite",
@@ -20,7 +20,7 @@
"depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
- "postinstall": "npx lefthook install && npx lefthook run post-merge"
+ "postinstall": "npx lefthook install && npx lefthook run post-merge"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
diff --git a/src/phases/outdated-phase.ts b/src/phases/outdated-phase.ts
deleted file mode 100644
index 4baf16d2f56..00000000000
--- a/src/phases/outdated-phase.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import BattleScene from "#app/battle-scene";
-import { Phase } from "#app/phase";
-import { Mode } from "#app/ui/ui";
-
-export class OutdatedPhase extends Phase {
- constructor(scene: BattleScene) {
- super(scene);
- }
-
- start(): void {
- this.scene.ui.setMode(Mode.OUTDATED);
- }
-}
diff --git a/src/system/game-data.ts b/src/system/game-data.ts
index b162962fac6..41746957d49 100644
--- a/src/system/game-data.ts
+++ b/src/system/game-data.ts
@@ -43,10 +43,9 @@ import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
import { WeatherType } from "#enums/weather-type";
import { TerrainType } from "#app/data/terrain";
-import { OutdatedPhase } from "#app/phases/outdated-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
-import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter";
+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 { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
@@ -403,10 +402,7 @@ export class GameData {
.then(error => {
this.scene.ui.savingIcon.hide();
if (error) {
- if (error.startsWith("client version out of date")) {
- this.scene.clearPhaseQueue();
- this.scene.unshiftPhase(new OutdatedPhase(this.scene));
- } else if (error.startsWith("session out of date")) {
+ if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
@@ -482,7 +478,7 @@ export class GameData {
localStorage.setItem(lsItemKey, "");
}
- applySystemDataPatches(systemData);
+ applySystemVersionMigration(systemData);
this.trainerId = systemData.trainerId;
this.secretId = systemData.secretId;
@@ -857,7 +853,7 @@ export class GameData {
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
- applySettingsDataPatches(settings);
+ applySettingsVersionMigration(settings);
for (const setting of Object.keys(settings)) {
setSetting(this.scene, setting, settings[setting]);
@@ -1313,7 +1309,7 @@ export class GameData {
return v;
}) as SessionSaveData;
- applySessionDataPatches(sessionData);
+ applySessionVersionMigration(sessionData);
return sessionData;
}
@@ -1354,10 +1350,7 @@ export class GameData {
this.scene.ui.savingIcon.hide();
}
if (error) {
- if (error.startsWith("client version out of date")) {
- this.scene.clearPhaseQueue();
- this.scene.unshiftPhase(new OutdatedPhase(this.scene));
- } else if (error.startsWith("session out of date")) {
+ if (error.startsWith("session out of date")) {
this.scene.clearPhaseQueue();
this.scene.unshiftPhase(new ReloadSessionPhase(this.scene));
}
diff --git a/src/system/version-converter.ts b/src/system/version-converter.ts
deleted file mode 100644
index 3a4416a975e..00000000000
--- a/src/system/version-converter.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import { allSpecies } from "#app/data/pokemon-species";
-import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data";
-import { SettingKeys } from "./settings/settings";
-
-const LATEST_VERSION = "1.0.5";
-
-export function applySessionDataPatches(data: SessionSaveData) {
- const curVersion = data.gameVersion;
-
- // Always sanitize money as a safeguard
- data.money = Math.floor(data.money);
-
- if (curVersion !== LATEST_VERSION) {
- switch (curVersion) {
- case "1.0.0":
- case "1.0.1":
- case "1.0.2":
- case "1.0.3":
- case "1.0.4":
- // --- PATCHES ---
-
- // Fix Battle Items, Vitamins, and Lures
- data.modifiers.forEach((m) => {
- if (m.className === "PokemonBaseStatModifier") {
- m.className = "BaseStatModifier";
- } else if (m.className === "PokemonResetNegativeStatStageModifier") {
- m.className = "ResetNegativeStatStageModifier";
- } else if (m.className === "TempBattleStatBoosterModifier") {
- // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
- if (m.typeId !== "DIRE_HIT") {
- m.className = "TempStatStageBoosterModifier";
- m.typeId = "TEMP_STAT_STAGE_BOOSTER";
-
- // Migration from TempBattleStat to Stat
- const newStat = m.typePregenArgs[0] + 1;
- m.typePregenArgs[0] = newStat;
-
- // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
- m.args = [ newStat, 5, m.args[1] ];
- } else {
- m.className = "TempCritBoosterModifier";
- m.typePregenArgs = [];
-
- // From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
- m.args = [ 5, m.args[1] ];
- }
-
- } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
- let maxBattles: number;
- switch (m.typeId) {
- case "MAX_LURE":
- maxBattles = 30;
- break;
- case "SUPER_LURE":
- maxBattles = 15;
- break;
- default:
- maxBattles = 10;
- break;
- }
-
- // From [ battlesLeft ] to [ maxBattles, battleCount ]
- m.args = [ maxBattles, m.args[0] ];
- }
- });
-
- data.enemyModifiers.forEach((m) => {
- if (m.className === "PokemonBaseStatModifier") {
- m.className = "BaseStatModifier";
- } else if (m.className === "PokemonResetNegativeStatStageModifier") {
- m.className = "ResetNegativeStatStageModifier";
- }
- });
- }
-
- data.gameVersion = LATEST_VERSION;
- }
-}
-
-export function applySystemDataPatches(data: SystemSaveData) {
- const curVersion = data.gameVersion;
- if (curVersion !== LATEST_VERSION) {
- switch (curVersion) {
- case "1.0.0":
- case "1.0.1":
- case "1.0.2":
- case "1.0.3":
- case "1.0.4":
- // --- LEGACY PATCHES ---
- if (data.starterData && data.dexData) {
- // Migrate ability starter data if empty for caught species
- Object.keys(data.starterData).forEach(sd => {
- if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
- data.starterData[sd].abilityAttr = 1;
- }
- });
- }
-
- // Fix Legendary Stats
- if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
- data.gameStats.subLegendaryPokemonSeen = 0;
- data.gameStats.subLegendaryPokemonCaught = 0;
- data.gameStats.subLegendaryPokemonHatched = 0;
- allSpecies.filter(s => s.subLegendary).forEach(s => {
- const dexEntry = data.dexData[s.speciesId];
- data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
- data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
- data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
- data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
- data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
- data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
- });
- data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
- data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
- data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
- }
-
- // --- PATCHES ---
-
- // Fix Starter Data
- if (data.starterData && data.dexData) {
- for (const starterId of defaultStarterSpecies) {
- if (data.starterData[starterId]?.abilityAttr) {
- data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
- }
- if (data.dexData[starterId]?.caughtAttr) {
- data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
- }
- }
- }
- }
-
- data.gameVersion = LATEST_VERSION;
- }
-}
-
-export function applySettingsDataPatches(settings: Object) {
- const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0";
- if (curVersion !== LATEST_VERSION) {
- switch (curVersion) {
- case "1.0.0":
- case "1.0.1":
- case "1.0.2":
- case "1.0.3":
- case "1.0.4":
- // --- PATCHES ---
-
- // Fix Reward Cursor Target
- if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
- settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
- delete settings["REROLL_TARGET"];
- localStorage.setItem("settings", JSON.stringify(settings));
- }
- }
- // Note that the current game version will be written at `saveSettings`
- }
-}
diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts
new file mode 100644
index 00000000000..f93e09b7a90
--- /dev/null
+++ b/src/system/version_migration/version_converter.ts
@@ -0,0 +1,182 @@
+import { SessionSaveData, SystemSaveData } from "../game-data";
+import { version } from "../../../package.json";
+
+// --- v1.0.4 (and below) PATCHES --- //
+import * as v1_0_4 from "./versions/v1_0_4";
+
+const LATEST_VERSION = version.split(".").map(value => parseInt(value));
+
+/**
+ * Converts incoming {@linkcode SystemSaveData} that has a version below the
+ * current version number listed in `package.json`.
+ *
+ * Note that no transforms act on the {@linkcode data} if its version matches
+ * the current version or if there are no migrations made between its version up
+ * to the current version.
+ * @param data {@linkcode SystemSaveData}
+ * @see {@link SystemVersionConverter}
+ */
+export function applySystemVersionMigration(data: SystemSaveData) {
+ const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
+
+ if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
+ const converter = new SystemVersionConverter();
+ converter.applyStaticPreprocessors(data);
+ converter.applyMigration(data, curVersion);
+ }
+}
+
+/**
+ * Converts incoming {@linkcode SessionSavaData} that has a version below the
+ * current version number listed in `package.json`.
+ *
+ * Note that no transforms act on the {@linkcode data} if its version matches
+ * the current version or if there are no migrations made between its version up
+ * to the current version.
+ * @param data {@linkcode SessionSaveData}
+ * @see {@link SessionVersionConverter}
+ */
+export function applySessionVersionMigration(data: SessionSaveData) {
+ const curVersion = data.gameVersion.split(".").map(value => parseInt(value));
+
+ if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
+ const converter = new SessionVersionConverter();
+ converter.applyStaticPreprocessors(data);
+ converter.applyMigration(data, curVersion);
+ }
+}
+
+/**
+ * Converts incoming settings data that has a version below the
+ * current version number listed in `package.json`.
+ *
+ * Note that no transforms act on the {@linkcode data} if its version matches
+ * the current version or if there are no migrations made between its version up
+ * to the current version.
+ * @param data Settings data object
+ * @see {@link SettingsVersionConverter}
+ */
+export function applySettingsVersionMigration(data: Object) {
+ const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0";
+ const curVersion = gameVersion.split(".").map(value => parseInt(value));
+
+ if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) {
+ const converter = new SettingsVersionConverter();
+ converter.applyStaticPreprocessors(data);
+ converter.applyMigration(data, curVersion);
+ }
+}
+
+/**
+ * Abstract class encapsulating the logic for migrating data from a given version up to
+ * the current version listed in `package.json`.
+ *
+ * Note that, for any version converter, the corresponding `applyMigration`
+ * function would only need to be changed once when the first migration for a
+ * given version is introduced. Similarly, a version file (within the `versions`
+ * folder) would only need to be created for a version once with the appropriate
+ * array nomenclature.
+ */
+abstract class VersionConverter {
+ /**
+ * Iterates through an array of designated migration functions that are each
+ * called one by one to transform the data.
+ * @param data The data to be operated on
+ * @param migrationArr An array of functions that will transform the incoming data
+ */
+ callMigrators(data: any, migrationArr: readonly any[]) {
+ for (const migrate of migrationArr) {
+ migrate(data);
+ }
+ }
+
+ /**
+ * Applies any version-agnostic data sanitation as defined within the function
+ * body.
+ * @param data The data to be operated on
+ */
+ applyStaticPreprocessors(_data: any): void {
+ }
+
+ /**
+ * Uses the current version the incoming data to determine the starting point
+ * of the migration which will cascade up to the latest version, calling the
+ * necessary migration functions in the process.
+ * @param data The data to be operated on
+ * @param curVersion [0] Current major version
+ * [1] Current minor version
+ * [2] Current patch version
+ */
+ abstract applyMigration(data: any, curVersion: number[]): void;
+}
+
+/**
+ * Class encapsulating the logic for migrating {@linkcode SessionSaveData} from
+ * a given version up to the current version listed in `package.json`.
+ * @extends VersionConverter
+ */
+class SessionVersionConverter extends VersionConverter {
+ override applyStaticPreprocessors(data: SessionSaveData): void {
+ // Always sanitize money as a safeguard
+ data.money = Math.floor(data.money);
+ }
+
+ override applyMigration(data: SessionSaveData, curVersion: number[]): void {
+ const [ curMajor, curMinor, curPatch ] = curVersion;
+
+ if (curMajor === 1) {
+ if (curMinor === 0) {
+ if (curPatch <= 4) {
+ console.log("Applying v1.0.4 session data migration!");
+ this.callMigrators(data, v1_0_4.sessionMigrators);
+ }
+ }
+ }
+
+ console.log(`Session data successfully migrated to v${version}!`);
+ }
+}
+
+/**
+ * Class encapsulating the logic for migrating {@linkcode SystemSaveData} from
+ * a given version up to the current version listed in `package.json`.
+ * @extends VersionConverter
+ */
+class SystemVersionConverter extends VersionConverter {
+ override applyMigration(data: SystemSaveData, curVersion: number[]): void {
+ const [ curMajor, curMinor, curPatch ] = curVersion;
+
+ if (curMajor === 1) {
+ if (curMinor === 0) {
+ if (curPatch <= 4) {
+ console.log("Applying v1.0.4 system data migraton!");
+ this.callMigrators(data, v1_0_4.systemMigrators);
+ }
+ }
+ }
+
+ console.log(`System data successfully migrated to v${version}!`);
+ }
+}
+
+/**
+ * Class encapsulating the logic for migrating settings data from
+ * a given version up to the current version listed in `package.json`.
+ * @extends VersionConverter
+ */
+class SettingsVersionConverter extends VersionConverter {
+ override applyMigration(data: Object, curVersion: number[]): void {
+ const [ curMajor, curMinor, curPatch ] = curVersion;
+
+ if (curMajor === 1) {
+ if (curMinor === 0) {
+ if (curPatch <= 4) {
+ console.log("Applying v1.0.4 settings data migraton!");
+ this.callMigrators(data, v1_0_4.settingsMigrators);
+ }
+ }
+ }
+
+ console.log(`System data successfully migrated to v${version}!`);
+ }
+}
diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts
new file mode 100644
index 00000000000..c20e2a281e7
--- /dev/null
+++ b/src/system/version_migration/versions/v1_0_4.ts
@@ -0,0 +1,135 @@
+import { SettingKeys } from "../../settings/settings";
+import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data";
+import { allSpecies } from "../../../data/pokemon-species";
+
+export const systemMigrators = [
+ /**
+ * Migrate ability starter data if empty for caught species.
+ * @param data {@linkcode SystemSaveData}
+ */
+ function migrateAbilityData(data: SystemSaveData) {
+ if (data.starterData && data.dexData) {
+ Object.keys(data.starterData).forEach(sd => {
+ if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
+ data.starterData[sd].abilityAttr = 1;
+ }
+ });
+ }
+ },
+
+ /**
+ * Populate legendary Pokémon statistics if they are missing.
+ * @param data {@linkcode SystemSaveData}
+ */
+ function fixLegendaryStats(data: SystemSaveData) {
+ if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) {
+ data.gameStats.subLegendaryPokemonSeen = 0;
+ data.gameStats.subLegendaryPokemonCaught = 0;
+ data.gameStats.subLegendaryPokemonHatched = 0;
+ allSpecies.filter(s => s.subLegendary).forEach(s => {
+ const dexEntry = data.dexData[s.speciesId];
+ data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount;
+ data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0);
+ data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount;
+ data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0);
+ data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount;
+ data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0);
+ });
+ data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught);
+ data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught);
+ data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught);
+ }
+ },
+
+ /**
+ * Unlock all starters' first ability and female gender option.
+ * @param data {@linkcode SystemSaveData}
+ */
+ function fixStarterData(data: SystemSaveData) {
+ for (const starterId of defaultStarterSpecies) {
+ if (data.starterData[starterId]?.abilityAttr) {
+ data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
+ }
+ if (data.dexData[starterId]?.caughtAttr) {
+ data.dexData[starterId].caughtAttr |= DexAttr.FEMALE;
+ }
+ }
+ }
+] as const;
+
+export const settingsMigrators = [
+ /**
+ * Migrate from "REROLL_TARGET" property to {@linkcode
+ * SettingKeys.Shop_Cursor_Target}.
+ * @param data the `settings` object
+ */
+ function fixRerollTarget(data: Object) {
+ if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
+ data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"];
+ delete data["REROLL_TARGET"];
+ localStorage.setItem("settings", JSON.stringify(data));
+ }
+ }
+] as const;
+
+export const sessionMigrators = [
+ /**
+ * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and
+ * other miscellaneous modifiers (vitamins, White Herb) to any new class
+ * names and/or change in reload arguments.
+ * @param data {@linkcode SessionSaveData}
+ */
+ function migrateModifiers(data: SessionSaveData) {
+ data.modifiers.forEach((m) => {
+ if (m.className === "PokemonBaseStatModifier") {
+ m.className = "BaseStatModifier";
+ } else if (m.className === "PokemonResetNegativeStatStageModifier") {
+ m.className = "ResetNegativeStatStageModifier";
+ } else if (m.className === "TempBattleStatBoosterModifier") {
+ const maxBattles = 5;
+ // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
+ if (m.typeId !== "DIRE_HIT") {
+ m.className = "TempStatStageBoosterModifier";
+ m.typeId = "TEMP_STAT_STAGE_BOOSTER";
+
+ // Migration from TempBattleStat to Stat
+ const newStat = m.typePregenArgs[0] + 1;
+ m.typePregenArgs[0] = newStat;
+
+ // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
+ m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ];
+ } else {
+ m.className = "TempCritBoosterModifier";
+ m.typePregenArgs = [];
+
+ // From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
+ m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ];
+ }
+ } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
+ let maxBattles: number;
+ switch (m.typeId) {
+ case "MAX_LURE":
+ maxBattles = 30;
+ break;
+ case "SUPER_LURE":
+ maxBattles = 15;
+ break;
+ default:
+ maxBattles = 10;
+ break;
+ }
+
+ // From [ battlesLeft ] to [ maxBattles, battleCount ]
+ m.args = [ maxBattles, Math.min(m.args[0], maxBattles) ];
+ }
+ });
+
+ data.enemyModifiers.forEach((m) => {
+ if (m.className === "PokemonBaseStatModifier") {
+ m.className = "BaseStatModifier";
+ } else if (m.className === "PokemonResetNegativeStatStageModifier") {
+ m.className = "ResetNegativeStatStageModifier";
+ }
+ });
+ }
+] as const;
diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts
index 0ef5c4d4611..48c0007118b 100644
--- a/src/test/utils/gameWrapper.ts
+++ b/src/test/utils/gameWrapper.ts
@@ -23,6 +23,7 @@ import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin;
import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin;
import EventEmitter = Phaser.Events.EventEmitter;
import UpdateList = Phaser.GameObjects.UpdateList;
+import { version } from "../../../package.json";
Object.defineProperty(window, "localStorage", {
value: mockLocalStorage(),
@@ -101,6 +102,7 @@ export default class GameWrapper {
injectMandatory() {
this.game.config = {
seed: ["test"],
+ gameVersion: version
};
this.scene.game = this.game;
this.game.renderer = {
diff --git a/src/ui/outdated-modal-ui-handler.ts b/src/ui/outdated-modal-ui-handler.ts
deleted file mode 100644
index fc4b93f9b8a..00000000000
--- a/src/ui/outdated-modal-ui-handler.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import BattleScene from "../battle-scene";
-import { ModalConfig, ModalUiHandler } from "./modal-ui-handler";
-import { addTextObject, TextStyle } from "./text";
-import { Mode } from "./ui";
-
-export default class OutdatedModalUiHandler extends ModalUiHandler {
- constructor(scene: BattleScene, mode: Mode | null = null) {
- super(scene, mode);
- }
-
- getModalTitle(): string {
- return "";
- }
-
- getWidth(): number {
- return 160;
- }
-
- getHeight(): number {
- return 64;
- }
-
- getMargin(): [number, number, number, number] {
- return [ 0, 0, 48, 0 ];
- }
-
- getButtonLabels(): string[] {
- return [ ];
- }
-
- setup(): void {
- super.setup();
-
- const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.", TextStyle.WINDOW, { fontSize: "48px", align: "center" });
- label.setOrigin(0.5, 0.5);
-
- this.modalContainer.add(label);
- }
-
- show(args: any[]): boolean {
- const config: ModalConfig = {
- buttonActions: []
- };
-
- return super.show([ config ]);
- }
-}
diff --git a/src/ui/ui.ts b/src/ui/ui.ts
index 373930c5d84..63cd48ab1cd 100644
--- a/src/ui/ui.ts
+++ b/src/ui/ui.ts
@@ -34,7 +34,6 @@ import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler";
import TitleUiHandler from "./title-ui-handler";
import SavingIconHandler from "./saving-icon-handler";
import UnavailableModalUiHandler from "./unavailable-modal-ui-handler";
-import OutdatedModalUiHandler from "./outdated-modal-ui-handler";
import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler";
import { Button } from "#enums/buttons";
import i18next from "i18next";
@@ -90,7 +89,6 @@ export enum Mode {
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
- OUTDATED,
CHALLENGE_SELECT,
RENAME_POKEMON,
RUN_HISTORY,
@@ -134,7 +132,6 @@ const noTransitionModes = [
Mode.LOADING,
Mode.SESSION_RELOAD,
Mode.UNAVAILABLE,
- Mode.OUTDATED,
Mode.RENAME_POKEMON,
Mode.TEST_DIALOGUE,
Mode.AUTO_COMPLETE,
@@ -200,7 +197,6 @@ export default class UI extends Phaser.GameObjects.Container {
new LoadingModalUiHandler(scene),
new SessionReloadModalUiHandler(scene),
new UnavailableModalUiHandler(scene),
- new OutdatedModalUiHandler(scene),
new GameChallengesUiHandler(scene),
new RenameFormUiHandler(scene),
new RunHistoryUiHandler(scene),
From 2f212f52eb8808bd60c4271835ba38d89158501a Mon Sep 17 00:00:00 2001
From: Mumble <171087428+frutescens@users.noreply.github.com>
Date: Wed, 16 Oct 2024 23:31:30 -0700
Subject: [PATCH 6/7] fixed effectChanceOVerrride location + removed ability
tags (#4677)
Co-authored-by: frutescens
---
src/data/ability.ts | 6 ++----
src/data/move.ts | 4 ++--
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/data/ability.ts b/src/data/ability.ts
index 6c4dededa04..5d2ccfc9d36 100644
--- a/src/data/ability.ts
+++ b/src/data/ability.ts
@@ -4913,8 +4913,7 @@ export function initAbilities() {
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1)
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
- .attr(IgnoreMoveEffectsAbAttr)
- .edgeCase(), // Does not work with secret power (unimplemented)
+ .attr(IgnoreMoveEffectsAbAttr),
new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr)
@@ -4958,8 +4957,7 @@ export function initAbilities() {
.attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1)
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
- .attr(MoveEffectChanceMultiplierAbAttr, 2)
- .edgeCase(), // does not work with secret power (unimplemented)
+ .attr(MoveEffectChanceMultiplierAbAttr, 2),
new Ability(Abilities.SWIFT_SWIM, 3)
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
diff --git a/src/data/move.ts b/src/data/move.ts
index 448008b733c..57307b49061 100644
--- a/src/data/move.ts
+++ b/src/data/move.ts
@@ -2891,8 +2891,6 @@ export class SecretPowerAttr extends MoveEffectAttr {
this.effectChanceOverride = move.chance;
const moveChance = this.getMoveChance(user, target, move, this.selfTarget);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
- // effectChanceOverride used in the application of the actual secondary effect
- this.effectChanceOverride = 100;
return true;
} else {
return false;
@@ -2915,6 +2913,8 @@ export class SecretPowerAttr extends MoveEffectAttr {
const biome = user.scene.arena.biomeType;
secondaryEffect = this.determineBiomeEffect(biome);
}
+ // effectChanceOverride used in the application of the actual secondary effect
+ secondaryEffect.effectChanceOverride = 100;
return secondaryEffect.apply(user, target, move, []);
}
From c5b3220b86579c40bac954e4d481df6787c1928b Mon Sep 17 00:00:00 2001
From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com>
Date: Thu, 17 Oct 2024 19:46:51 +0200
Subject: [PATCH 7/7] [Beta P3][UI] Fix item/cursor placement in reward screen
(#4678)
---
src/ui/modifier-select-ui-handler.ts | 31 ++++++++++++++++------------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts
index 0bae56c03b4..1948d75ac4c 100644
--- a/src/ui/modifier-select-ui-handler.ts
+++ b/src/ui/modifier-select-ui-handler.ts
@@ -17,6 +17,9 @@ import { IntegerHolder } from "./../utils";
import Phaser from "phaser";
export const SHOP_OPTIONS_ROW_LIMIT = 7;
+const SINGLE_SHOP_ROW_YOFFSET = 12;
+const DOUBLE_SHOP_ROW_YOFFSET = 24;
+const OPTION_BUTTON_YPOSITION = -62;
export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private modifierContainer: Phaser.GameObjects.Container;
@@ -68,7 +71,7 @@ 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, -64);
+ this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION);
this.transferButtonContainer.setName("transfer-btn");
this.transferButtonContainer.setVisible(false);
ui.add(this.transferButtonContainer);
@@ -78,7 +81,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
transferButtonText.setOrigin(1, 0);
this.transferButtonContainer.add(transferButtonText);
- this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, -64);
+ this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION);
this.checkButtonContainer.setName("use-btn");
this.checkButtonContainer.setVisible(false);
ui.add(this.checkButtonContainer);
@@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
checkButtonText.setOrigin(1, 0);
this.checkButtonContainer.add(checkButtonText);
- this.rerollButtonContainer = this.scene.add.container(16, -64);
+ this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
this.rerollButtonContainer.setName("reroll-brn");
this.rerollButtonContainer.setVisible(false);
ui.add(this.rerollButtonContainer);
@@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1);
this.rerollButtonContainer.add(this.rerollCostText);
- this.lockRarityButtonContainer = this.scene.add.container(16, -64);
+ this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION);
this.lockRarityButtonContainer.setVisible(false);
ui.add(this.lockRarityButtonContainer);
@@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
const shopTypeOptions = !removeHealShop
? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value)
: [];
- const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24;
+ 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);
@@ -212,7 +215,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
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) - (40 - (28 * row - 1))), shopTypeOptions[m]);
+ 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]);
option.setScale(0.375);
this.scene.add.existing(option);
this.modifierContainer.add(option);
@@ -456,16 +459,18 @@ 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 ? 6 : 22));
+ 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));
ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription"));
return ret;
}
const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2);
if (this.rowCursor < 2) {
- this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22));
+ // 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));
} else {
- this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1))));
+ // 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))));
}
const type = options[this.cursor].modifierTypeOption.type;
@@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.moveInfoOverlay.show(allMoves[type.moveId]);
}
} else if (cursor === 0) {
- this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60);
+ 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, -60);
+ this.cursorObj.setPosition((this.scene.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, -60);
+ this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc"));
} else {
- this.cursorObj.setPosition(6, -60);
+ this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4);
ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc"));
}