From 192b409a0d2b66d14109ce8d3a3ca1d7f9a329a2 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 25 Jan 2025 10:49:42 +0100 Subject: [PATCH 01/31] chore: update gradlew and dependencies for MC 1.21.4 --- build.gradle.kts | 8 ++++---- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 +++- gradlew.bat | 2 ++ 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2967995..8c35cd2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { `java-library` - id("io.papermc.paperweight.userdev") version "1.7.1" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.14" } repositories { @@ -21,9 +21,9 @@ repositories { dependencies { compileOnly("com.mojang:brigadier:1.0.18") - implementation("commons-io:commons-io:2.6") - paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") - implementation("net.kyori:adventure-text-minimessage:4.17.0") + implementation("commons-io:commons-io:2.7") + paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT") + implementation("net.kyori:adventure-text-minimessage:4.18.0") } java { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ zF^}Ea+#Je`Xy6;#D0FPnSrR%Y!QGA~NA^{oWmW8C<3dr{x6wWQ{4+bzemqV5W$i5~ z=J0jXZ>uZb>DT@0Ks?4QJ{`z?8JWl3$y;2pj#$XP*pv$>$g(z43{YH9KmmR6<#sIn zA`#=0#sgycaBQ^&}Xba!|KaZ8~b30v~nLt z9%#gz_*=~KD{3t^X~l>480*}PhKN=??g`RV|4Ud{Gyyl187MJ}r(#e+H$GEdI+p1s zq_25h;fV)$EPK%Dw-(G=f`yHB-_tttsC!?k7*#!|4a>`Ahj8nm?&n>NRs%jkZW^3-0P_yMP5&*6a26{MRj1&TPF zyE#|c)5uUHzMWx=rMKpuPih*V=S;W3MzIZTw2uTbr}8`p2bm+Z6Sa%vvWAWSf4H)p(+ zSQ8;EvUa#wqWV+9vmIio(%7wukK2SwjUS8Yl%Rq%=~PU)2$Tvm6`1!r3H@U#_|bB0 zmlT1PS3wPB(b&^+@YY7Y$n4l3mV3-X0$>z|gZp6O*Lhzn&?Gad2ZCF;+#95-Y?#y+ z?*l@Yf=a4w{Px=o!N|3~_XKfk&G;fN>Ps&dp2FpA~qD=0~=!NOS@B#XAKKkND>Y{4>rqxrViKD7;?>j8`R` z&G)3FN|dfsxnaI^!d1G%=>AbTTxZWo;n-DLrQ!sj=f~VAOe5zhGS(dgx|!ls62fbX zV@<7Ck^!}R=`Swr?(7w1rY6Nmq~sfXJ?TiKJLn=&SQdEt9$@0 zA+h1Wbwbri0s-stc8yVq;mRa6@kEf8^KXUz&jcic!+avDvvJFa>k0ioWug=T3oPw; zyj4it&0@>_*uI@2=^+T7sL1_!^aJW@Xfo8aC#3^WtQC7fET8b9C} z*u^ue6Ojn z7@(eskJ2+cNnH9~VyfIh<-|7!je~vGy*odz(sk-u$~SrYF3glruZ*W`{sqnS+9=;Z zh{D@MSG91%lr&ua8%$sJF%y1I<|e;EdfJykY8#D$Hc_81n5`$7;1N|b0tvvPLzSg& zn7!5x?T*@rQUKcUhTIjV(rw*5oQYlm5DbEO?60#mohHfbR$3_x#+PZoYi@Vd4`#YgKyTd^!4n{fN~WZDY61sAOm6 zl!d^i*a01QxpWM9Pcl?&{RgO}uq%ErOk5WpECvnfEh!*YP&1Sl)uTN4hg??Vqs~i5 zYsfufz3?{TtwuBN=`0~Qg1PlWH#OGG$ zLLWU17$v``)CE1cds_7kj8mJ{-+l8{DS|zAQ&3|qpOY=!J|kXUhXue9|H>4gqk|n) z-i34GmxLFj8asb3D#D&=ya*a5`C<=o?G;Ev^LV%;l#nH#O=7Nh@z1Do>j6Q;I5S2P zhg|AZbC&|c7}uSJt57s2IK#rSWuararn-02dkptTjo*R{c5o(bWV}_k3BBnKcE|6l zrHl&ezUyw^DmaMdDFVn<8ZY=7_{u{uW&*F<7Al6};lD(u;SB=RpIwI)PTyL=e25h* zGi{lRT}snjbMK~IUx|EGonH+w;iC2Ws)x>=5_{5$m?K z5(*1jMn%u0V1Y%m@`YS3kskt~`1p(rA4uk;Cs!w^KL$w>MH)+cP6|XKr4FfHIATJH z!EGAK4N>1yFR`-zW|w%ByRe#=&kA&#WyUldDGpt!wf-8SFWiSi!5QZL+l7*CE?u!NW1T$<1rdLJ9y3u{_zvHaM?#Rm4 zFk}^1!ffcrB|XK3gsO-s=wr*sUe&^$yN|KxrA)uW00Gu60%pw_+DcUjW`oW<35OC8 zq2{j8SgC}W$?10pvFU83(SL$%C?Kctu3*cs0aa%q!fjn1%xD*Jrm!F3HGR9-C{b?- zHp(cL;ezXMpL@0-1v0DMWddSDNZ5h?q50cOZyVi#bU3&PWE=(hpVn|M4_KYG5h9LffKNRsfhr^=SYiKg?#r&HNMi2@cd4aYL9lw(5_IvQJ zcB*DD()hUSAD^PdA0y|QrVnqwgI@pUXZXjHq3lG2OU&7sPOxxU$Y3&ytj6Qb=2#cC z;{d-{k|xI*bu+Vy&N+}{i(+1me!M;nshY_*&ZQLTGG*xNw#{RpI`3^eGfHck+*38NRgiGahkFethtVY=czJs#)VVc{T65rhU#3Vf?X)8f0)X{w!J3J{z|Sq|%?)nA+zo?$>L9@o`Kc|*7sJo4UjIqu0Ir~S5k^vEH};6K?-dZ0h*m%-1L zf!VC%YbM1~sZOG5zu&Sh>R;(md*_)kGHP)<;OA44W?y53PI%{&@MEN}9TOiqu+1a3AGetBr$c)Ao3OX>iGxmA;^^_alwS818r4Pn&uYe^;z6dh z)68T|AN=hjNdGpF7n>y+RTAZc9&opTXf zqWfK_dUv=mW{p_vN>|(cIkd(+Jy}qnK{IW%X*3!l`^H~FbAHwof+vLZ0C2ZXN1$v7 zgN&R9c8IO`fkR{6U%ERq8FN<1DQYbAN0-pH7EfcA{A&nhT!Be>jj>J!bNRw4NF|}! z1c70_#fkk!VQ!q1h2ff@`yDyrI1`np>*e#D4-Z~*!T^8#o*$V~!8bWQaie?P@KGBb z8rXc!YDL!$3ZgZZ%;-%~0Kn<+d+{xJ$stQbtN8GWV?MCJvzPU|(E(1z;rFw{&6vy) z3*@y%7Tx8rH-p$boS>bLyod?OKRE8v`QSBvGfY6f}_{Zo1q85xoyOF16n~yHx2W ziydUoYLkJmzq|n&2S(O!ZmLdP1(o1Jsq88cX)x3V-BK5eF&0e_0G!5?U7&3KN0`mc zH&Lt)q8!d_VgzxyL^(@xrbp2y)Hmr^V48));RSfE=*Ly0uh9!$3dv-vMZr2URf@l5zdwLjGZB zugY>7_fd_vbV*Qv1?H~>Z%RD%nEeFSI$n$$Lrpc6g>i4+XdBB!%zM$Bhrz5Swzyg? z$~I~n@~-wTBY3-T&pr+|gC+OHDoR?I(eLWa{Z#Rsh>lc~%u0!&R|s0pA*w<7QZ}{i z*AFr~0F3y~f$MGh_HDL7J_1?SxKL}fWIk!$G}`^{)xh*dZ5kK>xGL9>V`WZZg_ z)^Vm)EQK`yfh5KiR(vb&aHvhich z_5o+{d~0+4BEBqYJXyXBIEb1UgVDs;a!N2$9WA>CbfrWryqT25)S4E4)QXBd*3jN} z?phkAt`1rKW?xoLzEm!*IfkH|P>BtECVr0l8-IGk_`UjE#IWkUGqvyS+dMrCnFl<7RCgSMX^qn|Ld_4iYRldO zY&cHhv)GDo8nKvKwAbfyLR%t?9gG?R7~PSD#4D-;?F&!kV59O}neYut5AGbKwy-(U zqyBi=&Mgj|VIo>$u!DHM`R7O?W8-idbePuxiJMH``6c_5L-chKd}=rGC5Gfrc{f!* zWFEBm?l@_b7kzY7%1RQQbG5V<4=ZlkZ%sF74Q|mKOc7Ak7dP2#quiGcZ0_J%7Q?j{ zv9{WFw;n5G-Mn%r#0R;{jLt{yy}9J6rQ(>X9pJ`7Xy?Zv z=lNit#qXaq?CnElK^zF~sG}U5oCpR0T>FH=ZX}Prju$);?;VOhFH8L3I><9P_A|C+ z{;>~dk%9rrq(snjsEm}oUz2FQ21MCG*e?g)?{!&|eg7PX@I+Q0!hL6C7ZVY|g2E>i zr!Ri2@OfEu$)d52+>+cpgh6Z;cLYCZ&EMR0i<^~4&wEu_bdo;y^6}+U2GIQgW$|Od z_jg{O=pU>0-H$P-EOlWyQy#W0r@@_uT}Lg+!d5NxMii7aT1=|qm6BRaWOf{Pws54v zTu=}LR!V(JzI07>QR;;px0+zq=(s+XH-0~rVbmGp8<)7G+Jf)UYs<$Dd>-K+4}CsD zS}KYLmkbRvjwBO3PB%2@j(vOpm)!JABH_E7X^f#V-bzifSaKtE)|QrczC1$sC<<*Y z$hY*3E10fYk`2W09gM_U<2>+r^+ro$Bqh-O7uSa)cfPE_<#^O) zF+5V;-8LaCLKdIh3UB@idQZL`0Vx8`OE#6*1<;8(zi&E7MWB1S%~HAm%axyIHN2vd zA(pJGm_PraB0Aat3~?obWBs?iSc*NhM!{-l_WNCx4@F7I?)5&oI|z{o@JKd1HZ}zf*#}JjK3$ z-;3V*WJZvUcKvSOBH4c7C{fl8oRw8-vfgKQjNiR|KhQ%k6hWNEke(k8w-Ro| z7Y3)FsY-?7%;VT64vRM)l0%&HI~BXkSAOV#F3Bf#|3QLZM%6C{paqLTb3MU-_)`{R zRdfVQ)uX90VCa3ja$8m;cdtxQ*(tNjIfVb%#TCJWeH?o4RY#LWpyZBJHR| z6G-!4W5O^Z8U}e5GfZ!_M{B``ve{r0Z#CXV0x@~X#Pc;}{{ClY_uw^=wWurj0RKnoFzeY` z;gS!PCLCo*c}-hLc?C&wv&>P1hH75=p#;D3{Q8UZ0ctX!b)_@Ur=WCMEuz>pTs$@s z#7bIutL9Pm2FDb~d+H}uBI#pu6R}T{nzpz9U0XLb9lu@=9bTY&PEyFwhHHtXFX~6C zrcg|qqTk(|MIM%KQ<@j=DOjt|V)+8K26wE_CBNnZTg+Z+s}AU|jp6CFoIptG1{J*# z7Ne~l;ba*=bSwAMQ|Vq#fW~+je4PXA91YFzBubNF?ovIOw-$C-8=Ehed{lGD0}(Id zRe4sh8L>&T%{>8o))he}eE;5_ zxoXk3wX?MyNl-xF!q1d$G?=wp^`@09(jU&X zOqZIBI#dN`2PJNdATR3ivtub|nO$dulSaP|e4)WXF1YAGN1pDQIbIjXFG!oC85Mt; zW$eteoL{y^5t4TMRwP$jNPjZFpGsWnGe=jMMqKtcZm9Y9PFZLi*1p@qoKKub^T@2+ zk$@*KYdQ?Z`}<%4ALwk*Yc{(WTf@#u;as(fvE^9{Gk)lWbJP*SjttWofV0s?AB({~l zZI1hZVWFT~W-T?nfMMcnCS4-#6H-MU7H$KxD;yaM46K4Kc@~Q>xzB+QnD_I`b_l3m zo9pRx46b!p?a^&zCDwygqqV3epjs(s0NQI6ARA1n!Yy-qduipxQ& zUAlqRpNjBS+y-ZheD(!R;F}&^V_}b_gqH%tVZ5%%ziO7k^w=es+wZtK^i*vmrWNLMs{oWu_CIov|s1raZiS)>38>pYu;i+-t zI_DiNe6aA4KTZ2P09qPj(0~K4nUq^0+f(2$g`229zkG4jLzRvJUWE0oF1XHL4t3UN zDH466G56sy9hTZoAJB!C3;@F;ONxEk5u6Mv%zdo}Rq`=* zw1n7MOhfNSV48TS989ArIcj`C%Gk8~93~u>)!Yt2b4ZriKj9x2d`H2HQNJ=I>hkDlcZn zqRj>!;oRMTIOu zx|Zfsu~v76T{z7AC(jxj^c@tnJHZtGPsq$DE!8kqvkDx5W?KUJPL+!Ffpwfa+|5z5 zKPCiOPqZZrAG;2%OH0T$W|`C@C*!Z`@Wkop{CTjB&Tk`+{XPnt`ND`Haz;xV`H^RS zyXYtw@WlqTvToi;=mq1<-|IQ(gcOpU%)b#_46|IuWL#4$oYLbqwuk6=Q@xZaJSKVF zZcHs~ZBl;&lF3=+nK; zF`4gSCeZXlwmC_t4I`#PUNQ*)Uv&oGxMALip|sxv^lyVV73tKI7)+QY5=tEMas{vTD-BaTJ^*Y6gq~PU;F5X!sxqiq$iFCo+Uv7m%1w((=e}Vf*=dtds|6 zbX}91!G?C*KG03eHoN}RZS9DJxa&8YwNCT8?JxMXyZqZr13NA|GB{+vG`08C{V(yy zf*Lw$+tYSU_+dI`3n{bMrPdDb`A=Mkg!O=k>1|*3MC8j~- zXL79J4E=U^H=iBLTeHE_OKzE&dws8RNynsSJ!d;`zK?P92U{f)xvD7VQVosrXZrL+ z6lMVdD1YgL;%(1cq{#bS6yXmp|DS@nax#AqqlZhtUQdh<^2vr5`EpAO

LGYq)sa(w9^3-f}NHy=GR4v%t2YZly3m1G@5y`xBh_HGrD%f z>;|Ty?9FiJAc&UVD(StT4I` zfVQwxhE9bXE6r2mKO8Ag7{L^jCyqQb0QqKDPE=RAgqn8q1O^>(z7h5kE(6va%QqRZ zkIOmp(})rLSS(2{=C12e&@!W2=Jel-^_R``0xHO^+t!(oXbcv5yhD4g*$t_F)_5Dl zSVCgesW%;DtYPCFs{G;GX_o?1J3;QQPPv)rWw;>} zJ&KwnUqwNXloNXlK_+pNDfI~hON#SokVJb&ilg8d7^NWo2ZQymCqQMnjfi>ePibjr z-Z@q!?RGN$Mj}Nk){X_vaj6?Mj$>ACR*z|6MsXy3VZ^PFn@yHkPo(>m(iWepn8SC@ z>D2;R4m+gDRZ=SIX!b+CP(qE=JDIUkn=D$aUu+Ihn9-+k1LS3PreQg0N5eWIG@x${nC3v^7caS>1!PKNAY9J z#}E}Q9w#SP>(GY7Hbj&z4$Li6o5taBO|4+F`yS9zq*LJ<38wy4I>HA9(&GYrk4dLajKGww))BWli6Ln1A^Lda@N~p+snkb9C z@OthI+<##vp8!HVQT4Wk(=@zQ{OvZ$EKWS73+JHb)eYLGD-cqi6^|vd$<+IHuc?Nq zW7JertT~3))4?J|28n$I@nAD0c1%9C&IVhEZX~mUsf{efyS(XNG%ch;!N~d7S(Ri7 zb&=BuON95aVA&kLn6&MVU|x}xPMp7xwWxNU1wS+F6#y}1@^wQZB*(&ecT?RnQcI}Y z2*z!^!D?gDUhc@;M^OpLs4mq>C&p{}OWVv<)S9KMars@0JQ{c_ScGsFo3BJ)Irg++ zAWwypJdTO-_{Uh8m(Z!3KL7K{ZZzKHj;{M8I$mV>k znTM?sa0);^=X^cglL`uC+^J)M7nEa$w=VwFULg~%DJllw+7dJAj3{qnP5i3@wr7%y zjXp?Wl2%Th=my&3u?Q$RV6N5tzKMSPTsc#J+-cDDp~qFB6bL2C8AS7Y3PKtVhdhl) zIaLqH5+OnWPWSt(lQCgkN8lczc-V%_iZ{>#1%Z$N*>lu#S;0MZ$T2Y8Kg!U;hAZj> z6S#%$DQ_`Ic%Zr@?}GgjRXg@qTj^17n`65oJ@Wj0u1X8&+UVd|Xs?J+i_^GZ94m6= zUc96~Q`OJvlKB_Lr15*Yw_PUPEr?f?H&00b^-W%26mD)(n(rGGNfK9~2h=C>p-7BZ zFd&*&Msdu{w~(eyFOglwCPH^Rb}O(N7LtS+nnEwDx*pGD?|&9Si~M43a+*L(b0$5A zv`T`(G3xO;I_sx;FwTP21ZlfDpz zOo?}Vlgf~fo{YWm@n_JyD*frOg{XsvBA~|Tn4V6hu>Gd>89-rblfVJUaGvj6X%NZ} z$tFF9sx=4_$*c~G`9iPLGh@=sV+O{D2-t*K@J7H=`V+oVt}8?04WwU3h1BgS!f%1P zFak-T#7`TtLcR=Yz>g0R!ZQrH!YiZOQN=_V-UyncN1Rc18?KY?#O`v#JK+pq0K$~H z3D@v9DZF42R)b9#BBX{^$DOMlJ!g)Gc za{o-1e%F6NvgKq9tC8pV+9S$;9*zNv{J*)n&dmf~anP1)4~N%~h#c(=B#3*KgzhCKhFdgDoWi2IDog{RVyzK|Y`rCUs3T~pJMmdZJy4?b z&s5G=zhf**(t7Y^oC_mcTsE-{^}wiaoUu&?kojLKs>SJPxjcP>{a5CbXCx92AcBE) zHtqP}LjZ{W>PH?Tu(E0X=%{PBMW@F_?#7b&#!^q`<-5$ur+-q6 z{dn=(^UZw6*3-XM_(=@<1_*i&XM4=0t5u!gm6 z{UlmNGPKgO_;e;q9|#esq~Sq`<}%d{+sRmhvsA{5i*91=tub>OZZ%)xUA#4q$dDyy z1`w4%?OPLg3JeZb#cqSMO?*Xn%|-FCcuH2i2fn_{IFusub6;NQdN|7TD1N?%E8*g? z$apAt@cEe!I%jB=*q$p_3=t_5R0ph%{qaq+QDg!c99Y!Xa!&oDZOeis_ot)gNXr{l zdY$|So2Qed2Y7KMNBrS^E169kG%h<+z{Z_p_;shB!uY)>yAVcK=&!bg`lVg)4T1|7 z0}7FpfydVH4F87K@c!nEG+WGKm{Ouo)Slpl;#qcEIQ0zdMfLA#;dBxYw;p;KoVv6| z3_D5&7rJdG12CnDSvZUW?$UC6^UVSW^|vw|o-_4bz)(w5(3AiVhpeT(|=f#x_}E?s#qHZF#xA6AF_ujl$G z-jHD%q(d2}v2PhXx&6YWps~m(^+RXl91Q#xRRJBhjKl$FG4bk);|ag;ieUZ&!Ii3$ z(iGz1+0m7#g5>ASldBbNZL=ZHh=tmmJt$!71; zIML2GhEz1pg@1rQN(M^_691wAGkJ@Pga_05WuQ6! zG5RkGY2^`@(H~pp7&Ga+Pwh3L!Njj!-rc;^bTIfo5hP@H##1X8xUZJckrx>id`bAd3QUx9GuomqBYZ!uN1-&o zvTxC?;p8vL67&fW8fw(YOqt>L@bdLrEF*3OgYe$4n4{ zEB40LiU#6-0@5jdN`0w}N0qi@c0~oT2FP z)LNk&a82my?jv(tQpiMi$TK_L@lub#lsM$R{Dk?Ya@%%%huZkct~tSWM714c!45k}-ZLVA-bVM`>|_ZBbW_m-7| z3U%xrAhi}n?T(2F{_n4EZ10inkIFl#y09?7$uwBoJgqY8vylwev)fDOn;>0R!aEnV zBz%j0Mqpx~EZU3q@%+oV7;}|vt7$~ou@faEIq{p?FY$XXg&6*K)b_LP=}gi9`Bij3 zN`zEo|B6*|-;>S`rNa^BKRDbDAk>X#MsR`EvL>6bqU@SaDDs z8>bu@3YdRaWs*Te@G-UHjU%F~kTHw5(0PVJ+pwh#ha2u;DB+UMo@A5UYIl#5rtBV- zGX_hIpw}3C@H*Us(Cc-d#-gNrG#w$(9+S=GxO>3SR`SE2fHZ2KrDc#_C^$jI>Y}#; zMwY=R6@+dWi~0RXw(c@3GZ&%~9K(q&ee0Zw;pwL`E_tZak-#8^_b)Dpyi73^he?xV zXJ08&wh5-M&}qy4f7!D&=E)puDD(Nmg1d_(j`4LvxM5x_huNg-pGG%9rYqO6mImyJ@}*3Y>^3OvcnTG%EV1) zq_Ap?Z!Iw__7#D=pOWnQN$gB!Mr0!9yx|g<4icJh{cFOu3B8}&RiYm+Mb;VEK``LK zL(NcpcTiGieOIssSjr?ob}^``nNf&UcJhXyncO9m{6gD$kqSD`S69(aF8dkWz5>!9 zBLe4Sib7Hs2x_L2Ls6Ish$MGVKrGt5+_2zCyP1byaCg3upo+-I}R4&$m)8 zQ7|jc1Z^VWggpuQj*cP;>Zo9LS!VSzrqmZczaf;u`d0J(f%Z9r%An@s!e>n9%y=n!IZ_tVGu{Jmsbp}Fk%HJIU?a+-~bjfLTuH|JExA8EROowzr zqW9{YyZhR0a4clRK>1I4Ncx&WER~{iE;F^$T7K%X@3PGOA%6#Z%p3TS^&M;Dnjw@i z^o!$9nhcsmcHcY4?4j9+ofL_CWsZ4Hcch(rjsGfGD(nsH>w}^ERqGnz%iGj0j{g}h z7wMkJ-2Z2~eS>2!i}0~B63i;>SyFJU2+>VCS^AxaDOx%g6-t0eM^P<3+*z`ztvOqrG3)&#$K?& z_Y0wbWID47@cU`E1A6A&!`aZk0ZE@z-h#l1NqX2#`$Uev2gepW`rf8*!=rD5&;Jb{ zl08rU>dPo=K%-1Ao1~G-@4ve~y5#9E8x;TE0k5d^TC(=Zc>mwjW^c=+U-<9}b0ku~}gj z3sbW>R2M6DR!g#NUP;nxo>)@7*=RP{U18SDop6b2&PHce^&h97@xx3t+VK+!keE#} z;(Uf&89as9k8{$nkLbuB!-d7TP`_VJpL^Xs8OKB~ri$YUbW8fch64}7|0EWoT(TRj{ z*GT<7Y<7DsrCi79ZsM)z#c(!nNOGySOCkY1fAuQOq12&iUVC!a`#O;dBLf=d?&4*B zI~LgAO7E0qxK(uRTM;IgJ}+z^gD+bi-6I!3x{r9`l~%8TRP%UE0V8E*Sz>Nl1NVG<<7(wDHZ+HcOkQm$O&k+vyx)y)x{Pz!U8hS$*m zByc0h6BUI*BOpuL==P+H|Hx%`>7!W+1H!l9vi&)`V zyn2o9{z=lc+VX*!Vh~SF=)L}Z40XeG>LF6cP^b+R$NxSeUqbK^Q*UTalKzP8X%{9@RSCXm_NhF>{=S2 zi}ezam_^P`S!!-cyEW9y7DBbK93roz@Raccy*v}?mKXScU9E_4g;hBU7}zSofAFda zKYEe?{{I54 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..cea7a79 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf1..f3b75f3 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 77c80b6877462532bf90a6b7ac3d61d71023d6be Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 25 Jan 2025 14:26:58 +0100 Subject: [PATCH 02/31] feat: use gson for json parsing --- .../re/sylfa/itemcreator/ItemCreator.java | 24 +++++++- .../sylfa/itemcreator/items/CustomItem.java | 12 ++-- .../sylfa/itemcreator/items/ItemReader.java | 56 +++++++++++++------ .../sylfa/itemcreator/items/ItemRegistry.java | 4 +- .../typeAdapters/ComponentAdapter.java | 21 +++++++ .../itemcreator/typeAdapters/KeyAdapter.java | 20 +++++++ .../typeAdapters/MaterialAdapter.java | 26 +++++++++ 7 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java create mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java create mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index f621fb8..47407cd 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -2,10 +2,10 @@ import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.Recipe; import org.bukkit.permissions.Permission; @@ -14,9 +14,14 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.permissions.CommandPermissions; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import re.sylfa.itemcreator.commands.ItemCreatorCommand; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; @@ -24,6 +29,9 @@ import re.sylfa.itemcreator.recipes.CustomRecipe; import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; +import re.sylfa.itemcreator.typeAdapters.ComponentAdapter; +import re.sylfa.itemcreator.typeAdapters.KeyAdapter; +import re.sylfa.itemcreator.typeAdapters.MaterialAdapter; import re.sylfa.itemcreator.util.Log; public class ItemCreator extends JavaPlugin { @@ -31,6 +39,11 @@ public class ItemCreator extends JavaPlugin { private static ItemCreator instance; private static ItemRegistry itemRegistry = new ItemRegistry(); private static RecipeRegistry recipeRegistry = new RecipeRegistry(); + private static Gson gson = new GsonBuilder() + .registerTypeAdapter(Key.class, new KeyAdapter()) + .registerTypeAdapter(Material.class, new MaterialAdapter()) + .registerTypeAdapter(Component.class, new ComponentAdapter()) + .create(); @Override public void onEnable() { @@ -53,9 +66,14 @@ public static RecipeRegistry getRecipeRegistry() { return recipeRegistry; } + public static Gson getGson() { + return gson; + } + public void init(){ itemRegistry.removeAll(); itemRegistry.add(ItemReader.readAllItems().toArray(CustomItem[]::new)); + List readRecipes = RecipeReader.readAllRecipes(); Map readRecipesMap = readRecipes.stream().collect(Collectors.toMap(r -> r.key(), r -> r.asRecipe())); @@ -70,11 +88,11 @@ public void reload() { init(); } - public void registerPermissions() { + private void registerPermissions() { CommandPermissions.registerPermissions(new Permission("itemcreator.give", PermissionDefault.OP)); } - public void registerCommands() { + private void registerCommands() { LifecycleEventManager manager = this.getLifecycleManager(); manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { final Commands commands = event.registrar(); diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index e32c129..6dbfe84 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -40,6 +40,10 @@ public Key key() { return this.key; } + public void key(Key key) { + this.key = key; + } + public int customModelData() { return customModelData; } @@ -95,13 +99,13 @@ public ItemStack asItemStack() { return item; } + public static Builder builder(Key key) { + return new Builder().key(key); + } + public static class Builder { CustomItem item = new CustomItem(); private MiniMessage mm = MiniMessage.miniMessage(); - - public static Builder builder(Key key) { - return new CustomItem.Builder().key(key); - } private Builder key(Key key) { item.key = key; diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 923a945..43ddad3 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -1,12 +1,15 @@ package re.sylfa.itemcreator.items; import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; -import org.bukkit.Material; -import org.bukkit.configuration.file.YamlConfiguration; + +import com.mojang.authlib.minecraft.client.ObjectMapper; import net.kyori.adventure.key.Key; import re.sylfa.itemcreator.ItemCreator; @@ -14,6 +17,8 @@ public class ItemReader { + public static final ObjectMapper objectMapper = ObjectMapper.create(); + public static List readAllItems() { File itemsFolder = new File(ItemCreator.getInstance().getDataFolder(), "items"); if(!itemsFolder.exists()) { @@ -35,7 +40,7 @@ public static List readAllItems() { String folderName = folder.getName(); return Arrays.stream(folder.listFiles()) .filter(file -> { - if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { + if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { Log.warn("%s is not an item file in %s", file.getName(), folderName); return false; } else { @@ -57,23 +62,42 @@ public static CustomItem readItem(File file) { Log.warn("%s item is not in a namespace", file.getName()); return null; } - - return readItem(YamlConfiguration.loadConfiguration(file), parentName, fileName); + + try { + return readItemJson(readFile(file), parentName, fileName); + } catch (IOException e) { + e.printStackTrace(); + return null; + } } - public static CustomItem readItem(YamlConfiguration config, String folderName, String fileName) { + public static CustomItem readItemJson(String config, String folderName, String fileName) { Key key = Key.key(folderName, fileName); Log.log("Reading item %s", key.asString()); - return CustomItem.Builder.builder(key) - .customModelData(config.getInt("model")) - .itemName(config.getString("name", "")) - .material(Material.matchMaterial(config.getString("material", "diamond_pickaxe"))) - .lore(config.getStringList("lore")) - .maxDamage(config.getInt("maxDamage")) - .maxStackSize(config.getInt("maxStackSize", 1)) - .jukeboxPlayableComponent(config.getString("jukeboxSong.name"), config.getBoolean("jukeboxSong.showInTooltip", true)) - .enchantmentGlintOverride(config.getBoolean("enchantmentGlintOverride.set"), config.getBoolean("enchantmentGlintOverride.value")) - .build(); + // CustomItem item = objectMapper.readValue(config, CustomItem.class); + CustomItem item = ItemCreator.getGson().fromJson(config, CustomItem.class); + item.key(key); + return item; + } + + // public static CustomItem readItem(YamlConfiguration config, String folderName, String fileName) { + // Key key = Key.key(folderName, fileName); + // Log.log("Reading item %s", key.asString()); + + // return CustomItem.Builder.builder(key) + // .customModelData(config.getInt("model")) + // .itemName(config.getString("name", "")) + // .material(Material.matchMaterial(config.getString("material", "diamond_pickaxe"))) + // .lore(config.getStringList("lore")) + // .maxDamage(config.getInt("maxDamage")) + // .maxStackSize(config.getInt("maxStackSize", 1)) + // .jukeboxPlayableComponent(config.getString("jukeboxSong.name"), config.getBoolean("jukeboxSong.showInTooltip", true)) + // .enchantmentGlintOverride(config.getBoolean("enchantmentGlintOverride.set"), config.getBoolean("enchantmentGlintOverride.value")) + // .build(); + // } + + private static String readFile(File file) throws IOException { + return FileUtils.readFileToString(file, Charset.defaultCharset()); } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java index d8e1b93..cf438b0 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java @@ -17,7 +17,7 @@ public class ItemRegistry { final Map itemList = new HashMap<>(); public void add(CustomItem... item) { - Arrays.stream(item).forEach(i -> itemList.put(i.key(), i));; + Arrays.stream(item).forEach(i -> itemList.put(i.key(), i)); } @Nullable @@ -44,7 +44,7 @@ public void removeAll() { public ItemStack parse(Key key) { // first, try to get Minecraft material - if(key.namespace() == Key.MINECRAFT_NAMESPACE) { + if(key.namespace().equals(Key.MINECRAFT_NAMESPACE)) { Material material = Material.matchMaterial(key.value()); if (material != null) return new ItemStack(material); } diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java new file mode 100644 index 0000000..861e56a --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java @@ -0,0 +1,21 @@ +package re.sylfa.itemcreator.typeAdapters; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; + +public class ComponentAdapter implements JsonDeserializer{ + private static final MiniMessage mm = MiniMessage.miniMessage(); + @Override + public Component deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return mm.deserialize(json.getAsString()); + } + +} diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java new file mode 100644 index 0000000..859f4cf --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java @@ -0,0 +1,20 @@ +package re.sylfa.itemcreator.typeAdapters; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.kyori.adventure.key.Key; + +public class KeyAdapter implements JsonDeserializer{ + + @Override + public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return Key.key(json.getAsString()); + } + +} diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java new file mode 100644 index 0000000..b98183d --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java @@ -0,0 +1,26 @@ +package re.sylfa.itemcreator.typeAdapters; + +import java.lang.StackWalker.Option; +import java.lang.reflect.Type; + +import org.bukkit.Material; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +public class MaterialAdapter implements JsonDeserializer { + + @Override + public Material deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + String rawMaterial = json.getAsString(); + Material material = Material.matchMaterial(rawMaterial); + if(material == null) { + throw new JsonParseException(String.format("Bad Material: %s", rawMaterial)); + } + + return material; + } +} From 6b6789c7f9d852db7d7af5f27bc6232d152a5371 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:17:14 +0200 Subject: [PATCH 03/31] chore: update Minecraft to 1.21.5 --- .gitignore | 6 ++++-- build.gradle.kts | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 81fb6b4..2006678 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ # Ignore Gradle project-specific cache directory -.gradle +.gradle/ # Ignore Gradle build output directory build .vscode -bin \ No newline at end of file +bin + +.idea/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 8c35cd2..ca5df56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { `java-library` - id("io.papermc.paperweight.userdev") version "2.0.0-beta.14" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" } repositories { @@ -21,9 +21,11 @@ repositories { dependencies { compileOnly("com.mojang:brigadier:1.0.18") - implementation("commons-io:commons-io:2.7") - paperweight.paperDevBundle("1.21.4-R0.1-SNAPSHOT") - implementation("net.kyori:adventure-text-minimessage:4.18.0") + implementation("commons-io:commons-io:2.19") + paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") + implementation("net.kyori:adventure-text-minimessage:4.20.0") + compileOnly("org.projectlombok:lombok:1.18.38") + annotationProcessor("org.projectlombok:lombok:1.18.38") } java { From a7327ae0bc40ba44b4580e8a121c449c3ae22eca Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Fri, 18 Apr 2025 08:35:42 +0200 Subject: [PATCH 04/31] refactor: started refactoring JSON deserializers with Jackson --- .gitignore | 3 +- build.gradle.kts | 21 +- gradle/libs.versions.toml | 19 ++ run/plugins/ItemCreator/items/truc/truc.json | 4 + .../re/sylfa/itemcreator/ItemCreator.java | 35 ++- .../commands/ItemCreatorCommand.java | 2 +- .../argumentType/CustomItemArgumentType.java | 4 +- .../sylfa/itemcreator/items/CustomItem.java | 278 ++++++++---------- .../sylfa/itemcreator/items/ItemReader.java | 75 +++-- .../sylfa/itemcreator/items/ItemRegistry.java | 26 +- .../deserializers/ComponentDeserializer.java | 27 ++ .../items/deserializers/ItemDeserializer.java | 76 +++++ .../deserializers/MaterialDeserializer.java | 23 ++ .../parsers/PotionEffectParser.java | 57 ++++ .../java/re/sylfa/itemcreator/util/Log.java | 17 +- 15 files changed, 446 insertions(+), 221 deletions(-) create mode 100644 run/plugins/ItemCreator/items/truc/truc.json create mode 100644 src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java diff --git a/.gitignore b/.gitignore index 2006678..fa79b2d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ build .vscode bin -.idea/ \ No newline at end of file +.idea/ +run/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ca5df56..164d0df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,7 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This is a general purpose Gradle build. - * To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.8/samples - */ - plugins { `java-library` - id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" + alias { libs.plugins.paperweight } + alias { libs.plugins.runPaper } } repositories { @@ -20,12 +14,13 @@ repositories { } dependencies { - compileOnly("com.mojang:brigadier:1.0.18") - implementation("commons-io:commons-io:2.19") paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") - implementation("net.kyori:adventure-text-minimessage:4.20.0") - compileOnly("org.projectlombok:lombok:1.18.38") - annotationProcessor("org.projectlombok:lombok:1.18.38") + compileOnly(libs.brigadier) + implementation(libs.commonsIo) + implementation(libs.miniMessage) + compileOnly(libs.lombok) + annotationProcessor(libs.lombok) + implementation(libs.jackson) } java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ac3234..491536d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,2 +1,21 @@ # This file was generated by the Gradle 'init' task. # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format +[versions] +paperweight = "2.0.0-beta.16" +brigadier = "1.0.18" +runPaper = "2.3.1" +lombok = "1.18.38" +adventure = "4.20.0" +commonsIo = "2.19.0" +jackson = "2.19.0-rc2" + +[libraries] +brigadier = {group = "com.mojang", name = "brigadier", version.ref = "brigadier"} +lombok = {group = "org.projectlombok", name = "lombok", version.ref = "lombok"} +miniMessage = {group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure"} +commonsIo = {group = "commons-io", name = "commons-io", version.ref = "commonsIo"} +jackson = {group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson"} + +[plugins] +paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight"} +runPaper = { id = "xyz.jpenilla.run-paper", version.ref="runPaper"} \ No newline at end of file diff --git a/run/plugins/ItemCreator/items/truc/truc.json b/run/plugins/ItemCreator/items/truc/truc.json new file mode 100644 index 0000000..6b6cb5a --- /dev/null +++ b/run/plugins/ItemCreator/items/truc/truc.json @@ -0,0 +1,4 @@ +{ + "material": "minecraft:brick", + "itemName": "oeeeeeeeeeeeeee trop cool" +} \ No newline at end of file diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 47407cd..ef0e7e2 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -4,10 +4,15 @@ import java.util.Map; import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.meta.components.EquippableComponent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; @@ -26,12 +31,12 @@ import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; +import re.sylfa.itemcreator.items.deserializers.ComponentDeserializer; +import re.sylfa.itemcreator.items.deserializers.ItemDeserializer; +import re.sylfa.itemcreator.items.deserializers.MaterialDeserializer; import re.sylfa.itemcreator.recipes.CustomRecipe; import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; -import re.sylfa.itemcreator.typeAdapters.ComponentAdapter; -import re.sylfa.itemcreator.typeAdapters.KeyAdapter; -import re.sylfa.itemcreator.typeAdapters.MaterialAdapter; import re.sylfa.itemcreator.util.Log; public class ItemCreator extends JavaPlugin { @@ -39,16 +44,17 @@ public class ItemCreator extends JavaPlugin { private static ItemCreator instance; private static ItemRegistry itemRegistry = new ItemRegistry(); private static RecipeRegistry recipeRegistry = new RecipeRegistry(); - private static Gson gson = new GsonBuilder() - .registerTypeAdapter(Key.class, new KeyAdapter()) - .registerTypeAdapter(Material.class, new MaterialAdapter()) - .registerTypeAdapter(Component.class, new ComponentAdapter()) - .create(); + + @Getter + private static final ObjectMapper mapper = new ObjectMapper(); + @Override public void onEnable() { instance = this; Log.log("Enabled!"); + registerMappers(); + init(); registerPermissions(); registerCommands(); @@ -66,9 +72,6 @@ public static RecipeRegistry getRecipeRegistry() { return recipeRegistry; } - public static Gson getGson() { - return gson; - } public void init(){ itemRegistry.removeAll(); @@ -101,4 +104,14 @@ private void registerCommands() { ).forEach(command -> commands.register(command.command(), command.aliases())); }); } + + private void registerMappers() { + SimpleModule module = new SimpleModule(); + module.addDeserializer(ItemStack.class, new ItemDeserializer()) + .addDeserializer(Material.class, new MaterialDeserializer()) + .addDeserializer(Component.class, new ComponentDeserializer()); + + mapper.registerModule(module); + + } } diff --git a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java index d031857..9ba4ad7 100644 --- a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java +++ b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java @@ -49,7 +49,7 @@ private int giveItem(CommandContext ctx) { @SuppressWarnings("unchecked") Optional item = ctx.getArgument("itemKey", Optional.class); if(item.isPresent()) { - player.getInventory().addItem(item.get().asItemStack()); + player.getInventory().addItem(item.get().getItem()); } else { player.sendMessage(Component.text("Error: not a valid custom item.", NamedTextColor.RED)); } diff --git a/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java b/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java index da2b3dd..6fbd038 100644 --- a/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java +++ b/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java @@ -30,8 +30,8 @@ public static CustomItemArgumentType customItem() { } @Override - public @NotNull Optional convert(@NotNull Key key) throws CommandSyntaxException { - return Optional.ofNullable(ItemCreator.getItemRegistry().get(key)); + public Optional convert(@NotNull Key key) throws CommandSyntaxException { + return ItemCreator.getItemRegistry().get(key); } @Override diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index 6dbfe84..d9e4d8a 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -1,40 +1,16 @@ package re.sylfa.itemcreator.items; -import java.util.List; - -import org.bukkit.JukeboxSong; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.inventory.CraftItemStack; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; -import org.bukkit.persistence.PersistentDataType; - -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; +import lombok.Getter; import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.minecraft.core.component.DataComponents; -import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.Log; +import org.bukkit.inventory.ItemStack; +@Getter public class CustomItem { private static final String ID = "id"; private Key key; - private boolean hasCustomModelData = false; - private int customModelData; - private Component itemName; - private Material material; - private List lore; - private int maxDamage; - private boolean hasMaxDamage = false; - private int maxStackSize; - private JukeboxPlayableComponent jukeboxPlayableComponent; - private boolean hasEnchantmentGlintOverride; - private boolean enchantmentGlintOverride; + private ItemStack item; public Key key() { return this.key; @@ -44,58 +20,51 @@ public void key(Key key) { this.key = key; } - public int customModelData() { - return customModelData; - } - - public Component itemName() { - return itemName; - } - - public Material material() { - return material; - } - - public List lore() { - return lore; - } - - public int maxDamage() { - return maxDamage; - } - - public int maxStackSize() { - return maxStackSize; - } - - public JukeboxPlayableComponent jukeboxPlayableComponent() { - return jukeboxPlayableComponent; - } + // public int customModelData() { + // return customModelData; + // } + +// public List lore() { +// return lore; +// } +// +// public int maxDamage() { +// return maxDamage; +// } +// +// public int maxStackSize() { +// return maxStackSize; +// } +// +// public JukeboxPlayableComponent jukeboxPlayableComponent() { +// return jukeboxPlayableComponent; +// } public ItemStack asItemStack() { - var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); - // setMaxDamage(null) only resets the *patched* value, not the actual one - itemNms.remove(DataComponents.MAX_DAMAGE); - itemNms.remove(DataComponents.TOOL); - itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); - itemNms.remove(DataComponents.FOOD); - - ItemStack item = itemNms.asBukkitCopy(); - item.editMeta(Damageable.class, meta -> { - meta.getPersistentDataContainer().set(NamespacedKey.fromString(ID, ItemCreator.getInstance()), PersistentDataType.STRING, key.asString()); - meta.itemName(itemName); - meta.setCustomModelData(this.hasCustomModelData ? customModelData : null); - meta.lore(lore); - if(hasMaxDamage) { - meta.setMaxDamage(maxDamage); - } - meta.setMaxStackSize(maxStackSize); - meta.setJukeboxPlayable(jukeboxPlayableComponent); - if(hasEnchantmentGlintOverride) { - meta.setEnchantmentGlintOverride(enchantmentGlintOverride); - } - }); - +// var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); +// // setMaxDamage(null) only resets the *patched* value, not the actual one +// itemNms.remove(DataComponents.MAX_DAMAGE); +// itemNms.remove(DataComponents.TOOL); +// itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); +// itemNms.remove(DataComponents.FOOD); +// +// ItemStack item = itemNms.asBukkitCopy(); +// item.editMeta(Damageable.class, meta -> { +// meta.getPersistentDataContainer().set(NamespacedKey.fromString(ID, ItemCreator.getInstance()), PersistentDataType.STRING, key.asString()); +// meta.itemName(itemName); +// // meta.setCustomModelData(this.hasCustomModelData ? customModelData : null); +// meta.lore(lore); +// if(hasMaxDamage) { +// meta.setMaxDamage(maxDamage); +// } +// meta.setMaxStackSize(maxStackSize); +// meta.setJukeboxPlayable(jukeboxPlayableComponent); +// if(hasEnchantmentGlintOverride) { +// meta.setEnchantmentGlintOverride(enchantmentGlintOverride); +// } +// meta.setEquippable(equippableComponent); +// }); +// return item; } @@ -112,84 +81,89 @@ private Builder key(Key key) { return this; } - Builder customModelData(int customModelData) { - if(customModelData != 0) { - item.customModelData = customModelData; - item.hasCustomModelData = true; - } - return this; - } - - Builder itemName(String rawItemName) { - return itemName(mm.deserialize(rawItemName)); - } - - Builder itemName(Component itemName) { - item.itemName = itemName; - return this; - } - - Builder material(Material material) { - item.material = material; - return this; - } - - Builder lore(List lore) { - if(!lore.isEmpty()) { - item.lore = lore.stream().map(line -> mm.deserialize(""+line+"")).toList(); - } - return this; - } - - Builder maxDamage(int maxDamage) { - if(maxDamage > 0) { - item.maxDamage = maxDamage; - item.hasMaxDamage = true; - } else if (maxDamage < 0) { - Log.warn("Negative max damage for %s", item.key.asString()); - } - - return this; - } - - Builder maxStackSize(int maxStackSize) { - if(maxStackSize < 1 || maxStackSize > 99) { - maxStackSize = 1; - Log.warn(String.format("Max stack size for %s is not valid", item.key.asString())); - } - - item.maxStackSize = maxStackSize; - return this; - } - - Builder jukeboxPlayableComponent(String rawJukeboxSong, boolean showInTooltip) { - if(rawJukeboxSong == null) { - return this; - } - JukeboxSong jukeboxSong = RegistryAccess.registryAccess().getRegistry(RegistryKey.JUKEBOX_SONG).get(Key.key(rawJukeboxSong)); - if(jukeboxSong != null) { - // HACK there's no JukeboxPlayableComponent constructor - JukeboxPlayableComponent jukeboxPlayableComponent = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); - jukeboxPlayableComponent.setSong(jukeboxSong); - jukeboxPlayableComponent.setShowInTooltip(showInTooltip); - return jukeboxPlayableComponent(jukeboxPlayableComponent); - } - return this; - } + // Builder customModelData(int customModelData) { + // if(customModelData != 0) { + // item.customModelData = customModelData; + // item.hasCustomModelData = true; + // } + // return this; + // } + +// Builder itemName(String rawItemName) { +// return itemName(mm.deserialize(rawItemName)); +//// } +// +// Builder itemName(Component itemName) { +// item.itemName = itemName; +// return this; +// } + +// Builder material(Material material) { +// item.material = material; +// return this; +// } + +// Builder lore(List lore) { +// if(!lore.isEmpty()) { +// item.lore = lore.stream().map(line -> mm.deserialize(""+line+"")).toList(); +// } +// return this; +// } +// +// Builder maxDamage(int maxDamage) { +// if(maxDamage > 0) { +// item.maxDamage = maxDamage; +// item.hasMaxDamage = true; +// } else if (maxDamage < 0) { +// Log.warn("Negative max damage for %s", item.key.asString()); +// } +// +// return this; +// } +// +// Builder maxStackSize(int maxStackSize) { +// if(maxStackSize < 1 || maxStackSize > 99) { +// maxStackSize = 1; +// Log.warn(String.format("Max stack size for %s is not valid", item.key.asString())); +// } +// +// item.maxStackSize = maxStackSize; +// return this; +// } +// +// Builder jukeboxPlayableComponent(String rawJukeboxSong, boolean showInTooltip) { +// if(rawJukeboxSong == null) { +// return this; +// } +// JukeboxSong jukeboxSong = RegistryAccess.registryAccess().getRegistry(RegistryKey.JUKEBOX_SONG).get(Key.key(rawJukeboxSong)); +// if(jukeboxSong != null) { +// // HACK there's no JukeboxPlayableComponent constructor +// JukeboxPlayableComponent jukeboxPlayableComponent = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); +// jukeboxPlayableComponent.setSong(jukeboxSong); +//// jukeboxPlayableComponent.setShowInTooltip(showInTooltip); +// return jukeboxPlayableComponent(jukeboxPlayableComponent); +// } +// return this; +// } - Builder jukeboxPlayableComponent(JukeboxPlayableComponent jukeboxPlayableComponent) { - item.jukeboxPlayableComponent = jukeboxPlayableComponent; - return this; - } - - Builder enchantmentGlintOverride(boolean set, boolean override) { - item.hasEnchantmentGlintOverride = set; - item.enchantmentGlintOverride = override; - return this; - } +// Builder jukeboxPlayableComponent(JukeboxPlayableComponent jukeboxPlayableComponent) { +// item.jukeboxPlayableComponent = jukeboxPlayableComponent; +// return this; +// } +// +// Builder enchantmentGlintOverride(boolean set, boolean override) { +// item.hasEnchantmentGlintOverride = set; +// item.enchantmentGlintOverride = override; +// return this; +// } CustomItem build() { return item; } + + public Builder item(ItemStack item) { + this.item.item = item; + return this; + } } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 43ddad3..25c4085 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -1,23 +1,24 @@ package re.sylfa.itemcreator.items; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.kyori.adventure.key.Key; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.bukkit.inventory.ItemStack; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; + import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; - -import com.mojang.authlib.minecraft.client.ObjectMapper; - -import net.kyori.adventure.key.Key; -import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.Log; +import java.util.Objects; public class ItemReader { - - public static final ObjectMapper objectMapper = ObjectMapper.create(); + + static final ObjectMapper mapper = ItemCreator.getMapper(); public static List readAllItems() { File itemsFolder = new File(ItemCreator.getInstance().getDataFolder(), "items"); @@ -36,9 +37,9 @@ public static List readAllItems() { return true; } }) - .map(folder -> { + .flatMap(folder -> { String folderName = folder.getName(); - return Arrays.stream(folder.listFiles()) + return Arrays.stream(Objects.requireNonNull(folder.listFiles())) .filter(file -> { if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { Log.warn("%s is not an item file in %s", file.getName(), folderName); @@ -48,9 +49,8 @@ public static List readAllItems() { } }) .map(ItemReader::readItem) - .filter(file -> file != null); + .filter(Objects::nonNull); }) - .flatMap(a -> a) .toList(); } @@ -58,27 +58,56 @@ public static CustomItem readItem(File file) { String fileName = FilenameUtils.getBaseName(file.getPath()); String parentName = file.getParentFile().getName(); - if(parentName == "items") { + if("items".equals(parentName)) { Log.warn("%s item is not in a namespace", file.getName()); return null; } + if(!Key.parseableNamespace(parentName)) { + Log.error("%s namespace should only have characters from [a-z0-9_\\-.]"); + return null; + } + if(!Key.parseableValue(fileName)) { + Log.error("%s is not a valid item name", fileName); + } + + Key key = Key.key(parentName, fileName); +// try { try { - return readItemJson(readFile(file), parentName, fileName); + ItemStack item = mapper.readValue(file, ItemStack.class); + + + return CustomItem.builder(key) + .item(item) + .build(); } catch (IOException e) { - e.printStackTrace(); - return null; + throw new RuntimeException(e); } +// return readItemJson(readFile(file), parentName, fileName); +// return null; +// } catch (IOException e) { +// e.printStackTrace(); +// return null; +// } } public static CustomItem readItemJson(String config, String folderName, String fileName) { + + Key key = Key.key(folderName, fileName); Log.log("Reading item %s", key.asString()); - // CustomItem item = objectMapper.readValue(config, CustomItem.class); - CustomItem item = ItemCreator.getGson().fromJson(config, CustomItem.class); - item.key(key); - return item; + try { + CustomItem item = mapper.readValue(config, CustomItem.class); + Log.log(item.toString()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + +// CustomItem item = ItemCreator.getGson().fromJson(config, CustomItem.class); +// item.key(key); +// return item; + return null; } // public static CustomItem readItem(YamlConfiguration config, String folderName, String fileName) { diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java index cf438b0..24260f8 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java @@ -2,13 +2,13 @@ import java.util.Map; -import javax.annotation.Nullable; - import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import java.util.HashMap; import java.util.Arrays; +import java.util.Optional; + import net.kyori.adventure.key.Key; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; @@ -20,9 +20,8 @@ public void add(CustomItem... item) { Arrays.stream(item).forEach(i -> itemList.put(i.key(), i)); } - @Nullable - public CustomItem get(Key key) { - return itemList.getOrDefault(key, null); + public Optional get(Key key) { + return Optional.ofNullable(itemList.getOrDefault(key, null)); } public boolean has(Key key) { @@ -46,16 +45,19 @@ public ItemStack parse(Key key) { if(key.namespace().equals(Key.MINECRAFT_NAMESPACE)) { Material material = Material.matchMaterial(key.value()); - if (material != null) return new ItemStack(material); + if (material != null) { + return new ItemStack(material); + } } // it was not a Minecraft item, check if it's a custom one var itemRegistry = ItemCreator.getItemRegistry(); - if(itemRegistry.has(key)) { - return itemRegistry.get(key).asItemStack(); - }; - - Log.warn("Material not found: %s", key.asString()); - return null; + Optional item = itemRegistry.get(key); + if(item.isPresent()) { + return item.get().getItem(); + } else { + Log.warn("Material not found: %s", key.asString()); + return null; + } } } diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java new file mode 100644 index 0000000..21b3f02 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java @@ -0,0 +1,27 @@ +package re.sylfa.itemcreator.items.deserializers; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; + +import java.io.IOException; +import java.util.Optional; + +public class ComponentDeserializer extends StdDeserializer { + MiniMessage miniMessage = MiniMessage.miniMessage(); + + public ComponentDeserializer() { + super(Component.class); + } + + @Override + public Component deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + + return Optional.ofNullable(p.getValueAsString()) + .map(message -> miniMessage.deserialize(""+message+"")) + .orElse(Component.empty()); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java new file mode 100644 index 0000000..df62945 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java @@ -0,0 +1,76 @@ +package re.sylfa.itemcreator.items.deserializers; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.kyori.adventure.text.Component; +import net.minecraft.core.component.DataComponents; +import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import re.sylfa.itemcreator.ItemCreator; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ItemDeserializer extends StdDeserializer { + + ObjectMapper mapper = ItemCreator.getMapper(); + + public ItemDeserializer() { + super(ItemStack.class); + } + + @Override + public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { + JsonNode node = jsonParser.readValueAsTree(); + Material material = parseMaterial(node); + + + var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); + + // setMaxDamage(null) only resets the *patched* value, not the actual one + itemNms.remove(DataComponents.MAX_DAMAGE); + itemNms.remove(DataComponents.TOOL); + itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); + itemNms.remove(DataComponents.FOOD); + ItemStack item = itemNms.asBukkitCopy(); + + + parseItemName(node, item); + parseLore(node, item); + + return item; + } + + Material parseMaterial(JsonNode node) { + return mapper.convertValue(node.get("material"), Material.class); + } + + void parseLore(JsonNode node, ItemStack item) { + JsonNode loreNode = node.get("lore"); + if(loreNode != null && !loreNode.isEmpty()){ + List lore = new ArrayList<>(); + if(loreNode.isArray()) { + for(JsonNode line : loreNode) { + lore.add(mapper.convertValue(line, Component.class)); + } + } else { + lore.add(mapper.convertValue(loreNode, Component.class)); + } + item.editMeta(ItemMeta.class, meta -> meta.lore(lore)); + } + } + + void parseItemName(JsonNode node, ItemStack item) { + if(node.get("itemName").isTextual()) { + Component itemName = mapper.convertValue(node.get("itemName"), Component.class); + item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName)); + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java b/src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java new file mode 100644 index 0000000..dfbf59e --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java @@ -0,0 +1,23 @@ +package re.sylfa.itemcreator.items.deserializers; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; + +import java.io.IOException; +import java.util.Optional; + +public class MaterialDeserializer extends StdDeserializer { + public MaterialDeserializer() { + super(Material.class); + } + + @Override + public Material deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + return Optional.ofNullable(p.getValueAsString()) + .map(Material::matchMaterial) + .orElse(Material.STONE); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java b/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java new file mode 100644 index 0000000..dac275e --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java @@ -0,0 +1,57 @@ +package re.sylfa.itemcreator.parsers; + +import java.util.Map; +import java.util.Optional; + +import org.bukkit.NamespacedKey; +import org.bukkit.potion.PotionEffect; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import re.sylfa.itemcreator.util.Log; + +public class PotionEffectParser { + public static PotionEffect parse(Map rawPotionEffect) { + if(rawPotionEffect instanceof Map potionEffectMap && potionEffectMap.containsKey("type") && potionEffectMap.containsKey("duration") && potionEffectMap.containsKey("amplifier")) { + var rawType = potionEffectMap.get("type"); + var rawDuration = potionEffectMap.get("duration"); + var rawAmplifier = potionEffectMap.get("amplifier"); + var rawAmbient = potionEffectMap.get("ambient"); + var rawShowParticles = potionEffectMap.get("showParticles"); + var rawShowIcon = potionEffectMap.get("showIcon"); + + + if(rawType instanceof String parsedType && rawDuration instanceof Integer duration && rawAmplifier instanceof Integer amplifier) { + NamespacedKey key = NamespacedKey.minecraft(parsedType.toLowerCase()); + var type = Optional.ofNullable(RegistryAccess.registryAccess().getRegistry(RegistryKey.MOB_EFFECT).get(key)); + if(type.isEmpty()) { + Log.warn("Bad potion type: %s", parsedType); + + return null; + } + + PotionEffect potionEffect = new PotionEffect(type.get(), duration, amplifier); + if(rawAmbient instanceof Boolean ambient) { + potionEffect = potionEffect.withAmbient(ambient); + } else { + potionEffect = potionEffect.withAmbient(false); + } + + if(rawShowParticles instanceof Boolean showParticles) { + potionEffect = potionEffect.withParticles(showParticles); + } + if(rawShowIcon instanceof Boolean showIcon) { + potionEffect = potionEffect.withIcon(showIcon); + } + + return potionEffect; + + } else { + Log.warn("A potion effect should have at least a valid type, duration and amplifier"); + return null; + } + } else { + return null; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/util/Log.java b/src/main/java/re/sylfa/itemcreator/util/Log.java index f700bf5..c9c067b 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Log.java +++ b/src/main/java/re/sylfa/itemcreator/util/Log.java @@ -1,27 +1,32 @@ package re.sylfa.itemcreator.util; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -public class Log { - private static String PREFIX = "[ItemCreator] "; +public interface Log { + String PREFIX = "[ItemCreator] "; - public static void log(Component message){ + static void log(Component message){ Bukkit.getConsoleSender().sendMessage(Component.text(PREFIX).append(message)); } - public static void log(String message, Object... args){ + static void log(String message, Object... args){ Log.log(Component.text(String.format(message, args))); } - public static void warn(String message, Object... args){ + static void warn(String message, Object... args){ Log.warn(Component.text(String.format(message, args))); } - public static void warn(Component message){ + static void warn(Component message){ log(message.color(NamedTextColor.RED)); } + + static void error(Component message) { log(message.color(NamedTextColor.DARK_RED).decorate(TextDecoration.BOLD));} + + static void error(String message, Object... args) { Log.error(Component.text(String.format(message, args)));} } From bad90cd2e10acad59f2acb709e5643bc439adf0c Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:24:58 +0200 Subject: [PATCH 05/31] chore: update Minecraft to 1.21.8 + gradlew --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 ++- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 +++--- gradlew.bat | 4 ++-- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 164d0df..56d2ac2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,7 @@ repositories { } dependencies { - paperweight.paperDevBundle("1.21.5-R0.1-SNAPSHOT") + paperweight.paperDevBundle(libs.versions.paper) compileOnly(libs.brigadier) implementation(libs.commonsIo) implementation(libs.miniMessage) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 491536d..2c0781d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,8 @@ # This file was generated by the Gradle 'init' task. # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -paperweight = "2.0.0-beta.16" +paper = "1.21.8-R0.1-SNAPSHOT" +paperweight = "2.0.0-beta.17" brigadier = "1.0.18" runPaper = "2.3.1" lombok = "1.18.38" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 zcmXuKV_+Rz)3%+)Y~1X)v28cDZQE*`9qyPrXx!Mg8{4+s*nWFo&-eXbzt+q-bFO1% zb$T* z+;w-h{ce+s>j$K)apmK~8t5)PdZP3^U%(^I<0#3(!6T+vfBowN0RfQ&0iMAo055!% z04}dC>M#Z2#PO7#|Fj;cQ$sH}E-n7nQM_V}mtmG_)(me#+~0gf?s@gam)iLoR#sr( zrR9fU_ofhp5j-5SLDQP{O+SuE)l8x9_(9@h%eY-t47J-KX-1(`hh#A6_Xs+4(pHhy zuZ1YS9axk`aYwXuq;YN>rYv|U`&U67f=tinhAD$+=o+MWXkx_;qIat_CS1o*=cIxs zIgeoK0TiIa7t`r%%feL8VieY63-Aakfi~qlE`d;ZOn8hFZFX|i^taCw6xbNLb2sOS z?PIeS%PgD)?bPB&LaQDF{PbxHrJQME<^cU5b!Hir(x32zy{YzNzE%sx;w=!C z_(A>eZXkQ1w@ASPXc|CWMNDP1kFQuMO>|1X;SHQS8w<@D;5C@L(3r^8qbbm$nTp%P z&I3Ey+ja9;ZiMbopUNc2txS9$Jf8UGS3*}Y3??(vZYLfm($WlpUGEUgQ52v@AD<~Y z#|B=mpCPt3QR%gX*c^SX>9dEqck79JX+gVPH87~q0-T;ota!lQWdt3C-wY1Ud}!j8 z*2x5$^dsTkXj}%PNKs1YzwK$-gu*lxq<&ko(qrQ_na(82lQ$ z7^0Pgg@Shn!UKTD4R}yGxefP2{8sZ~QZY)cj*SF6AlvE;^5oK=S}FEK(9qHuq|Cm! zx6ILQBsRu(=t1NRTecirX3Iv$-BkLxn^Zk|sV3^MJ1YKJxm>A+nk*r5h=>wW*J|pB zgDS%&VgnF~(sw)beMXXQ8{ncKX;A;_VLcq}Bw1EJj~-AdA=1IGrNHEh+BtIcoV+Te z_sCtBdKv(0wjY{3#hg9nf!*dpV5s7ZvNYEciEp2Rd5P#UudfqXysHiXo`pt27R?Rk zOAWL-dsa+raNw9^2NLZ#Wc^xI=E5Gwz~_<&*jqz0-AVd;EAvnm^&4Ca9bGzM_%(n{>je5hGNjCpZJ%5#Z3&4}f3I1P!6?)d65 z-~d}g{g!&`LkFK9$)f9KB?`oO{a0VXFm1`W{w5bAIC5CsyOV=q-Q7Z8YSmyo;$T?K za96q@djtok=r#TdUkd#%`|QlBywo>ifG69&;k%Ahfic6drRP;K{V8ea_t2qbY48uYWlB3Hf6hnqsCO?kYFhV+{i> zo&AE+)$%ag^)ijm!~gU78tD%tB63b_tbv9gfWzS&$r@i4q|PM+!hS+o+DpKfnnSe{ zewFbI3Jc0?=Vz}3>KmVj$qTWkoUS8@k63XRP2m^e50x-5PU<4X!I#q(zj@EyT9K_E z9P%@Sy6Mq`xD<-E!-<3@MLp2Dq8`x}F?@}V6E#A9v6xm%@x1U3>OoFY{fX5qpxngY z+=2HbnEErBv~!yl%f`Eq2%&K%JTwgN1y@FZ#=ai+TFMFlG?UV{M1#%uCi#Knkb_h| z&ivG$>~NQ4Ou2-gy=8JdRe8`nJDsqYYs?)(LJkJ}NHOj|3gZxVQJWWp>+`H?8$$J5 z*_)+tlyII%x#dId3w(oXo`YEm^-|tFNNj-0rbEuUc2-=pZDk7fxWUlw;|@M9s1 zmK9*C)1Q?F5@NPUJOYOAe`GHnYB%G37_sg3dxAttqLs6Bro)4z ziy8j%C7KKDNL8r#Oj6!IHx|N(?%Zvo31y4;*L1%_KJh$v$6XhFkw*E|fEu9`or?JD_ z13X4g92;TZm0jA0!2R5qPD$W^U z`5XK|Y^27y_Q%D>wWGtF=K00-N0;=svka>o`(;~dOS(eT0gwsP{=Rq+-e2Ajq?D<)zww5V36u6^Ta8YT4cDaw} zfuGnhr_5?)D*1+*q<3tVhg(AsKhR1Di=nsJzt_si+)uac_7zx_pl#t(dh816IM zvToHR%D)$!Zj4Q^$s8A%HLRYa>q9dpbh=*kcF7nkM0RhMIOGq^7Tgn|Fvs)A% zznI7nlbWoA2=rHHbUZ4PJMXf{T$@>W1Tt4lb|Or4L;O!oFj8Op8KEE`^x^*VSJ`9~ z;Pe~{V3x*-2c|jBrvSV8s+*Y3VqFKa@Napr#JAd}4l7;sgn|Q#M!(<|IX1<)z!AC3 zv<5YpN58Fs4NYi|ndYcb=jVO6Ztpwd={@3Yp6orUYe6EG#s{qhX+L^7zMK+@cX1hh?gbp56>jX*_Z|2u9 zb*glt!xK>j!LyLnFtxs&1SLkyiL%xbMqgxywI-U*XV%%qwa5oiufFerY!wn*GgMq` zZ6mFf8MukDPHVaCQk#oyg^dhl*9p@Jc+4Q9+0iv?{}=}+&=>n+q{o z#rEZ<&Ku65y+1eRHwcl3G7bR`e{&~^fGg|0))$uW?B@;_sWSls!ctnjH6ykmM8WJx};hvdXZ>YKLS($5`yBK38HULv}&PKRo9k zdFzj>`CDIUbq8GxeIJ?8=61G-XO?7dYZ;xqtlG?qr`wzbh7YyaD=>eup7bVH`q*N5 z)0&n)!*wW$G<3A&l$vJ^Z-%1^NF$n3iPgqr6Yn_SsAsFQw?9fj z&AvH|_-6zethC3^$mLF7mF$mTKT<_$kbV6jMK0f0UonRN_cY?yM6v&IosO?RN=h z{IqdUJvZd#@5qsr_1xVnaRr`ba-7MyU4<_XjIbr$PmPBYO6rLrxC`|5MN zD8ae4rTxau=7125zw|TQsJpqm`~hLs@w_iUd%eMY6IR9{(?;$f^?`&l?U%JfX%JyV z$IdA`V)5CkvPA0yljj4!Ja&Hjx`zIkg_ceQ;4)vhoyBeW$3D<_LDR~M-DPzQQ?&!L*PUNb^moIz|QXB=S z9^9NnEpF+>_Oh6+Xr55ZLJ7`V=H}@D<70NiNGH{~^QE-U)*Sg@O}M|%{Rcpn z{0nD@D%@8!dE*mndd2g!-q9;)jb=IUED<(Pxh`9B>V3z#f>82~&CVZASC?|;C-VKy zJU35T|3jd(p8F|#n@T~Wh2l1yURI=LC>Uj_!8i7-DE_IaSKIMAx`WMEq8kN%8sAx% zOQs~R1v12(=_ghVxzylsYZum-%8QmjM3-s2V!jY|w#ccP)}OSW?MWhNu@o-t0eTg{ zyy`}x+}GObZC(k>-upb2C6#S*NOfWbKEyReP%gay8MT!pJpsx4jwCu%>7%sY}1L6Vybj_P+;yP`YS92 z^o_G!Gr_NP!ixe7d&82H&achfi83L;le3Fs?u%E*xbeOKkJr7mp=)RXjZF;h*hR<= zP_cs1hjc}0JlHal=enmG&G8wsn%Sm$5Wcgs=Zc}}A%3i6_<4k_`-$k2E5f6QV{a$V zg3VZO36o^w5q`q2ASwJw#?n7pBJyGt3R<`Sd8d|52=h&`|CPq&1Cz&42rRCHNjDZL z$}Y*L+#N;!K2Ov){~fmQM8hVYzj3H@{yS>?q3QhhDHWfNAJ#q@qko|rhlaGG4Qrvh zmHpmg&7YvgRuI|i78-{)|wFx(R^_ z{ag(}Kbbbx=UW42sAu}kg3yB#96dJlOB{+or<(51ylVwpXII7Hrlztq!pefQ?6pQhqSb76y=sQx zOC-swAJaqnL_ok{74u_IHojFk;RSSFfjdLrfqq{syUxA$Ld6D2#TMX(Phf~dvSuuX zmN2xzjwZxWHmbvK2M#OhE#{`urOzs=>%ku}nxymK-dB~smas?Z(YM^>x#K)M@?<&L zeagMnj!XK4=Mid$NvJ+JfSjvc`4rX9mTo^+iFs0q7ntZ{gfU3oSAbK_yzW3WA^`6x zWgPSLXlEVvh!G^fOzZ-O{C_v;V6=;DE+ZqRT4mbCq}xeQ0o z98Cho%25r#!cT_ozTd~FK^@AB3OnrAAEDI4==}#I_v}iw0nhA{y99mFRG*1kxFkZP z+are- z8D|3WoYE>s0<=h)^)0>^up+nPeu}Sv-A($6t3AUedFczOLn;NW5_xM0tMvvrOSZ}) zA2YG1m4GxLAHZ5k>%}pHYtf-caXMGcYmH8ZPLX9VCew0;@Pi-8zkH^#}Cu$%FmKJb=!)Twj!PgBmY0+>VUsyyT}Jy>vMt zo<^5lmPo5Jt-=)z2-F{2{jB{CpW2JDj%~JnP*rq^=(okNQpH=}#{kqMUw{&=e-5;G z!FwJVQTDS7YGL&|=vJ+xhg{dMika2m2A#l@$PazLQ<6$GLC+>4B37`4aW3&MgENJ% z#*tOQsg{>zmcuSgU?peLA}!Rlu&K3LTc@drSBaI?91dK75;_`(V`NHjkMj``jwjJx zcm_!liUxn=^!~0|#{g2#AuX9%;GTBq&k+Jz!~Cc+r?S}y=Q1okG0PRIi3C3wgP8F| zO2jcmnVbGXp*Mu&e#a9Q5a}w7$sITx@)8b}sh(v9#V(H$3GLHF@k!Wh+)kNueq;+r zFtj+^b1TQe?R#Y8{m!7~e6%83hbPKoizd2LIg3yS5=X2HE^l4_|(2q#LB zeNv&njrS$?=zzG?0Min#kY+3A)H1uMfogMYSm|vT%3i<_d9X&~N*ZCL4iB@YaJuo; zq}-;EGx~T43kq-UHmTn!@sc z3bwcs$rp?~73h*uZl_ysD*WK3_PS1G3N^t3U=KoRm_Gz@C?M>+x9HRMk(cA4m&L`! z=Lb~4*9zt*SHJgsAMAcTy*!1W^B>4T_doWvNw7UwmyA=Wq&kE{*GVHp9Yk5goUO;k zVb_3ARrFPG;&>Jv@P&`z%}t!*M|2127pm{S)gs~f_ID^lOH@nIW9DgU$=FjqNW0pv z&GYdoxe@)RAWWx^j|$N}sj*p)_bFpk`Y=NilvsI(>!Z&KBo&I+wb*kM5Vvkkr#;q< z3CobbF+GJ#MxL?rMldP0@XiC~yQCR57=wW_<$j!SY*$5J+^v{Pn!1{&@R-lHCiK8@ z&O=XQ=V?hjM;h&qCitHmHKJ_$=`v%;jixnQrve^x9{ykWs(;!Q9mlr#{VYVE93oaW z&z+vBD}!tBghkriZy7gX7xJp8c}ajR4;JDu^0#RdQo2itM^~uc==~eBgwx5-m7vLj zP)vE#k%~*N$bT#^>(C1sohq+DwAC{U*z(D)qjgghKKSy#$dPih`R09rfbfI-FLE!` zn!tg71Wr(D7ZV*4R@GqG&7)2K*Zc6_CMJoGu#Yc>9D#{eyZ>u-mrWG@4Hk(je3lnH zu9qvXdq+!`5R1mlzWjV^jvaHl>-^Z+g^s5dy49yem$0$>341=EGuOY=W5PCFBTbNN^19iIQ57C3KcV}z~z#Rvngs#j;g2gswC(TLWlViYW}tB5T#g4 z%vDUYTo1@+&zE&`P%fXc^@prE5z;E@;; zKtpEFYftJq-c0sD6lKYoEQ;O1X4uFZZ;3gdgfAKqIc=Dj6>unXAdM}DD*@a5LHk~o zyJjW@aK;XG%qr<)7Rqh7NdUpnTR6jc;6{FKcK_v_#h{IO{mez>^^70DAWB5whqq!J zevvLUotE;I?IWWf!ieJ-Hx`TqY5)ND>K0NCb7IW40Jk*J* z^#m%kIA~Go2=R|y5zM|*ehJxyuX;lOQZkArKVbQV(XmidUH|8U^q`wP(7%F}=uG}U z2~&~CLebE`c%SCdeU(l&hryL~+Y)6I^d@|||6F15IAGo`G+CdVf zc+!EycZnQH)OBE zyTd8k{(_v9d2}osA$*>Q>Q&OB(7ShxA$}p8ChVnYlXl5My$HlVx@ATprrj0}6)ycK zcQy#bwOms1CnS+xd26}k?J;WI{HR_U+1T^I!$B^S=pJkT705QaMF88VJp!s%`?y9z8f$&Xw(A}3u_(n5G{!)yH&zN)S?c1$SZlo>XieJ zyEFa>_p9B*cY){ct8=dq>uQTf# zd4vB4)(ebwQHlSAu}(6GCe28H32pz^}l%Zqs;Yl|B=l2d9HrCcUf%wxLYs4CBqJ#{gz*u6V$>?9IT@uSf~2Rgk6CNw;C21ZbNkm>ZTc@2zeOSXVE^>i5!2>t%!1cI z{FZA`*o4=dTDG3&{v$3xVr%g;3d(!SFJU}w6x_Re(ohlni)I54Wg{t zWLK{A(}qEIH@pamgtr3serA{THlp_IR(gt0CFguk={|Ochh10)7UV4DcnO7fvL<=x z^WCMg_TI?U8(loaUnAe+Nc9I1JIO#_C`=kJG(&wy%Cr9vRFcY9^8{A3A>GuSW~Zk( zMA#t~0Dw?;3^Ue|lhSp4p%YvYmw-&3ey3}+{6Uhz?l1D|6nYNok6?4N_C!OSR=QtS z2X&QtWlkZshPo#-dXBOlSqh3D;#*_`hyohR>vl$W+QC>HPOs0zwHKN`?zIKqCTw&w&NUGNS|abulHe{D+{q z`WvLw?C4K97cd}6V6f2NtfIAO;=c>qi^+y4#oMjK?5Hy9$Tg1#S~Cxoo-Zdpnt2kG^n}`9)Df-Spvx&Oi+6xXT=N*0l|d`p!ZU ziQo9$y}PYIF~Zqh^?6QZ8YS*JtD^gynifSLMlVYRhBi*f-mJFS<>l%5sp5$V$p*X9?V-0r4bKYvo3n@XkCm4vO-_v? zOsLkR?)>ogb>Ys*m^2>*6%Db0!J?Qvpyd+ODlbslPci9r#W>d~%vcU7J_V;#Um1+` zG0>Q$TrOLUF0%a3g=PaCdQVoUUWXgk>($39-P;tusnMlJ=Dz}#S|E== zl6b3bbYaYguw3Bpv|O(YR2aBk?(jo+QqN*^6f0x+to-@2uj!nu6X{qLK>*PxM!i0C zZwrQ}prOw6Ghz?ApvM`!L3Dzc@6mp<2hO0y{_`lqtt!FcUmBG+PBwl?>0Mwu)Ey{L zU;A{ywkT}jCZpPKH4`_o0$#4*^L7=29%)~!L4*czG!bAva#7ZCDR|6@lBE&cyy5eE zlKHwzv7R9gKZTF<8}3*8uVtI)!HE%AZRD-iW!AJI7oY43@9Z$0^MO@Egj1c?o(BwF ziz1|k#WOgAG?^r1 z>+p=DK?cA-RLIvcdmwq$q?R;ina0SPj@;Mus}W_V2xHnYhOq~=sxzA`yTUOsJ`8`VOSTE=IZ!x`cZYqHbgPijF>J>N7( zqbNsHK50vkB1NI52gyb^PflpU0DRw{&v7Y}Hy2>pV@W2f1EOd2j;H?|WiV%2?Dk7u zS(NrEUDl81<}yY9J#OCwM)N?x&PB-%1{oD*`_ZLiBJ=16uR{n+Lk~!t(&9U#>ZfVd8Iqn&idGd>uo?L@sjm>c|Lk z12d3Y>N9U`342@xaHl&Q@oE5V-f$s`04q983f0#m_WF=X_A89W8C#{uCdTNUZ+))$ zakPyNU)?MDayCKxWh0(-v~1rd8FxocW=Dc6B1%N4^SgQj$?ZMoAMQ-35)IMgf&)M?c@}4QG7=DTq{nHc7yp=CZ z1dh~VkK%OTr23U1mJ*a-DxX0Psvh_13t^YcPl9t?_^$pPEhhwGp}s~f=GFR;4@;@f z@B;R1U6Df?yl#Y=BgYTlP&<|8K27||rx_?{s|L);GM3^{Nn8HZp zFqxiG6s3Nb;PW3O=u;(-o(*q!^2i)jHY%N@;O5Hder~_@$zh4xG#-7?#S^-&M~yc} zh5Y=ltLBnTzt;Y%YNqi2d1M1LOz?MJbZ|Nc6>x19&l_S*2Rgk$DhaP7Y-C)4_uPzf zQm)OY)$AFfE1(0SxkbbN4}CHnlU`RqYFGIE7S9ipx_Q0vkE5JRq4Uc%zV7$?y(x$y zV^)5zwjH~+4?xN z9s@x~w`C_cS}khfI14K4Xgn^iuBxkd^u}3cY=VZI@-8iWHolPtt?JD5lZ1V=@g6yR zj0>bd7Z(dw+@)v#r!xpZaAxgT?4Ton(h`0}fkfF!ZDSu{f*r#{ZRp^oOrO3iB|Fa- z;|+PpW5JKZxJ-kjHf`-7ohmnO=a)Xl9lhI8&$)g6R#6PBIN$QSC8kT=4zj?w&=`!qjkCvvz;ypOfR7P)w^ z-7LFhXd6GLrFa_vGLwR5MRvcV*(r!NhQ@}T-ikBGy!fHaiePD$iA{|Q1$kct2`qHz z6nAyERuqvM6i2^?g@w7W2LLr~3s?pBDk6ce8@CxV;b%4%-rXK-GOk+($sSNK;_FBku zm89B}tpzL-x{dPS-IAjwyL*t7N%7~2E)9OsWJJWHc|}BNa5Xwdx(j7i7AmZhs?#zi z5{y$uQdx?O8x3>+5MR05HwUa-YZa*|UVLOb`T)KHk|~Gmwx8MfBUtM|afuM$0wb7m zR+_lU9=W~Y$uNlxt&(@&1;6t!r69A|W%;k3-%SzLlBzc0 z`b?Jmo`8{LI=d|I3JDAa|iK*D6=I_3q?%xFSLg1 zI^!pA=K}l1joBBj8aa8XHp^;Lf`9xNa&Cv+twW&$_HAwZfHrVcNUrRccn_ z1+L!z$k@LK28nc1VB|Fbwm$wO;B~yEdww1EUn|s&{-Tu;@$d94BLL(OQYx|aCa|&2WPT{qJzbNU!ep>j){o5=6le6 z>~Amqs+mCuOR2)aB!#sK5fuui7LsO!Qzl)lz?Lm!QoQFWbNIkfdkrn|)YbSu8WwxZ zO{}a~wE2Cu)`a3X+KI#LHm(Mi+}bOB6@N~H2}Y)e*}w8_z^Sx`c?CWvu*2{K#yqGo zx!Cu*+8&tdw!eiKqZIQlJg5Cb^hZ^Zh~Mb0l(4m4hc1mP&>oTdt7eS-bEz8mU~oObme{^%56|ou~EPOSFBa7VpUZC z0gVc<@IUeo~q)&?o zU@=bz-qfWm)&0Qn@W_fc9{wx={&-#8>0xHJ-+Ijl#P&1qB-%*KUU*DCPkKCLzF*#t z0U_vrk1(&Vwy6Vm8@#Th3J5J%5ZWd)G0mifB3onY8dA&%g6Hir5gqMH|hnEBL0VVvl~aJjdljF$-X@a zMg=J-bI?2LGw-8mHVF7Jbsk1K4LgWi7U>~QovGT2*t^U&XF#iDs_E$~G+t;U;tZn_@73Y6x>vU%x` z6?l`$@U4JYYe#|GcI^f+rsy|MdB|`PQunKSKkja4IGtj9G6buN&ZSnYi|ieaf{k5q z@ABM@!S(A6Y}Sv~YJcB;9JeqsM|-fPIZZfOgc*FSzIpEdT=YYT(R(z{(~X&x%6ZM1 zY0(|PepBl4dK*@9n6@`rUMd)K^^0!^?U-1rrB*b?LEZe<5taFp!NoC^lc>}YUy?5FjT9tFmC+%%DYNa+L zWr)zMB%y_6L{S%;dk6bJPO!wmT=wPPK1b$%+ffWcO8;2T+7C28T?{!96{%d`0G~j3 z)6g<%$dC{vAKJ22nY)fnxlD>P_Xb&@>wrG+ZpfQ%RX=R2kd@bH3N*M8=BO zi|Z$Z5e`0NcU5&aN_DST8O@4v3vroq3t<_5hBX;d)*AJgWPb~p=qx4}^Ms6pgyY`) zu z^|u7XSP^~b1)*61r(}zd!JOny@$KviSp>L|jSR!u*1IgKwId5jmAi2`qe%u+XCTwU z;a62_a~Z}TqDJ?6lje5hblv1f1(6U@kWpc)z|&nRBV*UIieQR{Rru*|$L2SzxtL&| z7abeg@xniYhexYoN6zxY{nI^*xKW0Gz8D~}tE>O4iCkpWn8wt4?S`(Ftv?<8vIvbw z(FFd5`p4~#m<(3uv2+pv7uVC$R(iZuhnxFEY{o}BxPg2nYK zzOjuMR`}t3{8z#zfLXy||4JCt|1nv5VFjS#|JEhRLI>(-;Rh~J7gK{as*K1{IJ%7F zoZnXx&Y54ABfp9q!HDWAJlvFFdSC9}J*llUYXFDN8meEa<0}s z8M~X?%iKLB$*-a}G_$rTh;U{M0vc<}N#PVAE1vQdL#9a-`uH3*cbJZ~u9ag-fny$i z8aCs;3E85mgVK&vWM6}FH9o^WI#G!=%YOB#gT`1^VttnSVf4$YKja@-;zARB-`7v< z*imICw^KX73Gq-go6e?w^os0U0HSxH>60JLWhFbDeGT&Z$d3;9NWy;WvICuoZaKMi z=UvTpLDrtssbhiK&A3EuWf6!)>$sUlRcn5?Pk^OCtvApB=6suN42uKN-Xs7u7EjXh zG|>-1Rp>w1KB%sI*b5dGwFbuHNN=|})sR(dekHBL=>I~l@Nao%H=w0q==`3$zP>!I zmgoBoi7ylm<9Fw6s3&T%wJ%>VQmx(H)!iq?ABhdSzitwHlFNGcBW4sc&9DmTThb^qz`diS`xzQT# zhZff!yj2#rS>yfS5?}{inV5BfcZw zF5uh!Z8b#76;GcBDp7^zWtzQ%J;D}es(iWWWQNA{SvyhO`X8oyNL?j8Afn=x(zHct z7)3c%RKTPAyKS0gwVpGLqR2_%EowBpk>rW}MFfsR9>#2aOL!HKZtg$bAOe+#;;w?3*If zQk=HPWSlX7cF?h1PVE1D>LL{K&Ze4d!#Y2qN+^N-`~RG(O^Gjg~EsZbW^ipD9*+uf$K4Cq=H zxnYj(#+^eUa_1nRDkJJH|9$VB>+n4c)jji1MPz$dV4Ojf;)iYjgw#m+4puPdwgLSj zubNnwfz=z1DqFmy@X!!7D}kTo6yBjVFYT`CisjAgjS^cO%|(B2vzWb5PcrnxTK4xu zm?ZZkCy>+)-K8*)fo5JCWa@}^R!iI}a6OA*S&ibX6V zKk0=}K_M7m$#QEMW=_j=4tDXgH{_l5u?oFF?CXKmk73#~&>ha8CH{7jDKT2WoJ&sW zD1wk_C4Q6m{-YEWeAg*gP5`2Yl>4S@DAbob$M?&Gk2@2%+H*H2wu_)XL3fn{D8ljl zh41$!&_(kR($}4zJj3?zH-A0f2$4;9tH|N9XT48P;?coFH~9`z4S_35{xiUZC4&-3 zo3Yt|ee&RI&qBF zW$mPrwbqtHO$6De21%1=8zUX5=uMV*>#k-H>d5vP zz8OPyI|HLGKn`U2i>k8-dUX}5DJ(|Oy>)cK%QOwU>>~+Wn?bp?yFpx?yE;9q{;DTa$CFGK2S&xDNk$24GuzOgK{np ztsuRfjYmLjvhn$}jK3F_+!AtM`LVw=u&FUIGIU6>0@nqZq~REsb}_1w!VB5-wbS#J zYPBNKKJcnu^LTORcjX|sa8KU?rH5RRhfJ&l7@AtLVi|n8R7-?$+OVx!2BrQCD8{a)Kc#rtcWIC2(YYu=0edjgP9sFpp0=(eKUE2*>jc+n@q? zKTY!?h-S?Ms1kNuRAjowlnTQZF=#1S3XPx<()Wc1>r=QN?#W;6OL z2|Y0fxO0y=?Qi#F4?$+-Qpt&J>-JT?;d6ITN&7R`s4l(v17J7rOD3#Mu@anT`A z88>nZmkgV5o2{_IQ^TOFu9g}ImZrc~3yltx&sdaLvM=bAFpUK=XGx*;5U2#%A{^-G zEpT(GF(}NVJNzn$I*!S`&mA<1j#FEw4`lJ|^Ii?VA+!l%tC)`Q6kS&`LD*!rp)SSZ z!fOJa=BWFG0rWJE<~c2SnT{ykD23&sE?h7iTM20!s3!XMY*WJK_oA3FzU zScKW==wTvjelr=iu2>(0OLprW-Pv$m4wZ7v>;gB4M5m0(gOK>_@aIy}t&Y`H8crZ% zbo1L-*2^hdvzq`~_{<=PT=3jZ#UgMI*bQbOCzf~T53X2F9_QJ+KHwwQCpU%g4AGP z7i4m>KYOFyVXw`L5P#h};Q56X@OHZ-P-1qabm)G~GS>9sP0ToSI#43Q5iDCjG6r<1 zyJZa^U&>SXTW+bvJNB5oHW0xNpCGimZgaFJSb^??Uz1|jbXP-h<65N`CgZYX8jM3^ zSJ2tNSxr8>9)`mMi8nHw1aDz_?+ZRuMO@tou|Q9z11zdD#ka!jZfeXi(bGK&_vVQ^ z?b#6fYLRy70Mb9>3LcE``^rMcoxj~!hvBT%&cQK#L#nhF)C)iw(B$hY1fwak15v#J z-<0Kg=Zh1uk_^yGnO~&Hl|4?14*DFz9!$a(EAbT!5(<}0xUlYlC%`_JfofaWqfWNEfhlbLb2Ds@#m_oKXUJ0 zdSUbdO-BOnM!b2U2o3t3AQ&HGTzjL}LBTpwM2|gf3<(USB~4unKD6^_G>?@N%R2V zE+a}P6(vB@x|W>|ol!d5vws)e>m=0+2Y~#n1%kb=NXlT+^$#v9N z0Lt8wQ#?o)_j$PRavtm~z!aRPQ85^H^}u0bjlfDm(!3xG(oMQY?(DW6m1QdXq-PG; z7jW?rNj(vW&SZZ>B^q=2mU!8NLql4|nTI;pSkw9gbip(A^U<9DVj%Sjd-T0)ldwku z!O)$tFvVGRJnSI!t*v+U;QlSXfMu%J>v5B@Rq<`V$DQ>YTCkc=so?hUx&dda4;A1r z>~5vZ0E0M|B&lv|71*mTuRX`GB3G>9RzF7}+2HIgGrV-?p|bN%&4si|xxb+z1S}F2 zOBQ37uO?>1n_T3UF8nYp?uWnU&+53X|N94hR8WunjZ{}VH({S=x7sRbdLq7vyftJ? z2@;dF{)x|0nI%sYQ|%pe)%r zxP>}6S+ylPH{St~1KGov%?}z^A&&&(B(s+ngv{wKZ_L(*D^+nzoie`$NZ_*#zQ@&T zeLY@LZ5;akVZ}L=Qc=fIphsO^5%YJ0FQWW3*3|ahxk16yr=ZgTqunNMFFko^CZVSh zlk<_(ZLf{~ks&04%zz`tNla=O_`5r6W>d-%mdkEryHLIgIZyrq88$=4=Im4xR_}|) zZ!?V3+6QZ7$+wYJ=>nqKQ2L_gKw%=9`ds2Mdo6`avM-uO$tdP}7Jandkx0}XQhkn# zzq9uFBxvJ^#%sW$s)6J+j5 zXmAN{4mTo60nJnc2C6XtOBsVbJYc5&a0nZ|e?0yj+kThaCezk^Cm!F<|A=cu`uO@u zMai;5H6<@WD$n?-1{?Pzr2mF?F||EI+58#(N9dB2U*+$o$gl7(T>0jTu!?94mCA7^eb%}7cOyZN?nfVx+L$x~x>^tyJj$vmKZOXBKkU?mdopygE`0+rPi zx3F#q)PBC|6M{n@2|m%_24@G{?ql$@S=PPaEh1sG9v zxo35;K!!nAr&^P|c$6z+&vUa@eX|Uw&nednN1SCQSFNx={#kvzFb``4ixf3m zIY=2lKDmS2WGQx#gfP0BOAD4i?UoNdWtRz&Q=#>Y75@;X*z^@rxbLVa`YnIz{oaTE zNGmThd0`N_?*0!a>=f<^TOdF{&|-km!E9iB4IUs0KsvY|y6}%EN>L%XAjjOs+WGAJ z=wAmEmK)JGoI&Uq$`1%&(sh$n^lmT{o9pDd>t(CQ;o9Sr;gFtdZ>-qZg7jbc*P~uh_&U$wOO;{P3h!F3|a}dH-WoGGsXGBvB2c7p<>_CnJAYP}_#gD0t)$ z$Is_In%83bCJkJDij^-Lbnh)JKexs8f3E|dDy=BUEES;}7{*+oxV&iNODhNv#y<$} z=-mY})V@*#j#N6^A*B940E$3$zfmk;3ReX3DO;=d*_(!|f4FL$#0mL1ToWidl)O|S z_mi9mELAQ#S-D7+a2+=an87R;9t|U~1&sgF{`AZ#ZsOL+=sb67R?kPP;SQrDJP#F^ zsr<9}0#5FYl#3;3$mekh_XV=g`LVN$408Oz1ZU^F@kv7gMcyAWTE+yQfcY<&di4?0 z09J)>xHkZoQg!{E*RBSy?JCKOX7n%2$6 z-dzz8T10-8&ZG00yi<2%x`4@L8oj$ZXP|WgZ7E%-(h>@kqIJqt!{ou4J@Anf#HcEw zPSv)TmeUHAmeK2Am3|mkp+~W?)6eVg;c7e2H48x zBw;iPnvFX(a}Y+nn8^W#;6K4qA&N3hg$HYE=n|Dy)1^$6Gxud`0!yZ0d*p;(03ud^ zy^hvb&{_%?^-|c8>2fAn_!5YCX`?Ov6`*x_BAqZdP7`m!E4|c0ttvHBo2}NJT1HQs ze_rYk1e$5HO|)A}>0a7uufbmK{SDV?ndJ&?hXXVWWefy|nb5Neb%C#pK9tl%P-U{v z%DOV=mf@tF5qHo|q4_JBR-PLXOPn6TUrQ#9e83Sw*iIv zU^kn1C|EKWK_mS%Ah;Pks|+@@OxM8{T4o@Zf(mvI z55b=nM5d)6kW5m_Lx%`#@%0J~At8s1=`iJf)}P0CE6_pa-@`H5WIHbP7t4>QJLNX9vAkd8^)UWbAP6$@LZXWxAVbOYkgCYh!Pi4lzTy1%B>Pf9ZYnAH}3- z*{;*nGg_ZWZvV-oB*dF(WQ0^x71UW+hk8Cp_g2sc=tD&+CHpenk8FnaqFX;|TH%e* z9ifj@(1+=xs1s>xxwM`XyvIu)rw0VwCz$GAQ(yL@$J9)4{viA{r49G#c+Z$S3LaiI z8H1fq(Zeb|M4x7oLLr4te=>z$^SG9N2w2ERGL4D=I9HuNqS6>W3ax}f`>ts|P^Zvm z@RHI@6xXbm9v9ry(J7RMY_2a`aPR71XW4B1S$a}He-4?~NS8>v_Z&;WYl>KnqBJ7-hpw*<(4p-DB;Erm4B)LPDS{#kCnL(dCt zzl#E4aVwa$czprcYdPwIDCcme_C!|1U))PSuuI$zk*W(Ap#uWp$Ho58;-{sE*^$YJ zfcvRRKNF?1B4(sbe>9@m?fS5nel8lSJLrFy&YLbuYc7$Di~9RZ6dwe@uT*+bv?gxR zf2UDHLuJLEg$yM9E&WcA_+R7?)37(a^as(%yhwk9vCtzREf&@5r9ab0gl1l{v<@{6 zC3O?M!(VOl{tcWYFh zcWyW`&qG3pOe@HR0(&Pf@bG-DEH=)i05VspTrF}nH!FPJEICoc3S)q%V+;_aFop)l zP;Po#SxD2ff0q4{T+T}wqs1MJ(W0uHR%OPB;l?2?$s`KN)CwvpIWi|N=M^e1V@wxw zhcbE=o-@%8PA~qV;Cea8wH_!IqWp_Sb&NfdNz}9rhH)r2Br^t) zMeQA%TY4kA4{q7j(jMtJ*xS>w>)_TMT^(L-L2JjGxOJj&ZV-)ggVi{5yFFtT>@y74 zJf{=@f2D8cEh09yg6#A&72XCLgRGuD?B$3Jh}mU9;ruBh4ewxD7AzgZW*I&BN(>mh ziz!$}F_R7^NNhzIC6VZOw|xa*NB`8Izi`@_wbT62%UAIpm3#SWG=pW%ix>j~;()!P z=|~#* zs~lrgJ~te{KY{96l8>ex)n>uuGMb%`c#snwpktC*Tn4EfgILng;xZ@8J7YPjGNU7z ziy8fhkvX(Gk4lucz zopwj%<+s`80do~2D`Ae3vs%C2n@KP&f1Tw*W`gvc{0^aDj8k(=qot>B`xmPR?nWM%F_Tp@8f$^zMC-x zxq5eR4y{vI3_c*+I&2E>TUd_fzE&@Pkna^rKrwaahT_Qipb*^GDr(jJ{9!?Jf23IL z(A^If6~w*; z?}1Z(f$4(T18(_hnK5l-&KgXmo>nd-3e?K(mCc5>6~3tQ)BGjdE37LV)Q^&pwQ#S) z&+u1NlKHDJYC|%1Na3%+nyEu^jPYK6&d&RoKPnRF@-yfpj11b3Z`tb@e>%>eq_``W zHjyW%v=QIIjMQf2l5wjwh-GwmTwut$YYW7S)B^oRCLq)v5C#Y+jB#TgxNhmo8p)ig z+m?O7x>V%vtNgs^JCwARHbhpo8tiRe{t^FJ)aIYKNc@@Cy2(NO%_oXe2h_a_mDEVt zmb7j{8H0tCIim0{RsMyjf5xg%)u5J6>nIZ!1*crg#_ZLsWwQbZRQGHCjX?b^(~`4- z%8a=}HZ#K!NGa0IY^23L=>CEKsPgamPfQ#BAATw`rjrHMokCmE$m&;$>$>FdWOl&m z)`l3}takOU{5O^V!Y`N18@mT#Hk8i4BUNORx;`YLf13b*mCvaBe-8<>i!%lf^-2;U z9Xu^Lie6DxK3T%#A{V~ncqJJ#j^vgU*fE*tQzR9Izl^818it9apbd#{E7lZ_VRf}E zc~xnS$S$5Fa)vkpeqLJ|acM0jlw*p5vTxcoxin9j54VyQ6lcuBR|hLNBB)YOqvR9U z!GXe8h=^BOD85uIf0M*0GA*2n7=9$tiDqrej<}AS5rg&?cv&o6pi1XUOT5%!|GH4f zvaj?*$t>7b&`TGoQk8_MWDe?v2r}Dt(=V&+RUEinS|JRG@uWH{KKj7Hj+!Oxo*$h3 zJSiyE3UmxBOJT8wLQ9;~a_QJ0+H$+Y7xq%5dSM}87BbO_f7fWu3%N;ZkQ#*^Fy;8l z+=R>08U>@C^*y3XHwO(!x~UB1eKROeJu9R4i#yRqn*t8KOlnf8LRwpLV^InvOY4y& z6Y0aoAta#nWk$@|ua--OGHHW!xhjPv3`wq-h()h-g$Rf$X%kb&Wa>o&%jl;Juf;h@YL`0DJV={S3<~|Q zxVKlNt>PnLnaimuw=2>%bOF+Krp5q#4}8Z1N3?_qAS?S%)arm{Ww3y0Sj8X=>X^3N zqTq|)7_lk>iEJQee_T8ouuaPZ z`ZGo<5HsR>A7m?9YOlD%ISXt11#1V2EoPx>=owC%+R@3XD;+F;=(T8c8;0RJ zTsm&wf4E6n@v_B&nSvZcHW#06QG>Wc4M@NZjXq_R6tyGE%uPgmQ2BjdC;x_^K7e<&Sro+Qon7}Z6ij>=e%vr_NLQ=+o& zBpJok>#>>@t9yzoIjkHJE78hf09L;KB)w^jj*Zi;(XexzZjXje(A)F$&QZE+l#Y+n z`=Vi2$nPAb_di1SF@@cJ_apQ%rsI6t?-IX1$@BzBhvht-IL`O`<;uJelNOBA7;pvZ zfB49mXR!WQo}M^PexS)v&gcE|!8|>kr>}-xBWE7K{@1Mi2C+ZCIZxkg5`fhJ{k9ES z?Q&jg{rY^Kz9*250O|V{Qa~U%CqezPdlGEt!}O!OX%T>bVgb8HsA8Oc79FMkJ{1BQ zAj1lz_A7b%#c`?Pf$=T5(=0B&}8~QNxNwRw*HCGxKs7 zAbuqb0wZTm!A@E!voDKNVzcs90B98$d1mpu$?pVH>>OjYdz|h7=c8OvnalIse-rG> z^TJ7MQ)h{-eY_~oi=$1-J+wg3^YM~AU$kfB%yWKA6u<1KR)jRN^V))`t?f_yozaju za%E*q=!xg(Q{=;$gM(CgBtI%caf_(Rsq{@aD+#S}=pC z86ka~*GGN4VU#aFW&hkLem=}?e|vn~F~*%Z>oir1(1J)V;P~B;pF%#~KE~a%?9Q`R zT%aOCGZYoCbw1uX$~|Kog$!cB?q~!dDf0Qo*L&^G+IB- z%c7$kALW4)e5h-jQveUupWrMkF~&y@j`9uT{Dx>3B5#~;1W8xjD8D&0f6BK2KH7bP zZxi%s6BzdKTl4((Xp?-8aO}B$ceSl^VLKn+QQT7@lRQFm{BB3JY*{801(`8^XP)m0 zD?Wbj7{5On_W1Gh19`qL&mS4*kHL?eO-i0WS*?JlPt9MR=TBSiCFAu3oJ*WezdvZZ zSy&eKQ%>+G2tl=09#H+Rf3Rl+Zi1CZ#ESIpy09nYSNtA9DI^G;;Ll9Z5|JT@L8pS6 z=LDaMhSef9kKYv$QmRE_E9?E9x+#R7EG1O<>7Jl@f=`e0)6s|@lKP$XQ0bTR{H&FQ zqg^6St}cX+CEqrS#MdXVu^sKs^EdCN)gfU|nuEu;t&|cN=jWpWf4BaikH05EkAG0a z`{60><}kwSr&av3l#hRYOk3;XuMV}FV=&DU*-9CmLvT+ z+WizQMWlnqEBL#Bo<24v@d&Bg{c`sRFGPy!hJDXGw0(p%#G{63F=LblwcdY3eAs2Vm zpQhd8QdM++1Q6AEX;GK+F4-R9ZGBt;ETo9?DCrv0D+1IDFD2JwEAD ztgpk0jFnYAjJJ(@@>0vEgx;*>?T$KtwXGVHwg{EYV4k~Ae-(8Mq(-WYZ0p$a#PooH1&29;1t$_t9$S2(58GNS8RjOP4xdqRX7GP!mS( zwXWr~Th0}t^{$I4?CPWqt{rr_D@Dz&!?e*gOjo$xOPgE|Qj5EaTHR}@&3zZOyYHqB z_w%$_-a=dCx6@YnYt$*fK-=U$L01^rp)ZLX{|8V@2MEVi07E4e007D}b)$q0%WLwQzAecs$;-Nd zASxmv2qLK4kS~#nq5^hlp^Wh%1BQZAKtXf}4pBfw6cmwp&P}qWT{hR>FFo(vkMniU z{hxF9eEi_U02Ygt0^2UTZ1s{$s=JNge?~JFs`gh0d#dZJgLbsfiWrV%$9z#cWYT!t zjF?8kq{&_*;S2Vf!HtPzG*RvEF(L`GzPc~$iyD1Ci)C~-H!lhd7@Lg7h!G1np548{3_1!t0yE`k(y=0q zK|2;q#^YwpX>6fwMt8(ipwh-oMr2;Z4jPg3t-iFjiEVP5Wj8W^l0Y%930Vneg%uYl z%W`q6JIRq+8;=~^6f>R1wX0ice^UuBBdtAFI2o4_6~UJ^kg?F#!|# zYr2j}n9N@@1>7~fuMD#_D5w%BpwLtNrqnEG8-Ir6ou2E2f_VZH!ltvzf8c{mpVs8; z#;m70j=`}S=A%Yn>Zr&LhjZ?R7!(;@XXOpGy-LRkte_4{1m@;F!7*B7==^LD=cSdP zjHE!>@hvj2=j%8b%Xsz_e=^rfuoNB3(?h2TOd@BOcPH#f(lJ*VPOpv?Y41)Ks62d1 zDEI_jNFx|D6O@q)DJR1``t~a28pcUU-Hb zr2w4G3E7TSV_>3VOTsau3RY9(%sAca@`GltA}bxT)ik1H!5XYBe?kY&r90kZSdnDh zJd5IBgehf8^CirA2(Y&E2`TajRIr|su8#*Igb3yNQi%@vQ|Qug0WPFt3=sf32k5POw*CcHVT&e?km<5rfT#*GFEMn@M&;M?CEXnO;5$&MkH%LTOA|6AF?7MP{_m z+0sTkD8^Y27Oe4f``K{+ti76n(*d037~VYDfUe=5dU+nO0CJFdc)it$BU zO%5G8uizR=3aYQ|=4MC7SFo%Y*Wx+?$Cw=WD(3RQ4HU_UDH>}?$Qz?#n3%XpD7%RuqWbW)B70MGJctpNfASD{o7H++vZu$4o1xXFA?ww{ zbWYj1)>vOM11H((N3yjpV{pzA1&`%9C|O8;qTz8oAyBw>%}U=A6;BG(jxNlRaoAGy zw1!8qhjHlOwzNr^`JZaog`d$CAt|9Y>il#($06H=pOe~P#7@x2FSr@lgz zs*2f8e^n2IOcmXU-YNne%Gnnv>GNc2HZc_ZisGIydd#(P!m?R4 zivLigs3CR?D@I^FJ=eFEUL)RNUX(Or!8C~c7a#Nf0~EDxE0#HPRnWs=+UPC{6t^VV zf1XabIi-5(-Jyy?!mSgUnpB~XV_Ytcm>sjoUU_Xrk!*W}#(=%bsJCjxKxz05sY_ z@G}Yk3Dc=EH=Dtv!#Ajku0+&I@M|%_fIyc`EM&DL*fHD9e%b4a#j?E+)M{6be`;Ty zj5$`+JbiP}?32xoXwpP8m%f=<^e{tJxy7oghoq4Pa<`(&N{~HO^qjLoRa7tJT!Sk7 zSsgN9G|@;e$Q&I@$3Q{O#Il^uu=VVmiBk!-Mt8Jk<70+$)=(E;&_XY3YUUYE+mq35 zGroo+M7UH)O&>)Tg_BG8Jq8ffe>0TcVv^EJOj3He0dUd!GEAWt_X^@_X}^c)tlGf( z_1=OVsHoe4Y4tl$>Dz%B-ohQ2HH10$f&WTSjk)Q4h1*FdNq1jYJA(Ovw%S2VOJTtX z>H@W0L#UVR!W51#ZKi)IoH&G~gQ!g5)U9Z$OQB^e8fZ@i{VD?~tQIWX*I2w);@?C{sP+OFC4_IfZtP}LT~3FqJG8Qta_S@ zd{Vkvu5N`^@ADRYnG%9GerFINTpiWH}CfKwRa=su8@xYMtWNUdJgtNAiV;Y+Vvf0(n9&Vd3lf?a|2 zyyMZp2p%U3hp@Z!sUbWwglALO>sM2F-mChR0km_#io86qt3HtRNa-qlkvtm4D=F+N z{ry3=vh!+J>Fd(tHxEt;zf#bwmKV7$3^W(rBK+m*wvRirDL}s&QrJB?i6Atd4)_cB zfJ^^8jKAEEf28nXf9Xdl4z_0iFG!aQePzN$eu?%GQ4sL##QTAOx3DYVE)$-Pf-<3Y z6gGQOqPX1C)iER{rbH=aO-fALiUh}@oulAayfieU^rNVS(J z)mTl^2~@tAe^!b)l2(foB|TZJmNY8*#H->Iagn%6(yPU_l3p*iOM0^ymh>U9SJJ)W zd9fc5FN&8WzhAt?)OC&PM)w4HMnSamqf#jJo|Dn53@=S?$ zm$)mKmy~z{%+m=xH=vS$SKv$n;7+))4h8h&FQj*-2UijZ-vAYN5vYCyO)N(-fvhgV zm>{B<=vszJt~HqKx&S4vAWB_fl({a&6!&VByDvb6JBX?7UQBaugx76LJ#Go~?*9Q$ zO9u!}1dt)a<&)icU4Pq312GVW|5&xPuGV_G@op77bzQ0`Ma3II6cj;0@G{*_x6$l@ zWLq!9K8SDOg$Q2w06vsBTNM!*$jtot=1)l8KVIJeY+_#EvERRF+`CN~+)~_fcio`v z*4!Y8Ql(|4lGuxq7O`$fleEN}9cjIwL&2@>M%LYJOKqvn8>I&WVJ`e@>#4mHnuhzUW>Zd%6?zt$4SI~lcxhl zC4TO|$3j~w-G4Q7M%K!ZiRsf{m&+`_EmNcWDpuKnz~ahZga7dAl|W%-^~!;R$uf$l zI4EIk3?ryIC}TXYW(0;0`IS)TrpP}tglbN4Rm~aBg2TZCuXEfjpuhoC)~>H#Ftz@S z>Dn`9pMU{c7+4fO0Z>Z^2t=Mc0&4*P0OtV!08mQ<1d~V*7L&|-M}HA1L$(|qvP}`9 z6jDcE$(EPEf?NsMWp)>mXxB>G$Z3wYX%eT2l*V%1)^uAZjamt$qeSWzyLHo~Y15=< z+Qx3$rdOKYhok&&0FWRF%4wrdA7*Ff&CHwk{`bE(eC0czzD`8jMNZJgbLWP4J>EL1 zrBCT*rZv%;&bG!{(|=Ze!pLc^VVUu~mC-S7>p5L>bWDzGPCPxXr%ySBywjS7eiGK;*?i?^3SIg!6H8!T(g4QQ%tWV0x-GTxc>x`MRw2YvQwFLXi(-2*! zpH1fqj&WM*)ss%^jQh*xx>$V^%w2Z&j!JV31wR!8-t%AmCUa;)Y-AU<8!|LS2%021Y5tmW3yZsi6 zH<#N!hAI1YOn3Won&Sv+4!2kBB?os0>2|tcxyat=z9bOEGV>NELSSm<+>3@EO`so2dTfRpG`DsAVrtljgQiju@ zLi;Ew$mLtxrwweRuSZebVg~sWWptaT7 z4VV)J7hC9B-cNaEhxy8v@MbAw(nN(FFn>3184{8gUtj=V_*gGP(WQby4xL6c6(%y8 z3!VL#8W`a1&e9}n@)*R^Im^+5^aGq99C`xc8L2Ne1WWY>>Fx9mmi@ts)>Sv|Ef~2B zXN7kvbe@6II43cH)FLy+yI?xkdQd-GTC)hTvjO{VdXGXsOz-7Xj=I4e57Lj&0e_C+ zAH@(u#l-zKg!>k+E-Qjf-cLWyx_m%Td}$9YvGPN_@+qVd*Q)5cI$TrLpP-Mh>_<6k zysd!BC`cEXVf*Q0Y(UgdE^PYo5;;FDXeF@IGwN8mf~#|e4$?Ec!zTJEQCEM2VQr*k z8Kzplz+)oH5+-jyAK;GP8!A zSKV>V#gDFTsa`xXt|1Uc3i&PSgl%D=JEwjW^F5vD0l6G!z|~>y03#T)?a;@!*(vAwmBFr?|-8vt&)jK z!?QG5DNz%WTH4H>vbUDpIEl_O19mVOmP_8bVz-kCsYEtX_1Ovb zj+KS444hDHKJfNHwq&hQ29#QGU>;3P1P+D_kVfmXiA~y=y{YGCGep{s6iwTA*ge*SZSH9K;{Gc1^NWT z@{>XOdHMwf#oVVr5e4%x1I%+r&CEE*Qu8V$tmu5mm?%|OR}{L++~wCzm$RIp(7a-4 zuUW|Jw)8G^n5G$)e{tS^RU&@6hKR!RWWQzWdvkgoyCMKT%caX_=zlus#?;Tc<%xwM zJewbXg?^RAe+_wMk=A>m=A@r~0~#Z6hmh`q^b!Z`=jde+%aR2&hxQ>`<7bXmDk+!% ze+$*7qh)2_^In4P`ktr>O8z!|UZGd$clcz~c=h>Hr~z=--z_oAmq3RVC-fGwS&sJu z1-B|M{Jx;us@*hy_J0o)`U?9cH0RlBfikrIP@yl=AE9!T32=5+P-i$<+jN!7%+FG| z&!5nrvTOegUa57UpZ*+hJA>p2ga0MxsK21E^Uo8!3b{#gdjViLw zDj?{%qL2b=fc}>G8S&udSPszN3la#if5csvd~EsYTU;zzV}C*VHpkOH)4w1W41*h( zbOQ8mmEBsPEo@ObLg z93$OR0O5mpOQ~kA@~zx=sm%~6;&yQdTLO>ECg3w&$V;K3Rxm$Mx#E3$#)AP`Y5ET>GF+K7Ons=3AJy$clM99)e@XPVK;DaXeI#{!nwqZB>eS#gwM4Gc z+UQjZ#jeu&%Mv~fw1GC37KsP2q#o_EXrxGY9xc+Ai=@m@d~k~Hixz2HYVc*MpSt<2 z$TixLN>0<8uJ7@5d0V_2pQVkF7Vq{{!dIm33#3Ft_}G2)yjM)!d^I{4d6C{M=mM$U zf6tOXHRy?rH1$Si=)u8jv@ewuk!jjLMIV6_5a7L3EjF@9Y$D=$k&f1(*4c#dO{r8e z(v+H}hoI~Q3P)vOmA?n#aMPBi8^%0|sj#w@`5rIzh zQ!tSbr|=trz3XA)gH(s7qlZqzSnr3Gf1k$a6s-R${PJy>^CsjPC{3BNQR^|!p8G=V zW%6Eb%Fa-3=o*=+gf}`(Z);pdp9v&gz7C z*}oPKd5d(eNI!)2=dpg8p7eD2T72>A&r(Oc#kZr8Zl0T=_oWh8{A0N9vXFPxf7T*> z@F=#&(1(wn_rW1wit#=dQbR@h$qP^^nkv#IIQ!Y8pN*0_p744iBi`tUFE&yiA8GoT zkhf%^=TflG&)tw(+<*mIXdUgu%{CxCbK8#JowN2@0SO=M^#R!H6?`{v`CUe5FJ?Sw zyCTwGaWuckZrbd*cS97n*}$HSe?&KIhht~x@pz>vsk20GwyCM?#|=m*99Q+xzrHv4AaMp^qVvE1qqxlUZ9nHsoy&~b@Pi; zbSxIXMqg&hucX*B)AZGlZ<_wNNMB2M8@&ts^)Xsm@z<+UH@_KAm7Vk&fBsM1e8*q} zC%twfR;0hW%s)2}p$g))S6XPbY}b-1+g56mZJ4@bdpGTo?Oxg^+aw*3?Jyme?QuE* z>k?^{mF+lLvMtd2WXr!S_d)uoY)gJo;16IEvvuH(Z&YlEF~4MtgVERw{mtdnP$YGQ zLX5QNiKcH()87Fhz);gaf8Zxp{{AQY07^yr*Rp8*MAN@Z(f^s9xq-6?{;3ChGh2NJ z5h72l13;O%#FbbiB|~{IS`?nriNJPIz>*(s7WJjAq^m9+Eguv+(JTTuX-2FlipGi# z>xbCfU@qZdcZ!5pBz#h2ErNo*n((t*0g$h4ur7sb6@-iGc#L$?z0#Uu)Xh){P%^cBVZ7wOS8%9=n+@X6!d z0j(RK8a`Hw2l5S1eVl@8los!kPhF(7@ijcCcL%PBB!<=~MKK)m$2=`T0Eu_#R=NXI zH=h{{`4iqLa>{Mue;U1>Y8Hp4#o-&#kU!*$UlB)|#anUx3hcmxfhe0Q0&^ZadKv7! zbC8#@-C);d@h~h3LJ*D3;sie9@`|I)B2%(-WLk{fsNVS{3NYNyg}nR)ue=tyK_MEW zlVVgDvV8=;&C^-g=a&0t>2a|ceQr0P|8{y#_POQ$^YjVXUgwtkpQOvO&n@>kdb!Un z_g|vV%RaZ<|2lm`_POQ$>nH%Z&n^1GBO19cTkgk1x9oGv{j_*W>RF15CZPW_^!Tj4^T{T!k9N#2;RO7iBy{i;&QUo$Tz+ znfE#GOwP=ozrTJ1Sc55We021t`blp}YoGj;%5y1uf!uNG{2U zc(N@c!)lX%wI3y3q;Kp>H=-52V;i3A7>>%(TwkwPYfo4kR?qm|#C16kwWU$vA^EoB z6NQd%bM%nHh`l&oU46V-HClA2e;$PpNH>BcwCIK7lE8cr+NK@KmP_V`PLn)Sf8 zDbz3|Fu5lWrRhrFHeWUO$ci zK|;QNMYU4B-{xxq=2gh0MJ_>CzIO%I2C`dQ0}U%zLwzhCD9eXj_~Pck%ya+e`Xnf; z1j}62O+JMJ**YJ(mx~=JE+{p9z;saHl6M^@O>uaJ(zL_pbbfg95AEkMI{P zQrP_-wu~WeK)#DjC~RTz1jWl>>J%&u_A8uVH0UJwtHj+O|MgSsVS$&sSO#aG3~yMr6^X${<>0 zQle|Lj@}|34Nrzqkl>m>`@k4<9*UKfc&#)tI4W!!rdA{x!$&L15^Z=Vs_fD^%wvtV z4GjkS3$YfV7A6gE;|0p94J`((b7fR@!QilW^Ak`-SZ_W1@A@+aUavpvf)AYzv|)!q z4VaP^lJwjZ|A#8&wqkPDwLy5?V^3lqxn2iXkLKsKp3v z)lw?h02Q#9dcl*)Nir~*8P80hEVZkB@JF-{`qDZ}%ic=6I zm%FuV~79YG9K?LnO!Z^jy-SC}sEQ=yjZJve> zhLEVZ{w5(ZoQbyviJ%i_b(}#LLsvu9$Wy~P3VYSGP5*j5?A-{?qgO|N4=ynDG-o(t zyH$VDmx5O`yrrVG6j*nCTSp%*G6XD#7Z}brjGFxGwwDl7VfqSEf=l#B~g+q=IW=b5Z!M<&ucX9YRuprWo1}sWhaiRi-Z__Z`V_?vU@yo}2(i zFdD}DxXjRbRIlL*gGOwBofG%{2tGu67-Ps#wKfT;#rvpD6d}xUOenjnl!5P12Z*7q zw!2cYy^fD{X!wL7>>Y4wID{LA*tcu0;U>}9^SSiBWz#PcPvS>06_ak^GaXZyW_ZJ^ z=DocXy5lp)=I}XgE9)%v+M=maz{HH12<9-a6nE%cQa3OVKU(g8u^m{zqPmtPawHNk zWR7wCpHO$PtcdUx!|AF`o4_oZJa38m07T<0{69Jm_wcovhi@1zG{6_Cwr^I%)O|y^ zYO*wZw@?12&fKV)RzYoo?-}~1q;zC-qb%&GVmhg#?!i<=i!>0|LdgHijnpTlpo4>E zJ*c*hO|z2vk8U1+%7RKMp{yWG^+$Y3922QYvQ(DNhU(N_cuU6$Dzv>0=5xNOeup?c zNo$t6oTaTgSFPlQTvG0VOE^gcRX<`ALi8~FK&RITk_PxKQN!sc(4M3F**1D|x$G9+ z+(ut+b|{%kY$001J2kwwjltaQEs*i>3w*#Zn|y(f7#?GPoIb8Gtu3 z6l++mVQpv&_A5%Vi@5j`T=XJZe@D@ehm?9h2I}XB_@(}4kR&~YHrm3(cAUT?`X&;S z^aR@e0Z>Z|2MApz`fv6F008!r5R-0yTcB1zlqZ!0#k7KfkdSS=y&hcen!76`8u=i8 z2484mW8w=xfFH^@+q=`!9=6HN?9Tr;yF0V{>-UeJ0FZ%A0-r7~^SKXVk(SPwS{9eZ zQbn8-OIociE7X)VHCfZj4Ci&GFlsOiR;iIJRaxoGXw(dGxk43#&53m>S)=uTq|9>^ zv)ObhvxHhb=kS$=qTqy4rO7l7nJURDW4f$LID5`?1J}a&-2B3PE?H*h;zu740{(*5 z&`a#OtS|ymO_x%VPRj~QUFfu4XL{-O9v0OB=uyFEst^tz2VT!z4g<2#lRmMJ`j5ZM7xZ*AM>%2rvSpe(=Ig+{%mm`qu9D$$nuwfAVtg)wU1D1@Oa-0qBDX0)tL}srdd3AKVr| zu!4652w2`d0fsD36d(v8?%fw448z=eKw!vV=GK+cg<@B0$2aAJ0j^IF7?!T;tpbe1 z;%>zpHr&Lcv2JbrpgXly(as#!?0ARvZ(9Tyw9dPLBI6nnUO(iIoc8&R_JI|#ma!w& zAcT?E9qq-QVS__Pcf=Ea+u?_rKX*`?w+8~YR^5P4}7sOkF z9^v<)Wd+*~+BRU@A=_f}TNYc7Hi#bHH2iMhXaTblw9&-j;qmcz7z^KOLL_{r36tEL z;@)&98f?OhrwP%oz<(i#LEKIdh93L_^e1MUFzdwUAZf=#X!!zWeTi=n`C^CXA?1cg z9Q>gxKI!0TcYM;pGp_iegD<(`iw>T3#itznkvl%+;5k=(+QA>Y9v3?#|5p?&G^NcjljeZ~g^f18y^%J9)Cd^>|=NijQzL5oim< zlYvkmuB9`wBAK$LhSPsqg44Xt6)qW^7KbGx93STK5hI&60&Pi2F?cADNrlr=CM*jZ zLoF@q;~O@SuHKr*C$ow|6UMLxJIZx~e9?Ss^Ty`ZaDtBpPPoAs zJW(yH$N4T<;S2#yPeoF?lu&qNOqVhlu1EGea_2aYXH89ap^|@L(Gh7>iYStriu4X0 z;c?T2YBH74HPSR?ZZItAvUReitVH^z=C?2`C}=rO7dV=-77=68sE%uDQcf{6cFi77 zhpm&o07Yne+0~cxtd5_*)sP&)@HC}ize=e%9 z#0xj(imzo}crbrYe63*c7RTYjDhiU1%Z6##t_Qui5BGbp8h+wH(WFEnJTC%R=pic) zGR)Vxl-NNqUE8ZG40R2ST?P81rl{~1FV5^e_8Pg(x$FW_6(mpMLKFJ(*W5>({#DW*Q zoCKbj>CJyx?{us_MShE|Mu(*hn_8mTv>ROv%chy0TJ@sGvER$E`JN~loQ0D;f|Gu7 zWz6bozzKCPos?s8CQ8kPJJs7yy@Vnhlrv7zVopqhG;I`3KjYvJ7U3Q84o~47P9z6E zG=+Dj6AqqAR72W5+#J*NkpVf)wXA6$(M~T?7#4pzGDBrUrkr3p#=R| z)ud>4j>mb%X;#lOggUgWlJKjV=@*U0pX+Y^LM!$sbuI0$Ut`oayK%Cl!#hQF;YI3S zNlkxGOJ@1oTeu+m*V=%8d-n8%+f;C_H)8o;-_FbP`qm5+m$!#sUS3~az?6UCnEncp zrIoW1GYikZ3^9(J+*73a_E2=I+@yTZzO&nHEt<<$te&=8HKwBfgjml-JG}$lI=92@ z4z$bd>F@tEaq6laA2^*uV=f+<_SYxIZ2lu1)15Avq4jrv%t_4M85a1jrdBbg?&OBO z?w|X;yr%s=o>F|n{!ss|&@a-Ga?>Xp`Tt1WnzOgFxn}QvF`pdqH+A0O6M<{R?*8aI zm|Fe9w=3;hq}hV*9V%VFm_Nouyj`+eMRi@5yyP88PxBQT&vbZ!!)Ky@-W>G*(aL2R zRrh*#Vd#O=-{*82{_t)2Q0>X_c9z?Dty^;DE4*(gK1oaCZ038&qGr3{1N+o{&GW)S zR_RrFeoeXT93w9WTJ=k2WmwRsyZJjz~raN31L?*7OZAKosxIC_$obw$Vto-F(G};KG84}n`sf{TwU%2wY3la+hh1Mo zOk8XAThu>BWiTy&7qj>ZQ^xVsJ)L}CZf)Xc&#mN8-WF1DX4>(>Q`45ejQ0=-ZM4zk z5L6XanSS@s%!u+}4U5KdXED2N1@ELz7MFYE%Vl0?GTZp&z)8j5fxVV0(M{Jk-YLI# zD7^e3@2_*4y-s~w)iFmb?A6PWbS|JU~kQ>A{z z<#_KpR{ZVn&J%Zz?8+_T3iQ3CX&uXK`8Ms6*u@`B+O_xJ&pYz;K_cUp%GV7lwA_XQ7h?=EiYO%jA1g4LkyE%H;C7 zPBKh~SnewUyI}=DY{&pStppCf@lAGIC^PvppTgt~O9f-}d3G+pn zHcEm8XU#X20bkb$bjx(06{tEH6~T)57MRE&F1=%5uthQcpfXUA=H!#g@?du$?pR}B zus~7Bs}5H9dx4fr4CvY|pq0)*@1y!kP7|oePX>Iq6EG0Z0Tmgcm@-Wp?51-IwPcVl z;ju?iv_==K$b6Bx4B|cu^pKur092#|ys(EK0ARQEYY^^{l%|QCuAjeEkp14?q>9h4@!6nkbbJ&fg5yu+?X8=+3#!VJj5-STn zB^PM!VxULuP~>AB87AvHdVm8Jad0aGgFcF?DbAA>SBOrobXEl`gda@_j7wDOI$XgD zA?Lm7ffXYk=VyXqs+K2Iu@*=nEBNf4$p*_rnW}xj5^+A_U=u*+w%i1|eiP93x+o@C zhJh7Ihbe;@`y&KjUXYgX_u)8xbzqD+z9U^n!xP?doXqyT+|nlWGZ zf)zbpp(6wDM6oe2=%E;$(+^UFIrO3?4Q`17gDC*02i4ujCr@1I$qFe_?ym&yj++j) RhRK)Bhkwq`;Yh)md4RrtR%sNbw?F7+wVN@9oT5^KvyxHCChVwDz29-_(~6`YI}kOI zb^sOR2x~T#ZdIJ>Rf@`fWMMck8Z~Fk7!ymA-q=^Hp5eZ$X)}%69EWv#a)HMQBo+#f z36F86&q=PH!h1hfL>Ol{cXt`zy7GFq%Eq79O{IA-u!cH*(wj1wN}D2M4WT6o(qxrW zEB}r}@-+r4&wIr;xO0(AI@=cYWb?m21~K;0A^-T{gEQnxfCN&@N(#Zq#RXZY87O0m z;t0Wp7M~;I&<5qU1T+?pjfUye_TixR_f>$?rT1}+*6u;9Gn0cXM{`4grB6(W zyBDpHwv$&%UIzt(jZMh^e3jZ{I@kE301olpI{yj0+;ZWogmFjno1+v zMW;sMFf7sR(_fhVjl~QhEC!kN?S1GnQ8&fuPw9z{5eDbyAAsT&CyjpUf=RK)X*YhW zwf>HLeXJxlm0mFjo>lB@ni;CUkg)*JRligsG*5>@wN*UJvbS&X^}x zn@^UJmJ90QY)d4OLkji-vg;l*>VWz+eRS?0G0Bg!HhZc?2Wz}S3kMg^_@+65nA?uo zkBwh=aDQVGH8XVK>zh0u{gJbev&iTnS1h3p(pF$?`aC^rhJj2lK`5&HHV#_?kJb zGMSi_SJ(*5xg|k>>Dvgt0#5hN#b8)>x5&pj4Wy_c7=p-XQ=>p*vRykohWoq+vj1uk znu?X~2=n2?uaB_*+Lr;+&434q#3lhbD9@_k1Te#nwy}MM^TTHt=B7p23Hvw*C##@< z$6AnfJ+Ri~X^`J(;3$v;d?J5C5U~zQwBA9#k|t1Y#>7ZrY#I@2J`|kfQ=Sxhc*rH| z{varkusu6HJ$Ca6x^v$ZA6sX;#AVi73(ebp61*3)LCF6yToc0LMMm{D%k+S_eJ<3CTZgjVEpgE=i5mX z0o|kFlPT7$0gM?NfN_Wk=T=zCXFhtz_fJrXuKFQ#uaUzUCWj%}$pz$g05t#ar{-1o z#ZYh6o&A&s>>NA5>#m&gf?X>M)bj>Q7YY}AR8nPC<0CJ`QolY!M*@PhNF4%4$5nFf z4{VxA-;8{~$A&>%Yo@~y4|O}IqYemSgP7Sy?d}}+e`ng%{?_hDUhCm`I`hP=rda|n zVWx~(i&}Q|fj^k+l$Y30zv6ME&AX7HTjy~frLaX)QgCMmQq3_qKEcRyY7nk_fa}Z$ ztrwMjNeJ|A@3=y7o^6LMBj@LkTyHm7pK(Vxq%M=uXr;M7{wWsrG~I1ki5OQ6#92Ih%Quj|8Z|qUzyy6 zUf%s*-I*73e%AX}cTI5r+ZsgVR1jr6I*hnu%*rSWqzs(T0KD7A4U}76 z)lH{eBF=pRy0q*o<*iM4@ojv65`y{#TKm=!5+7PwC>z)to^he4BI9`z60IYcFC8XC zZ<65C;OV<=0*{u4*i@nn?J4m6_p_jauY-;RSof^%yxer|uPQvyzOCP1x_-}6H;)~6 zkQH$^6A(lu&B^q)5vwSypjGu5P`Y#UdzM%Uhuh>vlisoS7c?a}|1hah-vo_i`e5;! z93hb``au;ow+t;(wB3-=ww(pgb`ZrEODvFvfEiQvXaSX6+A0ooWdEx3u-oBf9V((3iwRO z7r|AqsNjl$(oTUVvOf^E%G%WX=xJnm>@^c!%RBGy7j<>%w26$G5`?s89=$6leu-z; zm&YocPl2@2EDw6AVuSU&r>cR{&34@7`cLYzqnX)TU_5wibwZ+NC5dMyxz3f!>0(Y zJDdZUg*VS5udu>$bd~P>Zq^r)bO{ndzlaMiO5{7vEWb3Jf#FOpb7ZDmmnP?5x?`TX z@_zlHn)+{T;BtNeJ1Kdp2+u!?dDx4`{9omcB_-%HYs2n5W-t74WV76()dbBN+P)HN zEpCJy82#5rQM+vTjIbX*7<~F)AB_%L*_LL*fW-7b@ATWT1AoUpajnr9aJ19 zmY}jSdf+bZ;V~9%$rJ-wJ3!DTQ3``rU@M~E-kH$kdWfBiS8QL&(56OM&g*O73qNi( zRjq8{%`~n?-iv!fKL>JDO7S4!aujA}t+u6;A0sxCv_hy~Y2Pbe53I*A1qHMYgSCj0z6O zJ!z}o>nI#-@4ZvRP|M!GqkTNYb7Y)$DPWBF3NCjNU-395FoDOuM6T+OSEwNQn3C`D z-I}Tw$^1)2!XX+o@sZp^B4*!UJ=|lZi63u~M4Q%rQE`2}*SW$b)?||O1ay`#&Xjc! z0RB3AaS%X&szV$SLIsGT@24^$5Z8p%ECKsnE92`h{xp^i(i3o%;W{mjAQmWf(6O8A zf7uXY$J^4o{w}0hV)1am8s1awoz0g%hOx4-7 zx8o@8k%dNJ(lA#*fC+}@0ENA#RLfdZB|fY9dXBb;(hk%{m~8J)QQ7CO5zQ4|)Jo4g z67cMld~VvYe6F!2OjfYz?+gy}S~<7gU@;?FfiET@6~z&q*ec+5vd;KI!tU4``&reW zL3}KkDT;2%n{ph5*uxMj0bNmy2YRohzP+3!P=Z6JA*Crjvb+#p4RTQ=sJAbk@>dP^ zV+h!#Ct4IB`es)P;U!P5lzZCHBH#Q(kD*pgWrlx&qj1p`4KY(+c*Kf7$j5nW^lOB#@PafVap`&1;j9^+4;EDO%G9G4gK zBzrL7D#M1;*$YefD2I-+LH{qgzvY8#|K=-X`LN578mTYqDhU}$>9W&VOs z*wW$@o?Vfqr4R0v4Yo_zlb?HKOFS zU@WY7^A8Y{P)qU9gAz52zB8JHL`Ef!)aK7P)8dct2GxC*y2eQV4gSRoLzW*ovb>hR zb0w+7w?v6Q5x1@S@t%$TP0Wiu2czDS*s8^HFl3HOkm{zwCL7#4wWP6AyUGp_WB8t8 zon>`pPm(j}2I7<SUzI=fltEbSR`iSoE1*F3pH4`ax^yEo<-pi;Os;iXcNrWfCGP^Jmp935cN;!T8bve@Qljm z>3ySDAULgN1!F~X7`sAjokd_;kBL99gBC2yjO+ zEqO##8mjsq`|9xpkae&q&F=J#A}#1%b%i3jK-lptc_O$uVki1KJ?Y=ulf*D$sa)HC z=vNki?1aP~%#31<#s+6US0>wX5}nI zhec(KhqxFhhq%8hS?5p|OZ02EJsNPTf!r5KKQB>C#3||j4cr3JZ%iiKUXDCHr!!{g z=xPxc@U28V8&DpX-UCYz*k~2e)q?lRg<{o%1r;+U)q^{v&abJ9&nc6a32ft(Yk}`j ztiQP@yEKf@Nu3F;yo9O})Roh9P08j7@%ftn7U1y;`mard4+5 zB62wpg$Py_YvQ!PE2HpuC}3el-F3g{*&a z3q{eLy6Xz|F+aMrn8R8IW2NZu{tgsyc(>*TdV79@?V$jG(O+Iz2rnDBc|1cK8gR$Y zthvVTI;(eYhOdjapHe=9KI`|2i;{VIfvnR6`qof=4a=(BTZkev78+6GJW**Z!|yvS zes)T%U573C~Hm`&XJzE=2t7tFIZM`!^r^&z;W?dOj-N+a10^>wV(l~2naa?s; zTxU{z;Go|Ve!vUjUrZ$B#mWH)NSdxi;dWa-@w)-$wBOpo`DEG<;C#W||W}&@z>C`*j9V|`ai)z*2PG`TZt6T{a zj!#m3`Vz5R9wJkNMsJ1`fSCS2mHnizWDT!G0Ukp$%*_^X1=k=%mmO$^_0_d|kc8ek4_DZwomL(>GGtfEB)Wy&cfZ@9-T|hAq&fx;XR$$_yl6iogcR{u zm9g)axS6=_IL4=wQXf|EkzO68$Ms4*JXAt8gFxLCibt^C#C|I|v|U{%A;+NaBX-Yn z`HAmP*x5Ux@@Wkpxest$F~K8v0wlb9$3gHoPU(RMt+!BfjH?`8>KMK|!{28+fAk%6 zWdfyaD;Dr~`aJHn0}HIf^Y9*keGvm6!t?o%;je)wm`Dm$fN?YtdPI7S=Y23+15L{J zr;n3MYg`<50nW^`BM$&M(+PQ7@p7Lvn(kE`cmoNS7UkQmfvXQBs_unhdfM){k`Ho! zHL0#a6}Uzs=(bu;jnBAu>}%LzU3+{sDa6~)q_|pW1~*Is5J(~!lWvX(NpK_$=3Rbn zej|)%uR0imC;D5qF7p}kdg(-e{8#o!D_}?Fa<&{!5#8^b(dQl40ES%O_S(k8Z$?Hs z;~ee=^2*5S#A*gzEJgBkXyn*|;BBH97OOmvaZ>&U&RfU0P(?jgLPyFzybR2)7wG`d zkkwi) zJ^sn7D-;I;%VS+>JLjS6a2bmmL^z^IZTokqBEWpG=9{ zZ@<^lIYqt3hPZgAFLVv6uGt}XhW&^JN!ZUQ|IO5fq;G|b|H@nr{(q!`hDI8ss7%C$ zL2}q02v(8fb2+LAD>BvnEL8L(UXN0um^QCuG@s}4!hCn@Pqn>MNXS;$oza~}dDz>J zx3WkVLJ22a;m4TGOz)iZO;Era%n#Tl)2s7~3%B<{6mR!X`g^oa>z#8i)szD%MBe?uxDud2It3SKV>?7XSimsnk#5p|TaeZ7of*wH>E{djABdP7#qXq- z7iLK+F>>2{EYrg>)K^JAP;>L@gIShuGpaElqp)%cGY2UGfX1E;7jaP6|2dI@cYG%4 zr`K1dRDGg3CuY~h+s&b2*C>xNR_n>ftWSwQDO(V&fXn=Iz`58^tosmz)h73w%~rVOFitWa9sSsrnbp|iY8z20EdnnHIxEX6||k-KWaxqmyo?2Yd?Cu$q4)Qn8~hf0=Lw#TAuOs(*CwL085Qn9qZxg=)ntN*hVHrYCF3cuI2CJk7zS2a%yTNifAL{2M>vhQxo?2 zfu8%hd1$q{Sf0+SPq8pOTIzC&9%Ju9Rc1U9&yjGazlHEDaxY|nnS7rATYCW_NA&U? zN!7-zF#DXu0}k4pjN05yu#>x8o#Jx7|Fk=%OR((ti%UVKWQNH>+JhH#ziW1hD=rk* zD#1j?WuGxd-8VqG@n_Lqj^i=VBOg@GLePo0oHX9P*e7qBzIs1lzyp;}L3tP1 zl5;OiHG&-flQ;rYznH%~hz>fuJ!n*H#O)3NM3`3Z9H|VFfS-_xHRCuLjoIS9wT!F0 zJ-kV3w>7EguDzoBPxW>Rra0#+Y?;Woi7qJ1kpxTad?O?^=1cG@GeNtRZRi8_l-1CS z`(#oF<;VYR(l(gHIYH$y2=rj5m3QL{HQgbW9O!TU*jGj!bFazIL?MYnJEvELf}=I5 zTA6EhkHVTa0U#laMQ6!wT;4Tm4_gN$lp?l~w37UJeMInp}P>2%3b^Pv_E1wcwh zI$`G-I~h!*k^k!)POFjjRQMq+MiE@Woq$h3Dt8A%*8xj1q#x?x%D+o3`s*)JOj2oD7-R4Z*QKknE3S9x z8yA8NsVl&>T`a;qPP9b7l{gF&2x9t5iVUdV-yOC12zJnqe5#5wx0so2I)@8xb$uPG zNmv=X)TjpHG(H!$6Xp>)*S}r538R99Y{Pofv}pAFlUK;xi{E43^->z1srWR=J$8N! z4jRu;EAiLG9R$5#{gR){5?o^W^!t140^f=vCVSs@vK7#`-fv`P*WV|>nX610pK08< z>r#{r)fR?2pNG}8o)?uvX#UJI)YM5CG@0E8s1lEV`rom|kBmf={%h!o|26a=lNJbX z6gkBS7e{-p$-Vubn$(l_IbwS02j;+6h2Q5F7P?Du2N!r;Ql$M>S7Frf*r3M`!bvWU zbTgl2p}E<*fv?`N8=B71Dk03J=K@EEQ^|GY*NoHaB~(}_ zx`Su{onY@5(Owc#f`!=H`+_#I<0#PTT9kxp4Ig;Y4*Zi>!ehJ3AiGpwSGd<{Q7Ddh z8jZ(NQ*Nsz5Mu_F_~rtIK$YnxRsOcP-XzNZ)r|)zZYfkLFE8jK)LV-oH{?#)EM%gW zV^O7T z0Kmc1`!7m_~ zJl!{Cb80G#fuJa1K3>!bT@5&ww_VSVYIh_R#~;If$43z`T4-@R=a1Px7r@*tdBOTw zj-VzI{klG5NP!tNEo#~KLk(n`6CMgiinc1-i79z$SlM+eaorY!WDll+m6%i+5_6Mc zf#5j#MYBbY)Z#rd21gtgo3y@c(zQVYaIYKI%y2oVzbPWm;IE#Cw$8O$fV}v}S%QDA zkwxW{fa#Goh1O|+=CF3h3DWNw+L^ly?BNQ7DY~Eca}5nt^>p#3cc9s3iDub0nh`Wy z?oH|dW8-HG@d5E@U>NWPjnhTjr7C${Iwj#;F2G@++N=Y2tjV;z57RNgE|kXQC)1h- zx8ODU>kk};J8KiSUx5jSsA_XPou1OH8=R~q9{`r>VnHkU6A=!zNOH8IGJoO!+bQys zDS2-H(7+Jfe+&zf#;OSV=83I|^M;0`Kv*#4%%O7x>@BgGMU*@ajUvY>cYw^`*jm@+ z{LZ2lr{OTMoQXn2XUsK-l72oysi9vgV4Sux^1GsW6zTV;?p#J06EvSVyUq5$f4kq< z{Chq5Z?I%ZW}6&uL+f&0uCW#^LyL!Ac2*QRII5TDGfZ43YpXyS^9%6HBqqog$Sal3 zJjI$J+@}ja9Xp)Bnbk+pi=*ZAHN}8q@g$$g<6_4?ej&Rw)I%w(%jgGlS5dTHN`9(^<}Hg zD$PbZX+X>;$v4NjGJxMDvVBiIam$cP-;h0YqQ{YgxYn-g&!}lHgaG3^B=>Z!D*7tp zu19e;r`u*+@4h41Da&NZv$qy-i6#DdI)EVvmKO*PvIKz-9E5R*k#|`$zJza8QJ)Q{ zf~Vl+I=8oaq)K!lL7Et5ycH;m&LKIvC|z4FH5bo|>#Kg5z+Jy*8Ifai}5A#%@)TgPRaC4f>Qk&} z4WciN&V(T~u^xBgH=iP(#nd;_@L&`7FUF>Qm-;hOljv(!74f&if;fz2Mg=b%^8$^C zna!2I&iCz&9I5ckX-5mVoAwz~)_&b#&k$e+pp=U2q-OjkS@yZ8ly1$2Vh?}yF0={P zPd3O@g{0L=eT-Dm9?imeUP(!As&DJ_D=5lwQ=3)XWXg)12CoB=-g-HX9RSXgL;yo0 z?$7z8Sy9w?DvA^u`Fnl7r_J&_jJ7claq*2l9E~#iJIWAPXuAHfmF3-4YjFYhOXkNJ zVz8BS_4KCUe68n{cPOTTuD<#H&?*|ayPR2-eJ2U0j$#P!>fhd(LXM>b_0^Gm27$;s ze#JTrkdpb*ws{iJ1jprw#ta&Lz6OjSJhJgmwIaVo!K}znCdX>y!=@@V_=VLZlF&@t z!{_emFt$Xar#gSZi_S5Sn#7tBp`eSwPf73&Dsh52J3bXLqWA`QLoVjU35Q3S4%|Zl zR2x4wGu^K--%q2y=+yDfT*Ktnh#24Sm86n`1p@vJRT|!$B3zs6OWxGN9<}T-XX>1; zxAt4#T(-D3XwskNhJZ6Gvd?3raBu$`W+c(+$2E{_E_;yghgs~U1&XO6$%47BLJF4O zXKZLVTr6kc$Ee0WUBU0cw+uAe!djN=dvD*scic%t)0Jp*1& zhjKqEK+U~w93c<~m_Oh;HX{|zgz=>@(45=Ynh{k#3xlfg!k z>hsq90wPe(!NljYbnuL6s`Z!wQSL8|(A*@M8K>`nPJ<9Hb^ zB6o?#^9zP>3hp0>JAite*3N?Rm>nJ1Lpq4)eqSe8KM_f(0DB?k8DNN6(3 zU#>-{0}3~vYJ7iIwC?Zbh@aJ8kfIvY%RveZltThMN73#Ew}jOwVw+|vU5u-wMoo9C zO(tv#&5`DOhlzunPV?M~qlM|K74x4cBC_AC?2GNw_-Uv&QtPOj(7L4NtVh$`J%xci zioGVvj5s|GY886)(}g`4WS3_%%PrF(O|s-n&-SdfbssL`!Gi7Hrz_r$IO@*$1fYbQ zgdp6?(IUaNPaH7}0%U|9X8HFonsJRrVwfmf*o1;k0+PwV^i%f7U{LAayu`!x*FmhN za(#a^@Idw9)jN)K!=sFC(G)ZNaYY169*IJ_ouY9>W8tC>S&MEp$+7 zy)NFumpuE>=7T@`j}8pa)MGpJaZoG(Ex3AzzH>gUU^eyWp*N2Fx+9*4k~BU;lQ1PG zj4)_JlelzJ==t*7=n2(}B4^^bqqcKFcJ7yVzbH_CWK?{eXdpKm);4|o{aM=M&`E$=_~PVi2>>L zKTN_x&qA)@ak=v=0Hl5H6~?LOfO@1+fu5(sB|VWID)w?%{m+n#7bLaszEJ#;$HMdt z9qP0gk)hIYvE1!jseA^FGTyK=i4eTPjTL$R;6FywMBZBPlh2ar9!8wlj1sinLF-1g zR5}hLq>pb1|AC-WcF!38e*kFv|9n<$etuB=xE%B=PUs}iVFl>m;BiWUqRIxYh7}L&2w@{SS-t(zUp`wLWAyO=PEE=Ekvn@YS*K@($=i zBkTMaH<&cAk${idNy0KZ8xh}u;eAl*tstdM8DYnM5N;bDa`AB+(8>DqX+mj17R2xBp45UES|H*#GHb_%Nc{xWs7l{0pqmiBIPe@r=X%Y-h<-Ceo;4I>isrw1Hd zZd*VjT`H9gxbf{b3krEKNAaV$k>SzK(gzv}>;byq##WEhzTN^@B4+VJvW>y|U}}AQ z4^Bdz9%QKBWCy+h$I?L@ffl{fLLL41Tx|M+NjjRf(`KjHG4^y=x3l z!!-{*v7_^6MiJOC@C$WV=hz9J^Y^lK9#tzs6}-

Gn4F+B~IivciU9^t0j-Mgao3 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 DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -205,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 5573cd140ab37c87cbf94d8b0411830070ec2141 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:44:27 +0200 Subject: [PATCH 06/31] refactor: use Jackson to deserialize items --- .../re/sylfa/itemcreator/ItemCreator.java | 34 ++++--------- .../deserializers/ItemDeserializer.java | 5 +- .../deserializers/MaterialDeserializer.java | 5 +- .../TextComponentDeserializer.java} | 6 +-- .../itemcreator/recipes/CustomRecipe.java | 50 ++++++++++--------- 5 files changed, 43 insertions(+), 57 deletions(-) rename src/main/java/re/sylfa/itemcreator/{items => }/deserializers/ItemDeserializer.java (93%) rename src/main/java/re/sylfa/itemcreator/{items => }/deserializers/MaterialDeserializer.java (80%) rename src/main/java/re/sylfa/itemcreator/{items/deserializers/ComponentDeserializer.java => deserializers/TextComponentDeserializer.java} (83%) diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index ef0e7e2..b5f359b 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -12,38 +12,36 @@ import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.meta.components.EquippableComponent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.permissions.CommandPermissions; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import re.sylfa.itemcreator.commands.ItemCreatorCommand; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; -import re.sylfa.itemcreator.items.deserializers.ComponentDeserializer; -import re.sylfa.itemcreator.items.deserializers.ItemDeserializer; -import re.sylfa.itemcreator.items.deserializers.MaterialDeserializer; +import re.sylfa.itemcreator.deserializers.TextComponentDeserializer; +import re.sylfa.itemcreator.deserializers.ItemDeserializer; +import re.sylfa.itemcreator.deserializers.MaterialDeserializer; import re.sylfa.itemcreator.recipes.CustomRecipe; import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; import re.sylfa.itemcreator.util.Log; public class ItemCreator extends JavaPlugin { - + + @Getter private static ItemCreator instance; - private static ItemRegistry itemRegistry = new ItemRegistry(); - private static RecipeRegistry recipeRegistry = new RecipeRegistry(); + @Getter + private static final ItemRegistry itemRegistry = new ItemRegistry(); + @Getter + private static final RecipeRegistry recipeRegistry = new RecipeRegistry(); @Getter private static final ObjectMapper mapper = new ObjectMapper(); @@ -60,18 +58,6 @@ public void onEnable() { registerCommands(); } - public static ItemCreator getInstance() { - return instance; - } - - public static ItemRegistry getItemRegistry() { - return itemRegistry; - } - - public static RecipeRegistry getRecipeRegistry() { - return recipeRegistry; - } - public void init(){ itemRegistry.removeAll(); @@ -109,7 +95,7 @@ private void registerMappers() { SimpleModule module = new SimpleModule(); module.addDeserializer(ItemStack.class, new ItemDeserializer()) .addDeserializer(Material.class, new MaterialDeserializer()) - .addDeserializer(Component.class, new ComponentDeserializer()); + .addDeserializer(Component.class, new TextComponentDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java similarity index 93% rename from src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java index df62945..e7b84d3 100644 --- a/src/main/java/re/sylfa/itemcreator/items/deserializers/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java @@ -1,6 +1,5 @@ -package re.sylfa.itemcreator.items.deserializers; +package re.sylfa.itemcreator.deserializers; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; @@ -27,7 +26,7 @@ public ItemDeserializer() { } @Override - public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { + public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { JsonNode node = jsonParser.readValueAsTree(); Material material = parseMaterial(node); diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java similarity index 80% rename from src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java index dfbf59e..5eba8cd 100644 --- a/src/main/java/re/sylfa/itemcreator/items/deserializers/MaterialDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java @@ -1,6 +1,5 @@ -package re.sylfa.itemcreator.items.deserializers; +package re.sylfa.itemcreator.deserializers; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -15,7 +14,7 @@ public MaterialDeserializer() { } @Override - public Material deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public Material deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { return Optional.ofNullable(p.getValueAsString()) .map(Material::matchMaterial) .orElse(Material.STONE); diff --git a/src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java similarity index 83% rename from src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java index 21b3f02..3c2c82d 100644 --- a/src/main/java/re/sylfa/itemcreator/items/deserializers/ComponentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java @@ -1,4 +1,4 @@ -package re.sylfa.itemcreator.items.deserializers; +package re.sylfa.itemcreator.deserializers; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; @@ -10,10 +10,10 @@ import java.io.IOException; import java.util.Optional; -public class ComponentDeserializer extends StdDeserializer { +public class TextComponentDeserializer extends StdDeserializer { MiniMessage miniMessage = MiniMessage.miniMessage(); - public ComponentDeserializer() { + public TextComponentDeserializer() { super(Component.class); } diff --git a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java index 7f4d34e..d79d49c 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java @@ -3,6 +3,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.bukkit.Material; @@ -20,6 +21,9 @@ import org.bukkit.inventory.StonecuttingRecipe; import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.ArrayLenRange; +import org.jetbrains.annotations.NotNull; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.items.ItemRegistry; import re.sylfa.itemcreator.util.Log; @@ -54,23 +58,23 @@ public Recipe asRecipe() { Log.warn("Shape for %s is not set", keyValue); return null; } - if(shapedIngredients == null || shapedIngredients.entrySet().size() == 0) { + if(shapedIngredients == null || shapedIngredients.isEmpty()) { Log.warn("Recipe %s has no ingredients.", keyValue); return null; } ShapedRecipe shapedRecipe = new ShapedRecipe(key(), result); shapedRecipe.shape(shape); - shapedIngredients.forEach((ingredientKey, ingredient) -> shapedRecipe.setIngredient(ingredientKey, ingredient)); + shapedIngredients.forEach(shapedRecipe::setIngredient); return shapedRecipe; case SHAPELESS: - if(ingredients == null || ingredients.size() == 0) { + if(ingredients == null || ingredients.isEmpty()) { Log.warn("Recipe %s has no ingredients.", keyValue); return null; } ShapelessRecipe shapelessRecipe = new ShapelessRecipe(key(), result); - ingredients.forEach(ingredient -> shapelessRecipe.addIngredient(ingredient)); + ingredients.forEach(shapelessRecipe::addIngredient); return shapelessRecipe; case STONECUTTING: @@ -131,7 +135,7 @@ public Recipe asRecipe() { public static class Builder { CustomRecipe recipe = new CustomRecipe(); - private ItemRegistry itemRegistry = ItemCreator.getItemRegistry(); + private final ItemRegistry itemRegistry = ItemCreator.getItemRegistry(); public static Builder builder(String keyValue, String type) { return new Builder().keyValue(keyValue).type(type); @@ -143,24 +147,19 @@ private Builder keyValue(String keyValue) { } private Builder type(String rawType) { - RecipeType type = RecipeType.valueOf(rawType.toUpperCase()); - if(type != null) { - recipe.type = type; - } else { - Log.warn("Unknown recipe type for %s: %s", recipe.keyValue, rawType); - } + recipe.type = RecipeType.valueOf(rawType.toUpperCase()); return this; } Builder shapedIngredients(Map shapedIngredients) { - if(shapedIngredients == null || shapedIngredients.entrySet().size() == 0) { + if(shapedIngredients == null || shapedIngredients.isEmpty()) { return this; } Map parsedIngredients = shapedIngredients.entrySet().stream() .collect(Collectors.toMap( - entry -> entry.getKey(), + Map.Entry::getKey, entry -> itemRegistry.parse(Key.key(entry.getValue())) )); var unknownIngredient = parsedIngredients.entrySet().stream() @@ -184,19 +183,22 @@ Builder ingredients(List ingredients) { return this; } - List parsedIngredients = ingredients.stream().map(ingredient -> ItemCreator.getItemRegistry().parse(Key.key(ingredient))).toList(); - var unknownIngredient = ingredients.stream().filter(ingredient -> ingredient == null).findAny(); + List parsedIngredients = ingredients.stream() + .map(ingredient -> ItemCreator.getItemRegistry().parse(Key.key(ingredient))).toList(); + var unknownIngredient = ingredients.stream().filter(Objects::isNull).findAny(); if(unknownIngredient.isPresent()) { Log.warn("Unknown ingredient found for recipe %s", recipe.keyValue); } + // HACK why .toList() is not working here?? - List recipeChoices = parsedIngredients.stream().map(RecipeChoice.ExactChoice::new).collect(Collectors.toList()); - recipe.ingredients = recipeChoices; + recipe.ingredients = parsedIngredients.stream() + .map(RecipeChoice.ExactChoice::new) + .collect(Collectors.toList()); return this; } - Builder ingredient(String ingredient) { - if(ingredient == null || ingredient.isBlank()) { + Builder ingredient(@NonNull String ingredient) { + if(ingredient.isBlank()) { return this; } @@ -225,7 +227,7 @@ Builder result(String result, int amount) { return this; } - Builder shape(String[] shape) { + Builder shape(@ArrayLenRange(to = 3) String[] shape) { if(shape.length > 3) { Log.warn("Shape for %s has too many lines", recipe.keyValue); return this; @@ -255,10 +257,10 @@ Builder cooking(float experience, int cookingTime) { return this; } - Builder smithing(String rawBase, String rawAddition, String rawTemplate) { - if(rawBase == null && rawAddition == null && rawTemplate == null) { - return this; - } + Builder smithing(@NotNull String rawBase, + @NonNull String rawAddition, + @NotNull String rawTemplate) { + ItemStack base = itemRegistry.parse(Key.key(rawBase)); if(base == null) { From 7fa91fb6271d84817017a0814891932644596691 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:47:19 +0200 Subject: [PATCH 07/31] refactor: code cleaning --- src/main/java/re/sylfa/itemcreator/ItemCreator.java | 9 +++++++-- .../deserializers/TextComponentDeserializer.java | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index b5f359b..acf5b40 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -22,6 +22,7 @@ import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; @@ -65,7 +66,11 @@ public void init(){ List readRecipes = RecipeReader.readAllRecipes(); - Map readRecipesMap = readRecipes.stream().collect(Collectors.toMap(r -> r.key(), r -> r.asRecipe())); + Map readRecipesMap = readRecipes.stream() + .collect(Collectors.toMap( + CustomRecipe::key, + CustomRecipe::asRecipe + )); recipeRegistry.getAll().keySet().forEach(key -> Log.log("Removing %s: %b", key.asString(), Bukkit.removeRecipe(key))); recipeRegistry.removeAll(); @@ -82,7 +87,7 @@ private void registerPermissions() { } private void registerCommands() { - LifecycleEventManager manager = this.getLifecycleManager(); + LifecycleEventManager<@NonNull Plugin> manager = this.getLifecycleManager(); manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { final Commands commands = event.registrar(); List.of( diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java index 3c2c82d..b111851 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java @@ -18,7 +18,7 @@ public TextComponentDeserializer() { } @Override - public Component deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public Component deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{ return Optional.ofNullable(p.getValueAsString()) .map(message -> miniMessage.deserialize(""+message+"")) From a366abb0afce8c64225d74c9343ede5938b4dae4 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:01:57 +0200 Subject: [PATCH 08/31] feature: add custom item id to the final ItemStack --- .../re/sylfa/itemcreator/items/CustomItem.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index d9e4d8a..f0cd5bc 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -3,6 +3,10 @@ import lombok.Getter; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.StringTag; +import net.minecraft.world.item.component.CustomData; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; @Getter @@ -158,11 +162,18 @@ private Builder key(Key key) { // } CustomItem build() { - return item; + return this.customItem; } public Builder item(ItemStack item) { - this.item.item = item; + // add custom item ID into the customData component, to be used with resource packs + net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(item); + + CustomData customData = nmsCopy.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) + .update(compoundTag -> compoundTag.put("itemcreator:id", StringTag.valueOf(this.customItem.key.asString()))); + + nmsCopy.set(DataComponents.CUSTOM_DATA, customData); + this.customItem.item = nmsCopy.asBukkitCopy(); return this; } } From 1029bdb5cc2e04d4b2af3a99fdd0dc85d29d22a2 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:46:06 +0200 Subject: [PATCH 09/31] feature: NamedspacedKey parser + maxStackSize and itemModel components + some cleaning --- gradle/libs.versions.toml | 2 +- .../re/sylfa/itemcreator/ItemCreator.java | 4 +- .../deserializers/ItemDeserializer.java | 47 +++--- .../NamespacedKeyDeserializer.java | 21 +++ .../TextComponentDeserializer.java | 3 +- .../sylfa/itemcreator/items/CustomItem.java | 136 +----------------- .../sylfa/itemcreator/items/ItemReader.java | 87 +++-------- .../re/sylfa/itemcreator/util/JavaUtils.java | 12 ++ .../re/sylfa/itemcreator/util/Parsers.java | 52 +++++++ 9 files changed, 136 insertions(+), 228 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/util/JavaUtils.java create mode 100644 src/main/java/re/sylfa/itemcreator/util/Parsers.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2c0781d..6cc2c24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ runPaper = "2.3.1" lombok = "1.18.38" adventure = "4.20.0" commonsIo = "2.19.0" -jackson = "2.19.0-rc2" +jackson = "2.19.2" [libraries] brigadier = {group = "com.mojang", name = "brigadier", version.ref = "brigadier"} diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index acf5b40..0ca37c3 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -24,6 +24,7 @@ import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; +import re.sylfa.itemcreator.deserializers.NamespacedKeyDeserializer; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; @@ -100,7 +101,8 @@ private void registerMappers() { SimpleModule module = new SimpleModule(); module.addDeserializer(ItemStack.class, new ItemDeserializer()) .addDeserializer(Material.class, new MaterialDeserializer()) - .addDeserializer(Component.class, new TextComponentDeserializer()); + .addDeserializer(Component.class, new TextComponentDeserializer()) + .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java index e7b84d3..c91bc11 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java @@ -3,24 +3,19 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import net.kyori.adventure.text.Component; import net.minecraft.core.component.DataComponents; import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Parsers; import java.io.IOException; -import java.util.ArrayList; import java.util.List; public class ItemDeserializer extends StdDeserializer { - ObjectMapper mapper = ItemCreator.getMapper(); - public ItemDeserializer() { super(ItemStack.class); } @@ -28,8 +23,8 @@ public ItemDeserializer() { @Override public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { JsonNode node = jsonParser.readValueAsTree(); - Material material = parseMaterial(node); - + Material material = Parsers.parseMaterial(node, "material") + .orElse(Material.WOODEN_PICKAXE); var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); @@ -38,38 +33,36 @@ public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deser itemNms.remove(DataComponents.TOOL); itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); itemNms.remove(DataComponents.FOOD); + itemNms.remove(DataComponents.REPAIRABLE); ItemStack item = itemNms.asBukkitCopy(); parseItemName(node, item); parseLore(node, item); + parseMaxStackSize(node, item); + parseItemModel(node, item); return item; } - Material parseMaterial(JsonNode node) { - return mapper.convertValue(node.get("material"), Material.class); - } void parseLore(JsonNode node, ItemStack item) { - JsonNode loreNode = node.get("lore"); - if(loreNode != null && !loreNode.isEmpty()){ - List lore = new ArrayList<>(); - if(loreNode.isArray()) { - for(JsonNode line : loreNode) { - lore.add(mapper.convertValue(line, Component.class)); - } - } else { - lore.add(mapper.convertValue(loreNode, Component.class)); - } - item.editMeta(ItemMeta.class, meta -> meta.lore(lore)); - } + Parsers.getComponentArrayNodeValue(node, "lore") + .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); } void parseItemName(JsonNode node, ItemStack item) { - if(node.get("itemName").isTextual()) { - Component itemName = mapper.convertValue(node.get("itemName"), Component.class); - item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName)); - } + Parsers.getNodeComponentValue(node, "itemName") + .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); + } + + void parseMaxStackSize(JsonNode node, ItemStack item) { + Parsers.getNodeIntValue(node, "maxStackSize") + .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); + } + + void parseItemModel(JsonNode node, ItemStack item) { + Parsers.getNodeNamespacedKeyValue(node, "itemModel") + .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java new file mode 100644 index 0000000..5517862 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java @@ -0,0 +1,21 @@ +package re.sylfa.itemcreator.deserializers; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +public class NamespacedKeyDeserializer extends StdDeserializer { + public NamespacedKeyDeserializer() { + super(NamespacedKey.class); + } + + @Override + public @Nullable NamespacedKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String parsedString = p.getValueAsString(); + return NamespacedKey.fromString(parsedString); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java index b111851..5fd0553 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java @@ -1,6 +1,5 @@ package re.sylfa.itemcreator.deserializers; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -11,7 +10,7 @@ import java.util.Optional; public class TextComponentDeserializer extends StdDeserializer { - MiniMessage miniMessage = MiniMessage.miniMessage(); + private static final MiniMessage miniMessage = MiniMessage.miniMessage(); public TextComponentDeserializer() { super(Component.class); diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index f0cd5bc..18eb6a8 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -11,8 +11,6 @@ @Getter public class CustomItem { - private static final String ID = "id"; - private Key key; private ItemStack item; @@ -24,51 +22,7 @@ public void key(Key key) { this.key = key; } - // public int customModelData() { - // return customModelData; - // } - -// public List lore() { -// return lore; -// } -// -// public int maxDamage() { -// return maxDamage; -// } -// -// public int maxStackSize() { -// return maxStackSize; -// } -// -// public JukeboxPlayableComponent jukeboxPlayableComponent() { -// return jukeboxPlayableComponent; -// } - public ItemStack asItemStack() { -// var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); -// // setMaxDamage(null) only resets the *patched* value, not the actual one -// itemNms.remove(DataComponents.MAX_DAMAGE); -// itemNms.remove(DataComponents.TOOL); -// itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); -// itemNms.remove(DataComponents.FOOD); -// -// ItemStack item = itemNms.asBukkitCopy(); -// item.editMeta(Damageable.class, meta -> { -// meta.getPersistentDataContainer().set(NamespacedKey.fromString(ID, ItemCreator.getInstance()), PersistentDataType.STRING, key.asString()); -// meta.itemName(itemName); -// // meta.setCustomModelData(this.hasCustomModelData ? customModelData : null); -// meta.lore(lore); -// if(hasMaxDamage) { -// meta.setMaxDamage(maxDamage); -// } -// meta.setMaxStackSize(maxStackSize); -// meta.setJukeboxPlayable(jukeboxPlayableComponent); -// if(hasEnchantmentGlintOverride) { -// meta.setEnchantmentGlintOverride(enchantmentGlintOverride); -// } -// meta.setEquippable(equippableComponent); -// }); -// return item; } @@ -77,96 +31,16 @@ public static Builder builder(Key key) { } public static class Builder { - CustomItem item = new CustomItem(); - private MiniMessage mm = MiniMessage.miniMessage(); + CustomItem customItem = new CustomItem(); private Builder key(Key key) { - item.key = key; + customItem.key = key; return this; } - // Builder customModelData(int customModelData) { - // if(customModelData != 0) { - // item.customModelData = customModelData; - // item.hasCustomModelData = true; - // } - // return this; - // } - -// Builder itemName(String rawItemName) { -// return itemName(mm.deserialize(rawItemName)); -//// } -// -// Builder itemName(Component itemName) { -// item.itemName = itemName; -// return this; -// } - -// Builder material(Material material) { -// item.material = material; -// return this; -// } - -// Builder lore(List lore) { -// if(!lore.isEmpty()) { -// item.lore = lore.stream().map(line -> mm.deserialize(""+line+"")).toList(); -// } -// return this; -// } -// -// Builder maxDamage(int maxDamage) { -// if(maxDamage > 0) { -// item.maxDamage = maxDamage; -// item.hasMaxDamage = true; -// } else if (maxDamage < 0) { -// Log.warn("Negative max damage for %s", item.key.asString()); -// } -// -// return this; -// } -// -// Builder maxStackSize(int maxStackSize) { -// if(maxStackSize < 1 || maxStackSize > 99) { -// maxStackSize = 1; -// Log.warn(String.format("Max stack size for %s is not valid", item.key.asString())); -// } -// -// item.maxStackSize = maxStackSize; -// return this; -// } -// -// Builder jukeboxPlayableComponent(String rawJukeboxSong, boolean showInTooltip) { -// if(rawJukeboxSong == null) { -// return this; -// } -// JukeboxSong jukeboxSong = RegistryAccess.registryAccess().getRegistry(RegistryKey.JUKEBOX_SONG).get(Key.key(rawJukeboxSong)); -// if(jukeboxSong != null) { -// // HACK there's no JukeboxPlayableComponent constructor -// JukeboxPlayableComponent jukeboxPlayableComponent = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); -// jukeboxPlayableComponent.setSong(jukeboxSong); -//// jukeboxPlayableComponent.setShowInTooltip(showInTooltip); -// return jukeboxPlayableComponent(jukeboxPlayableComponent); -// } -// return this; -// } - -// Builder jukeboxPlayableComponent(JukeboxPlayableComponent jukeboxPlayableComponent) { -// item.jukeboxPlayableComponent = jukeboxPlayableComponent; -// return this; -// } -// -// Builder enchantmentGlintOverride(boolean set, boolean override) { -// item.hasEnchantmentGlintOverride = set; -// item.enchantmentGlintOverride = override; -// return this; -// } - - CustomItem build() { - return this.customItem; - } public Builder item(ItemStack item) { - // add custom item ID into the customData component, to be used with resource packs + // add custom item ID into the customData component net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(item); CustomData customData = nmsCopy.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) @@ -176,5 +50,9 @@ public Builder item(ItemStack item) { this.customItem.item = nmsCopy.asBukkitCopy(); return this; } + + CustomItem build() { + return this.customItem; + } } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 25c4085..8649ac3 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -1,17 +1,15 @@ package re.sylfa.itemcreator.items; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import net.kyori.adventure.key.Key; -import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.bukkit.inventory.ItemStack; import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; import java.io.File; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -28,30 +26,30 @@ public static List readAllItems() { return List.of(); } - return Arrays.stream(itemsFolder.listFiles()) - .filter(file -> { - if(!file.isDirectory()) { - Log.warn("%s is not an item namespace", file.getName()); - return false; - } else { - return true; - } - }) - .flatMap(folder -> { - String folderName = folder.getName(); - return Arrays.stream(Objects.requireNonNull(folder.listFiles())) + return JavaUtils.arrayStream(itemsFolder.listFiles()) .filter(file -> { - if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { - Log.warn("%s is not an item file in %s", file.getName(), folderName); + if(!file.isDirectory()) { + Log.warn("%s is not an item namespace", file.getName()); return false; } else { return true; } }) - .map(ItemReader::readItem) - .filter(Objects::nonNull); - }) - .toList(); + .flatMap(folder -> { + String folderName = folder.getName(); + return Arrays.stream(Objects.requireNonNull(folder.listFiles())) + .filter(file -> { + if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { + Log.warn("%s is not an item file in %s", file.getName(), folderName); + return false; + } else { + return true; + } + }) + .map(ItemReader::readItem) + .filter(Objects::nonNull); + }) + .toList(); } public static CustomItem readItem(File file) { @@ -72,61 +70,14 @@ public static CustomItem readItem(File file) { } Key key = Key.key(parentName, fileName); -// try { try { ItemStack item = mapper.readValue(file, ItemStack.class); - return CustomItem.builder(key) .item(item) .build(); } catch (IOException e) { throw new RuntimeException(e); } -// return readItemJson(readFile(file), parentName, fileName); -// return null; -// } catch (IOException e) { -// e.printStackTrace(); -// return null; -// } - } - - public static CustomItem readItemJson(String config, String folderName, String fileName) { - - - Key key = Key.key(folderName, fileName); - Log.log("Reading item %s", key.asString()); - - try { - CustomItem item = mapper.readValue(config, CustomItem.class); - Log.log(item.toString()); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - -// CustomItem item = ItemCreator.getGson().fromJson(config, CustomItem.class); -// item.key(key); -// return item; - return null; - } - - // public static CustomItem readItem(YamlConfiguration config, String folderName, String fileName) { - // Key key = Key.key(folderName, fileName); - // Log.log("Reading item %s", key.asString()); - - // return CustomItem.Builder.builder(key) - // .customModelData(config.getInt("model")) - // .itemName(config.getString("name", "")) - // .material(Material.matchMaterial(config.getString("material", "diamond_pickaxe"))) - // .lore(config.getStringList("lore")) - // .maxDamage(config.getInt("maxDamage")) - // .maxStackSize(config.getInt("maxStackSize", 1)) - // .jukeboxPlayableComponent(config.getString("jukeboxSong.name"), config.getBoolean("jukeboxSong.showInTooltip", true)) - // .enchantmentGlintOverride(config.getBoolean("enchantmentGlintOverride.set"), config.getBoolean("enchantmentGlintOverride.value")) - // .build(); - // } - - private static String readFile(File file) throws IOException { - return FileUtils.readFileToString(file, Charset.defaultCharset()); } } diff --git a/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java new file mode 100644 index 0000000..af2211d --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java @@ -0,0 +1,12 @@ +package re.sylfa.itemcreator.util; + +import java.util.Arrays; +import java.util.stream.Stream; + +public interface JavaUtils { + static Stream arrayStream(T[] array) { + return array != null + ? Arrays.stream(array) + : Stream.empty(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java new file mode 100644 index 0000000..33fcbe2 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -0,0 +1,52 @@ +package re.sylfa.itemcreator.util; + +import com.fasterxml.jackson.databind.JsonNode; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.checkerframework.checker.nullness.qual.NonNull; +import re.sylfa.itemcreator.ItemCreator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +public interface Parsers { + + static Optional getNodeValue(@NonNull JsonNode node, @NonNull String valueName, @NonNull Predicate nodePredicate, @NonNull Class valueClass) { + JsonNode foundNode = node.get(valueName); + return foundNode != null && nodePredicate.test(foundNode) + ? Optional.ofNullable(ItemCreator.getMapper().convertValue(foundNode, valueClass)) + : Optional.empty(); + } + + static Optional parseMaterial(JsonNode node, String valueName) { + return getNodeValue(node, valueName, JsonNode::isTextual, Material.class); + } + + static Optional getComponentArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + JsonNode foundNode = node.get(valueName); + if(foundNode == null || !foundNode.isArray()) return Optional.empty(); + List components = new ArrayList<>(); + for(JsonNode line : foundNode) { + + components.add(ItemCreator.getMapper().convertValue(line, Component.class)); + } + return components.isEmpty() + ? Optional.empty() + : Optional.of(components.toArray(Component[]::new)); + } + + static Optional getNodeIntValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isIntegralNumber, Integer.class); + } + + static Optional getNodeComponentValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isTextual, Component.class); + } + + static Optional getNodeNamespacedKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isTextual, NamespacedKey.class); + } +} From 6a18d1f439e03880791587faea40560aa6bce7a1 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 19 Jul 2025 23:54:57 +0200 Subject: [PATCH 10/31] chore: removed file from test server --- run/plugins/ItemCreator/items/truc/truc.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 run/plugins/ItemCreator/items/truc/truc.json diff --git a/run/plugins/ItemCreator/items/truc/truc.json b/run/plugins/ItemCreator/items/truc/truc.json deleted file mode 100644 index 6b6cb5a..0000000 --- a/run/plugins/ItemCreator/items/truc/truc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "material": "minecraft:brick", - "itemName": "oeeeeeeeeeeeeee trop cool" -} \ No newline at end of file From dd8db699a4a31224974b313e17c1145506648132 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 00:43:37 +0200 Subject: [PATCH 11/31] feature: also use json for recipes --- .../sylfa/itemcreator/items/CustomItem.java | 1 - .../sylfa/itemcreator/items/ItemReader.java | 2 +- .../itemcreator/recipes/CustomRecipe.java | 61 ++++++++++--------- .../itemcreator/recipes/RecipeReader.java | 43 ++++++++----- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index 18eb6a8..3762e4a 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -2,7 +2,6 @@ import lombok.Getter; import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.minimessage.MiniMessage; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.StringTag; import net.minecraft.world.item.component.CustomData; diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 8649ac3..87353dd 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -37,7 +37,7 @@ public static List readAllItems() { }) .flatMap(folder -> { String folderName = folder.getName(); - return Arrays.stream(Objects.requireNonNull(folder.listFiles())) + return JavaUtils.arrayStream(Objects.requireNonNull(folder.listFiles())) .filter(file -> { if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { Log.warn("%s is not an item file in %s", file.getName(), folderName); diff --git a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java index d79d49c..7f74eee 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java @@ -1,33 +1,21 @@ package re.sylfa.itemcreator.recipes; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - +import net.kyori.adventure.key.Key; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.inventory.BlastingRecipe; -import org.bukkit.inventory.CampfireRecipe; -import org.bukkit.inventory.FurnaceRecipe; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.RecipeChoice; -import org.bukkit.inventory.ShapedRecipe; -import org.bukkit.inventory.ShapelessRecipe; -import org.bukkit.inventory.SmithingTransformRecipe; -import org.bukkit.inventory.SmokingRecipe; -import org.bukkit.inventory.StonecuttingRecipe; - -import net.kyori.adventure.key.Key; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.bukkit.inventory.*; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.value.qual.ArrayLenRange; -import org.jetbrains.annotations.NotNull; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.items.ItemRegistry; import re.sylfa.itemcreator.util.Log; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + public class CustomRecipe { String keyValue; RecipeType type; @@ -79,21 +67,21 @@ public Recipe asRecipe() { case STONECUTTING: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new StonecuttingRecipe(key(), result, ingredient); case FURNACE: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new FurnaceRecipe(key(), result, ingredient, cookingExperience, cookingTime); case CAMPFIRE: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new CampfireRecipe(key(), result, ingredient, cookingExperience, cookingTime); @@ -178,7 +166,7 @@ Builder shapedIngredients(Map shapedIngredients) { } Builder ingredients(List ingredients) { - if(ingredients.size() > 9) { + if(ingredients == null || ingredients.size() > 9) { Log.warn("Too many ingredients for recipe %s", recipe.keyValue); return this; } @@ -197,8 +185,8 @@ Builder ingredients(List ingredients) { return this; } - Builder ingredient(@NonNull String ingredient) { - if(ingredient.isBlank()) { + Builder ingredient(@Nullable String ingredient) { + if(ingredient == null || ingredient.isBlank()) { return this; } @@ -213,6 +201,9 @@ Builder ingredient(@NonNull String ingredient) { } Builder result(String result, int amount) { + if(result == null || result.isBlank()) { + return this; + } if(amount < 1 || amount > 99) { Log.warn("Bad result amount for %s: %d", recipe.keyValue, amount); return this; @@ -228,6 +219,10 @@ Builder result(String result, int amount) { } Builder shape(@ArrayLenRange(to = 3) String[] shape) { + if(shape == null) { + return this; + } + if(shape.length > 3) { Log.warn("Shape for %s has too many lines", recipe.keyValue); return this; @@ -244,6 +239,9 @@ Builder shape(@ArrayLenRange(to = 3) String[] shape) { } Builder cooking(float experience, int cookingTime) { + if(experience == 0 && cookingTime == 0) { + return this; + } if(experience < 0) { Log.warn("Furnace experience is negative in recipe %s", recipe.keyValue); return this; @@ -257,10 +255,13 @@ Builder cooking(float experience, int cookingTime) { return this; } - Builder smithing(@NotNull String rawBase, - @NonNull String rawAddition, - @NotNull String rawTemplate) { + Builder smithing(String rawBase, + String rawAddition, + String rawTemplate) { + if(rawBase == null && rawAddition == null && rawTemplate == null) { + return this; + } ItemStack base = itemRegistry.parse(Key.key(rawBase)); if(base == null) { diff --git a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java index 0f25d7f..dc00211 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java @@ -1,17 +1,17 @@ package re.sylfa.itemcreator.recipes; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.apache.commons.io.FilenameUtils; import org.bukkit.configuration.file.YamlConfiguration; - import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + public class RecipeReader { public static List readAllRecipes() { @@ -22,16 +22,31 @@ public static List readAllRecipes() { return List.of(); } - return Arrays.stream(recipesFolder.listFiles()) - .map(file -> { - if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { - Log.warn("%s is not an recipe file", file.getName()); - return null; + return JavaUtils.arrayStream(recipesFolder.listFiles()) + .filter(file -> { + if(!file.isDirectory()) { + Log.warn("%s is not a recipe namespace", file.getName()); + return false; } else { - return RecipeReader.readRecipe(file); + return true; } }) - .filter(file -> file != null) + .flatMap(folder -> { + String folderName = folder.getName(); + return JavaUtils.arrayStream(folder.listFiles()) + .filter(file -> { +// if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { + if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { + Log.warn("%s is not an item file in %s", file.getName(), folderName); + return false; + } else { + return true; + } + }) + .map(RecipeReader::readRecipe); + }) + + .filter(Objects::nonNull) .toList(); } From 65d6a27bef400980b5793604e348e7f50b7886ef Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 12:56:53 +0200 Subject: [PATCH 12/31] feature: started Equippable deserializer --- .../re/sylfa/itemcreator/ItemCreator.java | 32 ++++++------ .../components/EquippableDeserializer.java | 50 +++++++++++++++++++ .../components/ToolDeserializer.java | 28 +++++++++++ .../{ => types}/ItemDeserializer.java | 28 +++++++++-- .../deserializers/types/KeyDeserializer.java | 18 +++++++ .../{ => types}/MaterialDeserializer.java | 2 +- .../NamespacedKeyDeserializer.java | 5 +- .../TextComponentDeserializer.java | 2 +- .../itemcreator/recipes/CustomRecipe.java | 2 +- .../re/sylfa/itemcreator/util/Parsers.java | 15 +++++- 10 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java rename src/main/java/re/sylfa/itemcreator/deserializers/{ => types}/ItemDeserializer.java (68%) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java rename src/main/java/re/sylfa/itemcreator/deserializers/{ => types}/MaterialDeserializer.java (92%) rename src/main/java/re/sylfa/itemcreator/deserializers/{ => types}/NamespacedKeyDeserializer.java (79%) rename src/main/java/re/sylfa/itemcreator/deserializers/{ => types}/TextComponentDeserializer.java (94%) diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 0ca37c3..901cb83 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -1,41 +1,42 @@ package re.sylfa.itemcreator; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import lombok.Getter; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.ToolComponent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.permissions.CommandPermissions; - -import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; -import re.sylfa.itemcreator.deserializers.NamespacedKeyDeserializer; +import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; +import re.sylfa.itemcreator.deserializers.types.*; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; -import re.sylfa.itemcreator.deserializers.TextComponentDeserializer; -import re.sylfa.itemcreator.deserializers.ItemDeserializer; -import re.sylfa.itemcreator.deserializers.MaterialDeserializer; import re.sylfa.itemcreator.recipes.CustomRecipe; import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; import re.sylfa.itemcreator.util.Log; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class ItemCreator extends JavaPlugin { @Getter @@ -102,7 +103,10 @@ private void registerMappers() { module.addDeserializer(ItemStack.class, new ItemDeserializer()) .addDeserializer(Material.class, new MaterialDeserializer()) .addDeserializer(Component.class, new TextComponentDeserializer()) - .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()); + .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()) + .addDeserializer(EquippableComponent.class, new EquippableDeserializer()) + .addDeserializer(Key.class, new KeyDeserializer()) + .addDeserializer(ToolComponent.class, new ToolDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java new file mode 100644 index 0000000..4741682 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java @@ -0,0 +1,50 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Optional; + +public class EquippableDeserializer extends StdDeserializer { + public EquippableDeserializer() { + super(EquippableComponent.class); + } + + @Override + public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + JsonNode slotNode = node.get("slot"); + EquipmentSlot slot; + if(slotNode != null && slotNode.isTextual()) { + slot = ItemCreator.getMapper().convertValue(slotNode, EquipmentSlot.class); + if(slot == null) { + Log.error("%s is not a valid slot type", slotNode.textValue()); + } + + } else { + return null; + } + + + + EquippableComponent equippableComponent = new ItemStack(Material.STONE).getItemMeta().getEquippable(); + Parsers.getBooleanNodeValue(node, "dispensable").ifPresent(equippableComponent::setDispensable); + Parsers.getBooleanNodeValue(node, "damageOnHurt").ifPresent(damage -> equippableComponent.setDamageOnHurt(damage)); + Parsers.getBooleanNodeValue(node, "equipOnInteract").ifPresent(equippableComponent::setEquipOnInteract); + Parsers.getNodeNamespacedKeyValue(node, "model").ifPresent(equippableComponent::setModel); + Parsers.getNodeNamespacedKeyValue(node, "cameraOverlay").ifPresent(equippableComponent::setCameraOverlay); + Optional.ofNullable(slot).ifPresent(equippableComponent::setSlot); + + return equippableComponent; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java new file mode 100644 index 0000000..173c2ed --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -0,0 +1,28 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; + +public class ToolDeserializer extends StdDeserializer { + + public ToolDeserializer() { super(ToolComponent.class); } + + @Override + public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + ToolComponent toolComponent = new ItemStack(Material.STONE).getItemMeta().getTool(); + Parsers.getNodeIntValue(node, "damagePerBlock").ifPresent( + damage -> toolComponent.setDamagePerBlock(damage) + ); + + return toolComponent; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java similarity index 68% rename from src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index c91bc11..caadcf4 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -1,4 +1,4 @@ -package re.sylfa.itemcreator.deserializers; +package re.sylfa.itemcreator.deserializers.types; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; @@ -8,7 +8,10 @@ import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.ToolComponent; import re.sylfa.itemcreator.util.Parsers; import java.io.IOException; @@ -23,7 +26,7 @@ public ItemDeserializer() { @Override public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { JsonNode node = jsonParser.readValueAsTree(); - Material material = Parsers.parseMaterial(node, "material") + Material material = Parsers.getMaterialValue(node, "material") .orElse(Material.WOODEN_PICKAXE); var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); @@ -31,6 +34,7 @@ public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deser // setMaxDamage(null) only resets the *patched* value, not the actual one itemNms.remove(DataComponents.MAX_DAMAGE); itemNms.remove(DataComponents.TOOL); + itemNms.remove(DataComponents.WEAPON); itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); itemNms.remove(DataComponents.FOOD); itemNms.remove(DataComponents.REPAIRABLE); @@ -41,7 +45,9 @@ public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deser parseLore(node, item); parseMaxStackSize(node, item); parseItemModel(node, item); - + parseEquippable(node, item); + parseMaxDamage(node, item); + parseTool(node, item); return item; } @@ -65,4 +71,20 @@ void parseItemModel(JsonNode node, ItemStack item) { Parsers.getNodeNamespacedKeyValue(node, "itemModel") .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); } + + void parseEquippable(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) + .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); + } + + void parseMaxDamage(JsonNode node, ItemStack item) { + Parsers.getNodeIntValue(node, "maxDamage") + .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> { + itemMeta.setMaxDamage(maxDamage); + })); + } + + void parseTool(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class); + } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java new file mode 100644 index 0000000..ad52781 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java @@ -0,0 +1,18 @@ +package re.sylfa.itemcreator.deserializers.types; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.kyori.adventure.key.Key; + +import java.io.IOException; + +public class KeyDeserializer extends StdDeserializer { + + public KeyDeserializer() { super(Key.class); } + + @Override + public Key deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return Key.key(p.getValueAsString()); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java similarity index 92% rename from src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java index 5eba8cd..f1091dc 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/MaterialDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java @@ -1,4 +1,4 @@ -package re.sylfa.itemcreator.deserializers; +package re.sylfa.itemcreator.deserializers.types; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java similarity index 79% rename from src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java index 5517862..d5ba363 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/NamespacedKeyDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java @@ -1,4 +1,4 @@ -package re.sylfa.itemcreator.deserializers; +package re.sylfa.itemcreator.deserializers.types; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; @@ -15,7 +15,6 @@ public NamespacedKeyDeserializer() { @Override public @Nullable NamespacedKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String parsedString = p.getValueAsString(); - return NamespacedKey.fromString(parsedString); + return NamespacedKey.fromString(p.getValueAsString()); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java similarity index 94% rename from src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java rename to src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java index 5fd0553..5ecdde8 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/TextComponentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java @@ -1,4 +1,4 @@ -package re.sylfa.itemcreator.deserializers; +package re.sylfa.itemcreator.deserializers.types; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; diff --git a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java index 7f74eee..dcd500e 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java @@ -259,7 +259,7 @@ Builder smithing(String rawBase, String rawAddition, String rawTemplate) { - if(rawBase == null && rawAddition == null && rawTemplate == null) { + if(rawBase == null || rawAddition == null || rawTemplate == null) { return this; } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 33fcbe2..f00b648 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -1,6 +1,7 @@ package re.sylfa.itemcreator.util; import com.fasterxml.jackson.databind.JsonNode; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.NamespacedKey; @@ -21,7 +22,15 @@ static Optional getNodeValue(@NonNull JsonNode node, @NonNull String valu : Optional.empty(); } - static Optional parseMaterial(JsonNode node, String valueName) { + static Optional getStringNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isTextual, String.class); + } + + static Optional getBooleanNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isBoolean, Boolean.class); + } + + static Optional getMaterialValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, Material.class); } @@ -49,4 +58,8 @@ static Optional getNodeComponentValue(@NonNull JsonNode node, @NonNul static Optional getNodeNamespacedKeyValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, NamespacedKey.class); } + + static Optional getNodeKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isTextual, Key.class); + } } From 7698e59e7f79f7dca90ee308e3f92c8123047ec4 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:02:34 +0200 Subject: [PATCH 13/31] feature: tool rules --- .../re/sylfa/itemcreator/ItemCreator.java | 4 ++- .../components/ToolDeserializer.java | 12 ++++--- .../components/ToolRuleDeserializer.java | 34 +++++++++++++++++++ .../deserializers/types/ItemDeserializer.java | 9 +++-- .../re/sylfa/itemcreator/util/Parsers.java | 30 ++++++++++++++-- 5 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 901cb83..b84bf32 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -24,6 +24,7 @@ import re.sylfa.itemcreator.commands.ItemCreatorCommand; import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; import re.sylfa.itemcreator.deserializers.types.*; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; @@ -106,7 +107,8 @@ private void registerMappers() { .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()) .addDeserializer(EquippableComponent.class, new EquippableDeserializer()) .addDeserializer(Key.class, new KeyDeserializer()) - .addDeserializer(ToolComponent.class, new ToolDeserializer()); + .addDeserializer(ToolComponent.class, new ToolDeserializer()) + .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java index 173c2ed..3b08b61 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -13,15 +13,19 @@ public class ToolDeserializer extends StdDeserializer { - public ToolDeserializer() { super(ToolComponent.class); } + public ToolDeserializer() { + super(ToolComponent.class); + } @Override public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.readValueAsTree(); ToolComponent toolComponent = new ItemStack(Material.STONE).getItemMeta().getTool(); - Parsers.getNodeIntValue(node, "damagePerBlock").ifPresent( - damage -> toolComponent.setDamagePerBlock(damage) - ); + Parsers.getNodeIntValue(node, "damagePerBlock").ifPresent(toolComponent::setDamagePerBlock); + Parsers.getFloatNodeValue(node, "defaultMiningSpeed").ifPresent(toolComponent::setDefaultMiningSpeed); + Parsers.getToolRulesArrayNodeValue(node, "rules") + .ifPresent(rulesList -> rulesList.forEach(ruleValue -> toolComponent.addRule(ruleValue.block(), ruleValue.speed(), ruleValue.correctForDrops()))); + return toolComponent; } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java new file mode 100644 index 0000000..2234200 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java @@ -0,0 +1,34 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Optional; + +public class ToolRuleDeserializer extends StdDeserializer { + public ToolRuleDeserializer() { super(ToolComponent.ToolRule.class); } + + @Override + public RuleValues deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + Optional blockMaterial = Parsers.getMaterialNodeValue(node, "block"); + Optional speed = Parsers.getFloatNodeValue(node, "speed"); + Optional correctForDrops = Parsers.getBooleanNodeValue(node, "correctForDrops"); + if(blockMaterial.isEmpty() || speed.isEmpty()) { + return null; + } + return new RuleValues( + blockMaterial.get(), + speed.get(), + correctForDrops.orElse(false) + ); + } + + public record RuleValues(Material block, float speed, boolean correctForDrops) {} +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index caadcf4..9d1a1b6 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -26,7 +26,7 @@ public ItemDeserializer() { @Override public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { JsonNode node = jsonParser.readValueAsTree(); - Material material = Parsers.getMaterialValue(node, "material") + Material material = Parsers.getMaterialNodeValue(node, "material") .orElse(Material.WOODEN_PICKAXE); var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); @@ -79,12 +79,11 @@ void parseEquippable(JsonNode node, ItemStack item) { void parseMaxDamage(JsonNode node, ItemStack item) { Parsers.getNodeIntValue(node, "maxDamage") - .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> { - itemMeta.setMaxDamage(maxDamage); - })); + .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); } void parseTool(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class); + Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) + .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); } } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index f00b648..9dfc098 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -7,6 +7,7 @@ import org.bukkit.NamespacedKey; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; import java.util.ArrayList; import java.util.List; @@ -22,6 +23,10 @@ static Optional getNodeValue(@NonNull JsonNode node, @NonNull String valu : Optional.empty(); } + static Optional getFloatNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isNumber, Float.class); + } + static Optional getStringNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, String.class); } @@ -30,13 +35,15 @@ static Optional getBooleanNodeValue(@NonNull JsonNode node, @NonNull St return getNodeValue(node, valueName, JsonNode::isBoolean, Boolean.class); } - static Optional getMaterialValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getMaterialNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, Material.class); } static Optional getComponentArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { JsonNode foundNode = node.get(valueName); - if(foundNode == null || !foundNode.isArray()) return Optional.empty(); + if(foundNode == null || !foundNode.isArray()) { + return Optional.empty(); + } List components = new ArrayList<>(); for(JsonNode line : foundNode) { @@ -47,6 +54,25 @@ static Optional getComponentArrayNodeValue(@NonNull JsonNode node, : Optional.of(components.toArray(Component[]::new)); } + static Optional> getToolRulesArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + JsonNode foundNode = node.get(valueName); + if(foundNode == null || !foundNode.isArray()) { + return Optional.empty(); + } + + List ruleValues = new ArrayList<>(); + for(JsonNode rule : foundNode) { + if(!rule.isObject()) { + continue; + } + ToolRuleDeserializer.RuleValues ruleValue = ItemCreator.getMapper().convertValue(rule, ToolRuleDeserializer.RuleValues.class); + if(ruleValue != null) { + ruleValues.add(ruleValue); + } + } + return Optional.of(ruleValues); + } + static Optional getNodeIntValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isIntegralNumber, Integer.class); } From 2c6dd499665fa6e46262987cbd0f30e8511cacce Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 17:07:15 +0200 Subject: [PATCH 14/31] feature: glider, enchantment glint override, rarity and jukebox playable components --- .../re/sylfa/itemcreator/ItemCreator.java | 5 +++- .../JukeboxPlayableDeserializer.java | 29 +++++++++++++++++++ .../deserializers/types/ItemDeserializer.java | 26 +++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index b84bf32..3ef2f55 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -14,6 +14,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; import org.bukkit.inventory.meta.components.ToolComponent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; @@ -23,6 +24,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; +import re.sylfa.itemcreator.deserializers.components.JukeboxPlayableDeserializer; import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; import re.sylfa.itemcreator.deserializers.types.*; @@ -108,7 +110,8 @@ private void registerMappers() { .addDeserializer(EquippableComponent.class, new EquippableDeserializer()) .addDeserializer(Key.class, new KeyDeserializer()) .addDeserializer(ToolComponent.class, new ToolDeserializer()) - .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()); + .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()) + .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java new file mode 100644 index 0000000..7a2a470 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java @@ -0,0 +1,29 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; + +import java.io.IOException; +import java.util.Optional; + +public class JukeboxPlayableDeserializer extends StdDeserializer { + public JukeboxPlayableDeserializer() { super(JukeboxPlayableComponent.class); } + + @Override + public JukeboxPlayableComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Optional songKey = Optional.ofNullable(NamespacedKey.fromString(p.getValueAsString())); + if(songKey.isEmpty()) { + return null; + } + + JukeboxPlayableComponent component = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); + component.setSongKey(songKey.get()); + + return component; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index 9d1a1b6..9accc2b 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -7,10 +7,12 @@ import net.minecraft.core.component.DataComponents; import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemRarity; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; import org.bukkit.inventory.meta.components.ToolComponent; import re.sylfa.itemcreator.util.Parsers; @@ -48,6 +50,10 @@ public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deser parseEquippable(node, item); parseMaxDamage(node, item); parseTool(node, item); + parseGlider(node, item); + parseEnchantmentGlintOverride(node, item); + parseRarity(node, item); + parseJukeboxPlayable(node, item); return item; } @@ -86,4 +92,24 @@ void parseTool(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); } + + void parseGlider(JsonNode node, ItemStack item) { + Parsers.getBooleanNodeValue(node, "glider") + .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); + } + + void parseEnchantmentGlintOverride(JsonNode node, ItemStack item) { + Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") + .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); + } + + void parseRarity(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) + .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); + } + + void parseJukeboxPlayable(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) + .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + } } From ffb9f291df0a353896a3beb05fff531f3ec61b62 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 20:31:53 +0200 Subject: [PATCH 15/31] feature: food + consumable components, and item deserializer --- .../re/sylfa/itemcreator/ItemCreator.java | 14 +- .../components/ConsumableDeserializer.java | 30 ++++ .../components/EquippableDeserializer.java | 3 + .../components/FoodDeserializer.java | 31 ++++ .../components/ToolDeserializer.java | 3 + .../types/CustomItemDeserializer.java | 145 ++++++++++++++++++ .../deserializers/types/ItemDeserializer.java | 125 ++++----------- .../sylfa/itemcreator/items/CustomItem.java | 14 +- .../sylfa/itemcreator/items/ItemReader.java | 9 +- .../re/sylfa/itemcreator/util/Parsers.java | 5 + 10 files changed, 264 insertions(+), 115 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 3ef2f55..f088da7 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import lombok.Getter; @@ -14,6 +15,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.FoodComponent; import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; import org.bukkit.inventory.meta.components.ToolComponent; import org.bukkit.permissions.Permission; @@ -23,10 +25,7 @@ import org.bukkit.util.permissions.CommandPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; -import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; -import re.sylfa.itemcreator.deserializers.components.JukeboxPlayableDeserializer; -import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; -import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; +import re.sylfa.itemcreator.deserializers.components.*; import re.sylfa.itemcreator.deserializers.types.*; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemReader; @@ -103,7 +102,8 @@ private void registerCommands() { private void registerMappers() { SimpleModule module = new SimpleModule(); - module.addDeserializer(ItemStack.class, new ItemDeserializer()) + module.addDeserializer(CustomItem.class, new CustomItemDeserializer()) + .addDeserializer(ItemStack.class, new ItemDeserializer()) .addDeserializer(Material.class, new MaterialDeserializer()) .addDeserializer(Component.class, new TextComponentDeserializer()) .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()) @@ -111,7 +111,9 @@ private void registerMappers() { .addDeserializer(Key.class, new KeyDeserializer()) .addDeserializer(ToolComponent.class, new ToolDeserializer()) .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()) - .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()); + .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()) + .addDeserializer(Consumable.class, new ConsumableDeserializer()) + .addDeserializer(FoodComponent.class, new FoodDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java new file mode 100644 index 0000000..54520dc --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java @@ -0,0 +1,30 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; + +public class ConsumableDeserializer extends StdDeserializer { + public ConsumableDeserializer() { super(Consumable.class); } + + @Override + public Consumable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + Consumable.Builder builder = Consumable.consumable(); + Parsers.getNodeValue(node, "animation", JsonNode::isTextual, ItemUseAnimation.class) + .ifPresent(builder::animation); + Parsers.getFloatNodeValue(node, "consumeSeconds").ifPresent(builder::consumeSeconds); + Parsers.getBooleanNodeValue(node, "hasConsumeParticles").ifPresent(builder::hasConsumeParticles); + Parsers.getNodeKeyValue(node, "sound").ifPresent(builder::sound); + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java index 4741682..60e661c 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java @@ -23,6 +23,9 @@ public EquippableDeserializer() { @Override public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } JsonNode slotNode = node.get("slot"); EquipmentSlot slot; if(slotNode != null && slotNode.isTextual()) { diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java new file mode 100644 index 0000000..eba3cf0 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java @@ -0,0 +1,31 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.FoodComponent; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; + +public class FoodDeserializer extends StdDeserializer { + + public FoodDeserializer() { super(FoodComponent.class); } + + @Override + public FoodComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + FoodComponent component = new ItemStack(Material.STONE).getItemMeta().getFood(); + Parsers.getBooleanNodeValue(node, "canAlwaysEat").ifPresent(component::setCanAlwaysEat); + Parsers.getNodeIntValue(node, "nutrition").ifPresent(component::setNutrition); + Parsers.getFloatNodeValue(node, "saturation").ifPresent(component::setSaturation); + return component; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java index 3b08b61..37f0489 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -20,6 +20,9 @@ public ToolDeserializer() { @Override public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } ToolComponent toolComponent = new ItemStack(Material.STONE).getItemMeta().getTool(); Parsers.getNodeIntValue(node, "damagePerBlock").ifPresent(toolComponent::setDamagePerBlock); Parsers.getFloatNodeValue(node, "defaultMiningSpeed").ifPresent(toolComponent::setDefaultMiningSpeed); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java new file mode 100644 index 0000000..0d4b44c --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -0,0 +1,145 @@ +package re.sylfa.itemcreator.deserializers.types; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.Consumable; +import net.minecraft.core.component.DataComponents; +import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemRarity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.FoodComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.items.CustomItem; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.List; + +public class CustomItemDeserializer extends StdDeserializer { + + public CustomItemDeserializer() { + super(CustomItem.class); + } + + @Override + public CustomItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode node = jsonParser.readValueAsTree(); + if(node == null) { + return null; + } + Material material = Parsers.getMaterialNodeValue(node, "material") + .orElse(Material.WOODEN_PICKAXE); + + var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); + + // setMaxDamage(null) only resets the *patched* value, not the actual one + itemNms.remove(DataComponents.MAX_DAMAGE); + itemNms.remove(DataComponents.TOOL); + itemNms.remove(DataComponents.WEAPON); + itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); + itemNms.remove(DataComponents.FOOD); + itemNms.remove(DataComponents.REPAIRABLE); + ItemStack item = itemNms.asBukkitCopy(); + + + parseItemName(node, item); + parseLore(node, item); + parseMaxStackSize(node, item); + parseItemModel(node, item); + parseEquippable(node, item); + parseMaxDamage(node, item); + parseTool(node, item); + parseGlider(node, item); + parseEnchantmentGlintOverride(node, item); + parseRarity(node, item); + parseJukeboxPlayable(node, item); + parseAmount(node, item); + parseUseRemainder(node, item); + parseFood(node, item); + parseConsumable(node, item); + + return new CustomItem(item); + } + + void parseConsumable(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) + .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); + } + + void parseFood(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) + .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); + } + + void parseAmount(JsonNode node, ItemStack item) { + Parsers.getNodeIntValue(node, "amount").ifPresent(item::setAmount); + } + + void parseLore(JsonNode node, ItemStack item) { + Parsers.getComponentArrayNodeValue(node, "lore") + .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); + } + + void parseItemName(JsonNode node, ItemStack item) { + Parsers.getNodeComponentValue(node, "itemName") + .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); + } + + void parseMaxStackSize(JsonNode node, ItemStack item) { + Parsers.getNodeIntValue(node, "maxStackSize") + .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); + } + + void parseItemModel(JsonNode node, ItemStack item) { + Parsers.getNodeNamespacedKeyValue(node, "itemModel") + .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); + } + + void parseEquippable(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) + .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); + } + + void parseMaxDamage(JsonNode node, ItemStack item) { + Parsers.getNodeIntValue(node, "maxDamage") + .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); + } + + void parseTool(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) + .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); + } + + void parseGlider(JsonNode node, ItemStack item) { + Parsers.getBooleanNodeValue(node, "glider") + .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); + } + + void parseEnchantmentGlintOverride(JsonNode node, ItemStack item) { + Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") + .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); + } + + void parseRarity(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) + .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); + } + + void parseJukeboxPlayable(JsonNode node, ItemStack item) { + Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) + .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + } + + void parseUseRemainder(JsonNode node, ItemStack item) { + Parsers.getNodeItemStackValue(node, "useRemainder") + .ifPresent(remainder -> item.editMeta(itemMeta -> itemMeta.setUseRemainder(remainder))); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index 9accc2b..ca3be6d 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -4,112 +4,39 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import net.minecraft.core.component.DataComponents; -import org.bukkit.Material; -import org.bukkit.craftbukkit.inventory.CraftItemStack; -import org.bukkit.inventory.ItemRarity; +import net.kyori.adventure.key.Key; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.components.EquippableComponent; -import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; -import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Parsers; import java.io.IOException; -import java.util.List; +import java.util.Optional; public class ItemDeserializer extends StdDeserializer { - - public ItemDeserializer() { - super(ItemStack.class); - } + public ItemDeserializer() { super(ItemStack.class); } @Override - public ItemStack deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - JsonNode node = jsonParser.readValueAsTree(); - Material material = Parsers.getMaterialNodeValue(node, "material") - .orElse(Material.WOODEN_PICKAXE); - - var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); - - // setMaxDamage(null) only resets the *patched* value, not the actual one - itemNms.remove(DataComponents.MAX_DAMAGE); - itemNms.remove(DataComponents.TOOL); - itemNms.remove(DataComponents.WEAPON); - itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); - itemNms.remove(DataComponents.FOOD); - itemNms.remove(DataComponents.REPAIRABLE); - ItemStack item = itemNms.asBukkitCopy(); - - - parseItemName(node, item); - parseLore(node, item); - parseMaxStackSize(node, item); - parseItemModel(node, item); - parseEquippable(node, item); - parseMaxDamage(node, item); - parseTool(node, item); - parseGlider(node, item); - parseEnchantmentGlintOverride(node, item); - parseRarity(node, item); - parseJukeboxPlayable(node, item); - return item; - } - - - void parseLore(JsonNode node, ItemStack item) { - Parsers.getComponentArrayNodeValue(node, "lore") - .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); - } - - void parseItemName(JsonNode node, ItemStack item) { - Parsers.getNodeComponentValue(node, "itemName") - .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); - } - - void parseMaxStackSize(JsonNode node, ItemStack item) { - Parsers.getNodeIntValue(node, "maxStackSize") - .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); - } - - void parseItemModel(JsonNode node, ItemStack item) { - Parsers.getNodeNamespacedKeyValue(node, "itemModel") - .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); - } - - void parseEquippable(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) - .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); - } - - void parseMaxDamage(JsonNode node, ItemStack item) { - Parsers.getNodeIntValue(node, "maxDamage") - .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); - } - - void parseTool(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) - .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); - } - - void parseGlider(JsonNode node, ItemStack item) { - Parsers.getBooleanNodeValue(node, "glider") - .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); - } - - void parseEnchantmentGlintOverride(JsonNode node, ItemStack item) { - Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") - .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); - } - - void parseRarity(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) - .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); - } - - void parseJukeboxPlayable(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) - .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + public ItemStack deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode treeNode = p.readValueAsTree(); + Optional key; + Optional amount; + if(treeNode.isObject()) { + key = Parsers.getNodeKeyValue(treeNode, "id"); + amount = Parsers.getNodeIntValue(treeNode, "amount"); + } else if (treeNode.isTextual() && !treeNode.asText().isBlank()){ + key = Optional.of(Key.key(treeNode.asText())); + amount = Optional.of(1); + } else { + return null; + } + if(key.isEmpty()) { + return null; + } + ItemStack parsedItem = ItemCreator.getItemRegistry().parse(key.get()); + if(parsedItem != null) { + return parsedItem.asQuantity(amount.orElse(1)); + } else { + return null; + } } } diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index 3762e4a..4b7905c 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -1,6 +1,8 @@ package re.sylfa.itemcreator.items; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import net.kyori.adventure.key.Key; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.StringTag; @@ -8,7 +10,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; -@Getter +@Getter @AllArgsConstructor @NoArgsConstructor public class CustomItem { private Key key; private ItemStack item; @@ -17,6 +19,12 @@ public Key key() { return this.key; } + public CustomItem(ItemStack item) { + this.item = item; + } + + CustomItem withKey(Key key) { return CustomItem.builder(key).item(getItem()).build(); } + public void key(Key key) { this.key = key; } @@ -46,11 +54,11 @@ public Builder item(ItemStack item) { .update(compoundTag -> compoundTag.put("itemcreator:id", StringTag.valueOf(this.customItem.key.asString()))); nmsCopy.set(DataComponents.CUSTOM_DATA, customData); - this.customItem.item = nmsCopy.asBukkitCopy(); + this.customItem.item = nmsCopy.asBukkitCopy().asOne(); return this; } - CustomItem build() { + public CustomItem build() { return this.customItem; } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 87353dd..3dfd63d 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -3,14 +3,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import net.kyori.adventure.key.Key; import org.apache.commons.io.FilenameUtils; -import org.bukkit.inventory.ItemStack; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; import java.io.File; import java.io.IOException; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -71,11 +69,8 @@ public static CustomItem readItem(File file) { Key key = Key.key(parentName, fileName); try { - ItemStack item = mapper.readValue(file, ItemStack.class); - - return CustomItem.builder(key) - .item(item) - .build(); + CustomItem customItem = mapper.readValue(file, CustomItem.class); + return customItem.withKey(key); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 9dfc098..dfbc244 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -5,6 +5,7 @@ import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; @@ -88,4 +89,8 @@ static Optional getNodeNamespacedKeyValue(@NonNull JsonNode node, static Optional getNodeKeyValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, Key.class); } + + static Optional getNodeItemStackValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemStack.class); + } } From eba0c4a09ee9107a3940d343ce774cef6e8aa65f Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:54:15 +0200 Subject: [PATCH 16/31] fix: only get items after loading, to allow "nesting" --- .../re/sylfa/itemcreator/ItemCreator.java | 4 +- .../commands/ItemCreatorCommand.java | 20 ++++----- .../types/CustomItemDeserializer.java | 12 +++--- .../deserializers/types/ItemDeserializer.java | 26 ++++-------- .../types/ItemKeyDeserializer.java | 41 +++++++++++++++++++ .../sylfa/itemcreator/items/CustomItem.java | 38 +++++++++++++---- .../re/sylfa/itemcreator/items/ItemKey.java | 16 ++++++++ .../sylfa/itemcreator/items/ItemRegistry.java | 14 +++---- .../re/sylfa/itemcreator/util/Parsers.java | 5 +++ 9 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/items/ItemKey.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index f088da7..e171beb 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -28,6 +28,7 @@ import re.sylfa.itemcreator.deserializers.components.*; import re.sylfa.itemcreator.deserializers.types.*; import re.sylfa.itemcreator.items.CustomItem; +import re.sylfa.itemcreator.items.ItemKey; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; import re.sylfa.itemcreator.recipes.CustomRecipe; @@ -113,7 +114,8 @@ private void registerMappers() { .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()) .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()) .addDeserializer(Consumable.class, new ConsumableDeserializer()) - .addDeserializer(FoodComponent.class, new FoodDeserializer()); + .addDeserializer(FoodComponent.class, new FoodDeserializer()) + .addDeserializer(ItemKey.class, new ItemKeyDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java index 9ba4ad7..318a1d1 100644 --- a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java +++ b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java @@ -1,24 +1,22 @@ package re.sylfa.itemcreator.commands; -import static com.mojang.brigadier.Command.SINGLE_SUCCESS; -import static io.papermc.paper.command.brigadier.Commands.argument; -import static io.papermc.paper.command.brigadier.Commands.literal; - -import java.util.List; -import java.util.Optional; - -import org.bukkit.entity.Player; - import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; - import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.commands.argumentType.CustomItemArgumentType; import re.sylfa.itemcreator.items.CustomItem; +import java.util.List; +import java.util.Optional; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + public class ItemCreatorCommand implements Command{ @Override @@ -49,7 +47,7 @@ private int giveItem(CommandContext ctx) { @SuppressWarnings("unchecked") Optional item = ctx.getArgument("itemKey", Optional.class); if(item.isPresent()) { - player.getInventory().addItem(item.get().getItem()); + player.getInventory().addItem(item.get().asItemStack()); } else { player.sendMessage(Component.text("Error: not a valid custom item.", NamedTextColor.RED)); } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 0d4b44c..26d3a03 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -62,11 +62,14 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese parseRarity(node, item); parseJukeboxPlayable(node, item); parseAmount(node, item); - parseUseRemainder(node, item); parseFood(node, item); parseConsumable(node, item); - return new CustomItem(item); + + CustomItem.Builder builder = CustomItem.builder() + .item(item); + Parsers.getNodeItemKeyValue(node, "useRemainder").ifPresent(builder::useRemainder); + return builder.build(); } void parseConsumable(JsonNode node, ItemStack item) { @@ -137,9 +140,4 @@ void parseJukeboxPlayable(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); } - - void parseUseRemainder(JsonNode node, ItemStack item) { - Parsers.getNodeItemStackValue(node, "useRemainder") - .ifPresent(remainder -> item.editMeta(itemMeta -> itemMeta.setUseRemainder(remainder))); - } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index ca3be6d..06261eb 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -4,37 +4,25 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import net.kyori.adventure.key.Key; import org.bukkit.inventory.ItemStack; import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.Parsers; +import re.sylfa.itemcreator.items.ItemKey; import java.io.IOException; -import java.util.Optional; public class ItemDeserializer extends StdDeserializer { public ItemDeserializer() { super(ItemStack.class); } @Override public ItemStack deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - JsonNode treeNode = p.readValueAsTree(); - Optional key; - Optional amount; - if(treeNode.isObject()) { - key = Parsers.getNodeKeyValue(treeNode, "id"); - amount = Parsers.getNodeIntValue(treeNode, "amount"); - } else if (treeNode.isTextual() && !treeNode.asText().isBlank()){ - key = Optional.of(Key.key(treeNode.asText())); - amount = Optional.of(1); - } else { - return null; - } - if(key.isEmpty()) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { return null; } - ItemStack parsedItem = ItemCreator.getItemRegistry().parse(key.get()); - if(parsedItem != null) { - return parsedItem.asQuantity(amount.orElse(1)); + + ItemKey itemKey = ItemCreator.getMapper().convertValue(node, ItemKey.class); + if(itemKey != null) { + return itemKey.asItemStack(); } else { return null; } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java new file mode 100644 index 0000000..7aa8cf3 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java @@ -0,0 +1,41 @@ +package re.sylfa.itemcreator.deserializers.types; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.kyori.adventure.key.Key; +import re.sylfa.itemcreator.items.ItemKey; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Optional; + +public class ItemKeyDeserializer extends StdDeserializer { + public ItemKeyDeserializer() { super(ItemKey.class);} + + @Override + public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null || (!node.isTextual() && !node.isObject())) { + return null; + } + + Optional key; + Optional amount; + if(node.isObject()) { + key = Parsers.getNodeKeyValue(node, "id"); + amount = Parsers.getNodeIntValue(node, "amount"); + } else if (node.isTextual() && !node.asText().isBlank()){ + key = Optional.of(Key.key(node.asText())); + amount = Optional.of(1); + } else { + return null; + } + if(key.isEmpty()) { + return null; + } + + return new ItemKey(key.get(), amount.orElse(1)); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index 4b7905c..f9db73a 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -10,10 +10,13 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; +import java.util.Optional; + @Getter @AllArgsConstructor @NoArgsConstructor public class CustomItem { private Key key; private ItemStack item; + private ItemKey useRemainder; public Key key() { return this.key; @@ -23,20 +26,29 @@ public CustomItem(ItemStack item) { this.item = item; } - CustomItem withKey(Key key) { return CustomItem.builder(key).item(getItem()).build(); } + CustomItem withKey(Key key) { return CustomItem.builder(key).item(getItem()).useRemainder(getUseRemainder()).build(); } public void key(Key key) { this.key = key; } public ItemStack asItemStack() { - return item; + ItemStack finalItem = this.withItemId(key); + if(useRemainder != null) { + Optional.ofNullable(useRemainder.asItemStack()) + .ifPresent(remainder -> finalItem.editMeta(itemMeta -> itemMeta.setUseRemainder(remainder))); + } + return finalItem; } public static Builder builder(Key key) { return new Builder().key(key); } + public static Builder builder() { + return new Builder(); + } + public static class Builder { CustomItem customItem = new CustomItem(); @@ -47,14 +59,12 @@ private Builder key(Key key) { public Builder item(ItemStack item) { - // add custom item ID into the customData component - net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(item); - - CustomData customData = nmsCopy.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) - .update(compoundTag -> compoundTag.put("itemcreator:id", StringTag.valueOf(this.customItem.key.asString()))); + this.customItem.item = item; + return this; + } - nmsCopy.set(DataComponents.CUSTOM_DATA, customData); - this.customItem.item = nmsCopy.asBukkitCopy().asOne(); + public Builder useRemainder(ItemKey remainderKey) { + this.customItem.useRemainder = remainderKey; return this; } @@ -62,4 +72,14 @@ public CustomItem build() { return this.customItem; } } + + private ItemStack withItemId(Key key) { + net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(item); + + CustomData customData = nmsCopy.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) + .update(compoundTag -> compoundTag.put("itemcreator:id", StringTag.valueOf(key.asString()))); + + nmsCopy.set(DataComponents.CUSTOM_DATA, customData); + return nmsCopy.asBukkitCopy().asOne(); + } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemKey.java b/src/main/java/re/sylfa/itemcreator/items/ItemKey.java new file mode 100644 index 0000000..2765fb4 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/items/ItemKey.java @@ -0,0 +1,16 @@ +package re.sylfa.itemcreator.items; + +import net.kyori.adventure.key.Key; +import org.bukkit.inventory.ItemStack; +import re.sylfa.itemcreator.ItemCreator; + +public record ItemKey(Key key, int amount) { + public ItemStack asItemStack() { + ItemStack parsedItem = ItemCreator.getItemRegistry().parse(key); + if(parsedItem != null) { + return parsedItem.asQuantity(amount); + } else { + return null; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java index 24260f8..1441dac 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java @@ -1,18 +1,16 @@ package re.sylfa.itemcreator.items; -import java.util.Map; - +import net.kyori.adventure.key.Key; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; -import java.util.HashMap; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; -import net.kyori.adventure.key.Key; -import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.Log; - public class ItemRegistry { final Map itemList = new HashMap<>(); @@ -54,7 +52,7 @@ public ItemStack parse(Key key) { var itemRegistry = ItemCreator.getItemRegistry(); Optional item = itemRegistry.get(key); if(item.isPresent()) { - return item.get().getItem(); + return item.get().asItemStack(); } else { Log.warn("Material not found: %s", key.asString()); return null; diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index dfbc244..088326a 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -9,6 +9,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; +import re.sylfa.itemcreator.items.ItemKey; import java.util.ArrayList; import java.util.List; @@ -93,4 +94,8 @@ static Optional getNodeKeyValue(@NonNull JsonNode node, @NonNull String val static Optional getNodeItemStackValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemStack.class); } + + static Optional getNodeItemKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemKey.class); + } } From 4c571c3424d0924292a346095fe7ed0af478a227 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Mon, 21 Jul 2025 20:58:46 +0200 Subject: [PATCH 17/31] feature: death protection, consume effect, potion and potion type deserializer --- .../re/sylfa/itemcreator/ItemCreator.java | 10 ++- .../components/ConsumeEffectDeserializer.java | 90 +++++++++++++++++++ .../DeathProtectionDeserializer.java | 33 +++++++ .../components/PotionEffectDeserializer.java | 48 ++++++++++ .../PotionEffectTypeDeserializer.java | 25 ++++++ .../types/CustomItemDeserializer.java | 88 ++++-------------- .../re/sylfa/itemcreator/util/Parsers.java | 5 ++ 7 files changed, 229 insertions(+), 70 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index e171beb..a965471 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import lombok.Getter; @@ -22,6 +24,8 @@ import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.bukkit.util.permissions.CommandPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; @@ -115,7 +119,11 @@ private void registerMappers() { .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()) .addDeserializer(Consumable.class, new ConsumableDeserializer()) .addDeserializer(FoodComponent.class, new FoodDeserializer()) - .addDeserializer(ItemKey.class, new ItemKeyDeserializer()); + .addDeserializer(ItemKey.class, new ItemKeyDeserializer()) + .addDeserializer(PotionEffect.class, new PotionEffectDeserializer()) + .addDeserializer(DeathProtection.class, new DeathProtectionDeserializer()) + .addDeserializer(ConsumeEffect.class, new ConsumeEffectDeserializer()) + .addDeserializer(PotionEffectType.class, new PotionEffectTypeDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java new file mode 100644 index 0000000..d3b7b9d --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java @@ -0,0 +1,90 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.set.RegistrySet; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.*; + +public class ConsumeEffectDeserializer extends StdDeserializer { + + public ConsumeEffectDeserializer() { super(ConsumeEffect.class); } + + @Override + public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isTextual, Type.class); + if(type.isEmpty()) { + return null; + } + + return switch (type.get()) { + case APPLY_STATUS_EFFECTS -> { + JsonNode effects = node.get("effects"); + if(effects == null || !effects.isArray()) { + yield null; + } + + List potionEffects = new ArrayList<>(); + for(JsonNode effect : effects) { + PotionEffect potionEffect = ItemCreator.getMapper().convertValue(effect, PotionEffect.class); + if(potionEffect != null) { + potionEffects.add(potionEffect); + } + } + + float probability = Parsers.getFloatNodeValue(node, "probability").orElse(1.0f); + yield ConsumeEffect.applyStatusEffects(potionEffects, probability); + } + case CLEAR_ALL_STATUS_EFFECTS -> ConsumeEffect.clearAllStatusEffects(); + case PLAY_SOUND -> Parsers.getNodeKeyValue(node, "sound") + .map(ConsumeEffect::playSoundConsumeEffect) + .orElse(null); + case REMOVE_STATUS_EFFECTS -> { + JsonNode effects = node.get("effects"); + if(effects == null || !effects.isArray()) { + yield null; + } + + Set effectTypes = new HashSet<>(); + for(JsonNode effect : effects) { + String rawKey = effect.asText(); + if(rawKey == null || rawKey.isBlank()) { + continue; + } + Key key = Key.key(rawKey); + + PotionEffectType effectType = Registry.EFFECT.get(key); + if(effectType != null) { + effectTypes.add(effectType); + } + } + + + yield ConsumeEffect.removeEffects(RegistrySet.keySetFromValues(RegistryKey.MOB_EFFECT, effectTypes)); + } + case TELEPORT_RANDOMLY -> Parsers.getFloatNodeValue(node, "diameter") + .map(ConsumeEffect::teleportRandomlyEffect) + .orElse(null); + }; + } + + public enum Type { + APPLY_STATUS_EFFECTS, + CLEAR_ALL_STATUS_EFFECTS, + PLAY_SOUND, + REMOVE_STATUS_EFFECTS, + TELEPORT_RANDOMLY + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java new file mode 100644 index 0000000..3734b5e --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java @@ -0,0 +1,33 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import re.sylfa.itemcreator.ItemCreator; + +import java.io.IOException; +import java.util.Optional; + +public class DeathProtectionDeserializer extends StdDeserializer { + public DeathProtectionDeserializer() { super(DeathProtection.class); } + + @Override + public DeathProtection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isArray()) { + return null; + } + + DeathProtection.Builder builder = DeathProtection.deathProtection(); + + for(JsonNode effect : node) { + Optional.ofNullable(ItemCreator.getMapper().convertValue(effect, ConsumeEffect.class)) + .ifPresent(builder::addEffect); + } + + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java new file mode 100644 index 0000000..6ce9f32 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java @@ -0,0 +1,48 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Optional; + +public class PotionEffectDeserializer extends StdDeserializer { + public PotionEffectDeserializer() { super(PotionEffect.class); } + + @Override + public PotionEffect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { + return null; + } + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isTextual, PotionEffectType.class); + Optional duration = Parsers.getNodeIntValue(node, "duration"); + Optional amplifier = Parsers.getNodeIntValue(node, "amplifier"); + boolean ambient = Parsers.getBooleanNodeValue(node, "ambient").orElse(true); + boolean particles = Parsers.getBooleanNodeValue(node, "particles").orElse(true); + boolean icon = Parsers.getBooleanNodeValue(node, "icon").orElse(true); + + if(type.isEmpty()) { + Log.error("Potion type missing"); + return null; + } + + if(duration.isEmpty()) { + Log.error("Potion duration missing"); + return null; + } + + if(amplifier.isEmpty()) { + Log.error("Potion amplifier missing"); + return null; + } + + return new PotionEffect(type.get(), duration.get(), amplifier.get(), ambient, particles, icon); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java new file mode 100644 index 0000000..aa88817 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java @@ -0,0 +1,25 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.potion.PotionEffectType; + +import java.io.IOException; + +public class PotionEffectTypeDeserializer extends StdDeserializer { + public PotionEffectTypeDeserializer() { super(PotionEffectType.class); } + + @Override + public PotionEffectType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isTextual()) { + return null; + } + Key key = Key.key(node.asText()); + return Registry.MOB_EFFECT.get(key); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 26d3a03..d8a5a20 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.DeathProtection; import net.minecraft.core.component.DataComponents; import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftItemStack; @@ -50,94 +51,43 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese ItemStack item = itemNms.asBukkitCopy(); - parseItemName(node, item); - parseLore(node, item); - parseMaxStackSize(node, item); - parseItemModel(node, item); - parseEquippable(node, item); - parseMaxDamage(node, item); - parseTool(node, item); - parseGlider(node, item); - parseEnchantmentGlintOverride(node, item); - parseRarity(node, item); - parseJukeboxPlayable(node, item); - parseAmount(node, item); - parseFood(node, item); - parseConsumable(node, item); - - - CustomItem.Builder builder = CustomItem.builder() - .item(item); - Parsers.getNodeItemKeyValue(node, "useRemainder").ifPresent(builder::useRemainder); - return builder.build(); - } - - void parseConsumable(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) - .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); - } - - void parseFood(JsonNode node, ItemStack item) { - Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) - .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); - } - - void parseAmount(JsonNode node, ItemStack item) { - Parsers.getNodeIntValue(node, "amount").ifPresent(item::setAmount); - } - - void parseLore(JsonNode node, ItemStack item) { - Parsers.getComponentArrayNodeValue(node, "lore") - .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); - } - - void parseItemName(JsonNode node, ItemStack item) { Parsers.getNodeComponentValue(node, "itemName") .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); - } - - void parseMaxStackSize(JsonNode node, ItemStack item) { + Parsers.getComponentArrayNodeValue(node, "lore") + .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); Parsers.getNodeIntValue(node, "maxStackSize") .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); - } - - void parseItemModel(JsonNode node, ItemStack item) { Parsers.getNodeNamespacedKeyValue(node, "itemModel") .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); - } - - void parseEquippable(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); - } - - void parseMaxDamage(JsonNode node, ItemStack item) { Parsers.getNodeIntValue(node, "maxDamage") .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); - } - - void parseTool(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); - } - - void parseGlider(JsonNode node, ItemStack item) { Parsers.getBooleanNodeValue(node, "glider") .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); - } - - void parseEnchantmentGlintOverride(JsonNode node, ItemStack item) { Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); - } - - void parseRarity(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); - } - - void parseJukeboxPlayable(JsonNode node, ItemStack item) { Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + Parsers.getNodeIntValue(node, "amount").ifPresent(item::setAmount); + Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) + .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); + Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) + .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); + Parsers.getNodeIntValue(node, "damage") + .ifPresent(damage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setDamage(damage))); + Parsers.getNodeValue(node, "deathProtection", JsonNode::isArray, DeathProtection.class) + .ifPresent(deathProtection -> item.setData(DataComponentTypes.DEATH_PROTECTION, deathProtection)); + + + CustomItem.Builder builder = CustomItem.builder() + .item(item); + Parsers.getNodeItemKeyValue(node, "useRemainder").ifPresent(builder::useRemainder); + return builder.build(); } + } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 088326a..6d4b7c2 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -6,6 +6,7 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; @@ -98,4 +99,8 @@ static Optional getNodeItemStackValue(@NonNull JsonNode node, @NonNul static Optional getNodeItemKeyValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemKey.class); } + + static Optional getNodePotionEffectValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isObject, PotionEffect.class); + } } From bbfe7eb1a206a12a09e224ffd9f965ad2f66ea75 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Mon, 21 Jul 2025 21:48:30 +0200 Subject: [PATCH 18/31] feature: add consumable effects in consumable deserializer --- .../components/ConsumableDeserializer.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java index 54520dc..a73c6df 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java @@ -5,7 +5,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; import re.sylfa.itemcreator.util.Parsers; import java.io.IOException; @@ -25,6 +28,19 @@ public Consumable deserialize(JsonParser p, DeserializationContext ctxt) throws Parsers.getFloatNodeValue(node, "consumeSeconds").ifPresent(builder::consumeSeconds); Parsers.getBooleanNodeValue(node, "hasConsumeParticles").ifPresent(builder::hasConsumeParticles); Parsers.getNodeKeyValue(node, "sound").ifPresent(builder::sound); + JsonNode effects = node.get("effects"); + if(effects != null) { + if(effects.isArray()) { + for(JsonNode effect : effects) { + ConsumeEffect consumeEffect = ItemCreator.getMapper().convertValue(effect, ConsumeEffect.class); + if(consumeEffect != null) { + builder.addEffect(consumeEffect); + } + } + } else { + Log.error("Consumable effect is not valid"); + } + } return builder.build(); } } From 9c3c62d74b51f99c08d9fb05d98d08ddeeed9221 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Tue, 22 Jul 2025 00:25:55 +0200 Subject: [PATCH 19/31] feature: dyed color and instrument components + color derserializer --- .../re/sylfa/itemcreator/ItemCreator.java | 8 +- .../components/ConsumeEffectDeserializer.java | 4 +- .../MusicInstrumentDeserializer.java | 58 +++++++++ .../types/ColorDeserializer.java | 112 ++++++++++++++++++ .../types/CustomItemDeserializer.java | 7 ++ .../re/sylfa/itemcreator/util/Parsers.java | 6 + 6 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index a965471..7c6533b 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -11,9 +11,7 @@ import lombok.Getter; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; +import org.bukkit.*; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.components.EquippableComponent; @@ -123,7 +121,9 @@ private void registerMappers() { .addDeserializer(PotionEffect.class, new PotionEffectDeserializer()) .addDeserializer(DeathProtection.class, new DeathProtectionDeserializer()) .addDeserializer(ConsumeEffect.class, new ConsumeEffectDeserializer()) - .addDeserializer(PotionEffectType.class, new PotionEffectTypeDeserializer()); + .addDeserializer(PotionEffectType.class, new PotionEffectTypeDeserializer()) + .addDeserializer(Color.class, new ColorDeserializer()) + .addDeserializer(MusicInstrument.class, new MusicInstrumentDeserializer()); mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java index d3b7b9d..35223c3 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java @@ -43,7 +43,7 @@ public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) thro potionEffects.add(potionEffect); } } - + float probability = Parsers.getFloatNodeValue(node, "probability").orElse(1.0f); yield ConsumeEffect.applyStatusEffects(potionEffects, probability); } @@ -65,7 +65,7 @@ public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) thro } Key key = Key.key(rawKey); - PotionEffectType effectType = Registry.EFFECT.get(key); + PotionEffectType effectType = Registry.MOB_EFFECT.get(key); if(effectType != null) { effectTypes.add(effectType); } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java new file mode 100644 index 0000000..ec108f5 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java @@ -0,0 +1,58 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.MusicInstrument; +import org.bukkit.Sound; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Optional; + +public class MusicInstrumentDeserializer extends StdDeserializer { + + public MusicInstrumentDeserializer() { super(MusicInstrument.class); } + + @Override + public MusicInstrument deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if (node == null || (!node.isObject() && !node.isTextual())) { + return null; + } + + // by key + if(node.isTextual()) { + Key key = Key.key(node.asText()); + return RegistryAccess.registryAccess().getRegistry(RegistryKey.INSTRUMENT).get(key); + } + + // by inline instrument + if(node.isObject()) { + Optional soundKey = Parsers.getNodeKeyValue(node, "soundKey"); + if (soundKey.isEmpty()) { + return null; + } + Optional duration = Parsers.getFloatNodeValue(node, "duration"); + Optional range = Parsers.getFloatNodeValue(node, "range"); + Optional description = Parsers.getNodeComponentValue(node, "description"); + TypedKey typedKey = RegistryKey.SOUND_EVENT.typedKey(soundKey.get()); + + return MusicInstrument.create(builder -> builder.empty() + .soundEvent(typedKey) + .description(description.orElse(Component.empty())) + .duration(duration.orElse(1.0f)) + .range(range.orElse(1.0f)) + ); + } + + // default + return null; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java new file mode 100644 index 0000000..340b745 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -0,0 +1,112 @@ +package re.sylfa.itemcreator.deserializers.types; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.bukkit.Color; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ColorDeserializer extends StdDeserializer { + private Color getColorByName(String name) { + return switch (name.toUpperCase()) { + case "AQUA" -> Color.AQUA; + case "BLACK" -> Color.BLACK; + case "BLUE" -> Color.BLUE; + case "FUSCHIA" -> Color.FUCHSIA; + case "GRAY" -> Color.GRAY; + case "GREEEN" -> Color.GREEN; + case "LIME" -> Color.LIME; + case "MAROON" -> Color.MAROON; + case "NAVY" -> Color.NAVY; + case "OLIVE" -> Color.OLIVE; + case "ORANGE" -> Color.ORANGE; + case "PURPLE" -> Color.PURPLE; + case "RED" -> Color.RED; + case "SILVER" -> Color.SILVER; + case "TEAL" -> Color.TEAL; + case "WHITE" -> Color.WHITE; + case "YELLOW" -> Color.YELLOW; + default -> null; + }; + } + public ColorDeserializer() { super(Color.class); } + + @Override + public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + // as an RGB int value + if(node.isIntegralNumber()) { + int colorNumber = node.asInt(); + return Color.fromRGB(colorNumber); + } + + // as an hex value #123456 + if(node.isTextual()) { + String textValue = node.asText(); + if(textValue.startsWith("#") && (textValue.length() == 7 || textValue.length() == 9)) { + int rgbValue = Integer.valueOf(textValue.substring(1), 16); + if(textValue.length() == 7) { + return Color.fromRGB(rgbValue); + } else { + return Color.fromARGB(rgbValue); + } + } else { + return getColorByName(node.asText()); + } + } + + // as an array of 3 or 4 ints for (alpha), red, green, blue [255, 2, 140, 255] + if(node.isArray()) { + List numbers = new ArrayList<>(); + for(JsonNode value : node) { + if(!value.isIntegralNumber() || value.asInt() < 0 || value.asInt() > 255 ) { + Log.error("\"%s\" is not a valid number"); + return null; + } + numbers.add(value.intValue()); + } + if(numbers.size() < 3 || numbers.size() > 4) { + Log.error("%s is not a valid color"); + } + int[] intValues = numbers.stream().mapToInt(i -> i).toArray(); + if(intValues.length == 3) { + return Color.fromRGB(intValues[0], intValues[1], intValues[2]); + } else { + return Color.fromARGB(intValues[0], intValues[1], intValues[2], intValues[3]); + } + } + + // as an object {"red": 255, "blue": 120, "green": 41, "alpha": 255} + if(node.isObject()) { + Map map = ItemCreator.getMapper().convertValue(node, Map.class); + Optional red = Parsers.getNodeIntValue(node, "red"); + Optional green = Parsers.getNodeIntValue(node, "green"); + Optional blue = Parsers.getNodeIntValue(node, "blue"); + Optional alpha = Parsers.getNodeIntValue(node, "alpha"); + if(red.isEmpty() || blue.isEmpty() || green.isEmpty()) { + return null; + } + return Color.fromARGB( + alpha.orElse(255), + red.get(), + green.get(), + blue.get() + ); + } + + return null; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index d8a5a20..6a2ed22 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -7,8 +7,10 @@ import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.DyedItemColor; import net.minecraft.core.component.DataComponents; import org.bukkit.Material; +import org.bukkit.MusicInstrument; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemRarity; import org.bukkit.inventory.ItemStack; @@ -23,6 +25,7 @@ import java.io.IOException; import java.util.List; +import java.util.Objects; public class CustomItemDeserializer extends StdDeserializer { @@ -82,6 +85,10 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(damage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setDamage(damage))); Parsers.getNodeValue(node, "deathProtection", JsonNode::isArray, DeathProtection.class) .ifPresent(deathProtection -> item.setData(DataComponentTypes.DEATH_PROTECTION, deathProtection)); + Parsers.getNodeColorValue(node, "dyedColor") + .ifPresent(color -> item.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color))); + Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) + .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); CustomItem.Builder builder = CustomItem.builder() diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 6d4b7c2..18921af 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; +import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; @@ -14,6 +15,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -103,4 +105,8 @@ static Optional getNodeItemKeyValue(@NonNull JsonNode node, @NonNull St static Optional getNodePotionEffectValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isObject, PotionEffect.class); } + + static Optional getNodeColorValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, Objects::nonNull, Color.class); + } } From 3a0c676512e5d6578b05c4e0684e9f4abdeb2e0c Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:01:24 +0200 Subject: [PATCH 20/31] feature: profile and enchantable components --- gradle/libs.versions.toml | 10 +-- .../re/sylfa/itemcreator/ItemCreator.java | 38 +++++++++- .../components/EnchantableDeserializer.java | 40 ++++++++++ .../components/ProfileDeserializer.java | 74 +++++++++++++++++++ .../types/ColorDeserializer.java | 12 ++- .../types/CustomItemDeserializer.java | 7 ++ .../re/sylfa/itemcreator/util/JavaUtils.java | 8 ++ .../re/sylfa/itemcreator/util/Parsers.java | 5 ++ 8 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6cc2c24..d36c82e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,13 +2,13 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] paper = "1.21.8-R0.1-SNAPSHOT" -paperweight = "2.0.0-beta.17" +paperweight = "2.0.0-beta.18" brigadier = "1.0.18" -runPaper = "2.3.1" +runPaper = "3.0.0-beta.1" lombok = "1.18.38" -adventure = "4.20.0" -commonsIo = "2.19.0" -jackson = "2.19.2" +adventure = "4.24.0" +commonsIo = "2.20.0" +jackson = "2.20.0" [libraries] brigadier = {group = "com.mojang", name = "brigadier", version.ref = "brigadier"} diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 7c6533b..1f26b93 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -1,17 +1,24 @@ package re.sylfa.itemcreator; +import com.destroystokyo.paper.profile.ProfileProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ResolvableProfile; import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import lombok.Getter; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.MusicInstrument; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.meta.components.EquippableComponent; @@ -27,8 +34,27 @@ import org.bukkit.util.permissions.CommandPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; -import re.sylfa.itemcreator.deserializers.components.*; -import re.sylfa.itemcreator.deserializers.types.*; +import re.sylfa.itemcreator.deserializers.components.ConsumableDeserializer; +import re.sylfa.itemcreator.deserializers.components.ConsumeEffectDeserializer; +import re.sylfa.itemcreator.deserializers.components.DeathProtectionDeserializer; +import re.sylfa.itemcreator.deserializers.components.EnchantableDeserializer; +import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; +import re.sylfa.itemcreator.deserializers.components.FoodDeserializer; +import re.sylfa.itemcreator.deserializers.components.JukeboxPlayableDeserializer; +import re.sylfa.itemcreator.deserializers.components.MusicInstrumentDeserializer; +import re.sylfa.itemcreator.deserializers.components.PotionEffectDeserializer; +import re.sylfa.itemcreator.deserializers.components.PotionEffectTypeDeserializer; +import re.sylfa.itemcreator.deserializers.components.ProfileDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; +import re.sylfa.itemcreator.deserializers.types.ColorDeserializer; +import re.sylfa.itemcreator.deserializers.types.CustomItemDeserializer; +import re.sylfa.itemcreator.deserializers.types.ItemDeserializer; +import re.sylfa.itemcreator.deserializers.types.ItemKeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.KeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.MaterialDeserializer; +import re.sylfa.itemcreator.deserializers.types.NamespacedKeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.TextComponentDeserializer; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.items.ItemKey; import re.sylfa.itemcreator.items.ItemReader; @@ -123,7 +149,11 @@ private void registerMappers() { .addDeserializer(ConsumeEffect.class, new ConsumeEffectDeserializer()) .addDeserializer(PotionEffectType.class, new PotionEffectTypeDeserializer()) .addDeserializer(Color.class, new ColorDeserializer()) - .addDeserializer(MusicInstrument.class, new MusicInstrumentDeserializer()); + .addDeserializer(MusicInstrument.class, new MusicInstrumentDeserializer()) + .addDeserializer(Enchantable.class, new EnchantableDeserializer()) + .addDeserializer(ResolvableProfile.class, new ProfileDeserializer()) + .addDeserializer(ProfileProperty.class, new ProfileDeserializer.ProfilePropertyDeserializer()) + ; mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java new file mode 100644 index 0000000..3491b94 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java @@ -0,0 +1,40 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.Enchantable; +import org.checkerframework.checker.nullness.qual.Nullable; +import re.sylfa.itemcreator.util.Log; + +import java.io.IOException; + +public class EnchantableDeserializer extends StdDeserializer { + private static final int NOT_FOUND = -1; + public EnchantableDeserializer() { + super(Enchantable.class); + } + + @Override @Nullable + public Enchantable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + boolean enabled = node.get("enabled").asBoolean(false); + if(!enabled) { + return null; + } + + int value = node.get("value").asInt(NOT_FOUND); + if(value <= 0) { + Log.error("%s is not valid for enchantable value"); + return null; + } + + return Enchantable.enchantable(value); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java new file mode 100644 index 0000000..99d4b60 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java @@ -0,0 +1,74 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.destroystokyo.paper.profile.ProfileProperty; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +public class ProfileDeserializer extends StdDeserializer { + public ProfileDeserializer() { + super(ResolvableProfile.class); + } + @Override + public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + ResolvableProfile.Builder builder = ResolvableProfile.resolvableProfile(); + + Parsers.getStringNodeValue(node, "name").ifPresent(builder::name); + Parsers.getNodeUUIDValue(node, "uuid").ifPresent(builder::uuid); + + if(node.has("property")) { + Parsers.getNodeValue(node, "property", JsonNode::isObject, ProfileProperty.class) + .ifPresent(builder::addProperty); + } else if (node.has("properties")) { + JsonNode properties = node.get("properties"); + JavaUtils.iteratorStream(properties.elements()) + .map(currentProperty -> ItemCreator.getMapper().convertValue(currentProperty, ProfileProperty.class)) + .filter(Objects::nonNull) + .forEach(builder::addProperty); + } + + return builder.build(); + } + + public static class ProfilePropertyDeserializer extends StdDeserializer { + public ProfilePropertyDeserializer() { + super(ProfileProperty.class); + } + + @Override + public ProfileProperty deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + Optional name = Parsers.getStringNodeValue(node, "name"); + if(name.isEmpty()) { + return null; + } + Optional value = Parsers.getStringNodeValue(node, "value"); + if(value.isEmpty()) { + return null; + } + + Optional signature = Parsers.getStringNodeValue(node, "signature"); + + return new ProfileProperty(name.get(), value.get(), signature.orElse(null)); + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java index 340b745..2372bcf 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -21,9 +21,9 @@ private Color getColorByName(String name) { case "AQUA" -> Color.AQUA; case "BLACK" -> Color.BLACK; case "BLUE" -> Color.BLUE; - case "FUSCHIA" -> Color.FUCHSIA; + case "FUCHSIA" -> Color.FUCHSIA; case "GRAY" -> Color.GRAY; - case "GREEEN" -> Color.GREEN; + case "GREEN" -> Color.GREEN; case "LIME" -> Color.LIME; case "MAROON" -> Color.MAROON; case "NAVY" -> Color.NAVY; @@ -50,12 +50,17 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc // as an RGB int value if(node.isIntegralNumber()) { int colorNumber = node.asInt(); + if(colorNumber < 0 || colorNumber > 0xFFFFFF) { + Log.error("%s is not a valid color int.", colorNumber); + return null; + } + return Color.fromRGB(colorNumber); } - // as an hex value #123456 if(node.isTextual()) { String textValue = node.asText(); + // as an hex value #RRGGBB or #AARRGGBB if(textValue.startsWith("#") && (textValue.length() == 7 || textValue.length() == 9)) { int rgbValue = Integer.valueOf(textValue.substring(1), 16); if(textValue.length() == 7) { @@ -64,6 +69,7 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc return Color.fromARGB(rgbValue); } } else { + // with color name return getColorByName(node.asText()); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 6a2ed22..f97f040 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -8,6 +8,8 @@ import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.DyedItemColor; +import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ResolvableProfile; import net.minecraft.core.component.DataComponents; import org.bukkit.Material; import org.bukkit.MusicInstrument; @@ -51,6 +53,7 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); itemNms.remove(DataComponents.FOOD); itemNms.remove(DataComponents.REPAIRABLE); + itemNms.remove(DataComponents.ENCHANTABLE); ItemStack item = itemNms.asBukkitCopy(); @@ -89,6 +92,10 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(color -> item.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color))); Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); + Parsers.getNodeValue(node, "enchantable", JsonNode::isObject, Enchantable.class) + .ifPresent(enchantable -> item.setData(DataComponentTypes.ENCHANTABLE, enchantable)); + Parsers.getNodeValue(node, "profile", JsonNode::isObject, ResolvableProfile.class) + .ifPresent(profile -> item.setData(DataComponentTypes.PROFILE, profile)); CustomItem.Builder builder = CustomItem.builder() diff --git a/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java index af2211d..fd93262 100644 --- a/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java +++ b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java @@ -1,7 +1,11 @@ package re.sylfa.itemcreator.util; import java.util.Arrays; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.stream.Stream; +import java.util.stream.StreamSupport; public interface JavaUtils { static Stream arrayStream(T[] array) { @@ -9,4 +13,8 @@ static Stream arrayStream(T[] array) { ? Arrays.stream(array) : Stream.empty(); } + + static Stream iteratorStream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); + } } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 18921af..a6ea845 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.function.Predicate; public interface Parsers { @@ -109,4 +110,8 @@ static Optional getNodePotionEffectValue(@NonNull JsonNode node, @ static Optional getNodeColorValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, Objects::nonNull, Color.class); } + + static Optional getNodeUUIDValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, n -> n.isTextual() && !n.isEmpty(), UUID.class); + } } From f332fb97243055c733767171d9521ac764ebefc0 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:02:36 +0200 Subject: [PATCH 21/31] refactor: remove unused classes --- .../parsers/PotionEffectParser.java | 57 ------------------- .../typeAdapters/ComponentAdapter.java | 21 ------- .../itemcreator/typeAdapters/KeyAdapter.java | 20 ------- .../typeAdapters/MaterialAdapter.java | 26 --------- 4 files changed, 124 deletions(-) delete mode 100644 src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java delete mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java delete mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java delete mode 100644 src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java diff --git a/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java b/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java deleted file mode 100644 index dac275e..0000000 --- a/src/main/java/re/sylfa/itemcreator/parsers/PotionEffectParser.java +++ /dev/null @@ -1,57 +0,0 @@ -package re.sylfa.itemcreator.parsers; - -import java.util.Map; -import java.util.Optional; - -import org.bukkit.NamespacedKey; -import org.bukkit.potion.PotionEffect; - -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; -import re.sylfa.itemcreator.util.Log; - -public class PotionEffectParser { - public static PotionEffect parse(Map rawPotionEffect) { - if(rawPotionEffect instanceof Map potionEffectMap && potionEffectMap.containsKey("type") && potionEffectMap.containsKey("duration") && potionEffectMap.containsKey("amplifier")) { - var rawType = potionEffectMap.get("type"); - var rawDuration = potionEffectMap.get("duration"); - var rawAmplifier = potionEffectMap.get("amplifier"); - var rawAmbient = potionEffectMap.get("ambient"); - var rawShowParticles = potionEffectMap.get("showParticles"); - var rawShowIcon = potionEffectMap.get("showIcon"); - - - if(rawType instanceof String parsedType && rawDuration instanceof Integer duration && rawAmplifier instanceof Integer amplifier) { - NamespacedKey key = NamespacedKey.minecraft(parsedType.toLowerCase()); - var type = Optional.ofNullable(RegistryAccess.registryAccess().getRegistry(RegistryKey.MOB_EFFECT).get(key)); - if(type.isEmpty()) { - Log.warn("Bad potion type: %s", parsedType); - - return null; - } - - PotionEffect potionEffect = new PotionEffect(type.get(), duration, amplifier); - if(rawAmbient instanceof Boolean ambient) { - potionEffect = potionEffect.withAmbient(ambient); - } else { - potionEffect = potionEffect.withAmbient(false); - } - - if(rawShowParticles instanceof Boolean showParticles) { - potionEffect = potionEffect.withParticles(showParticles); - } - if(rawShowIcon instanceof Boolean showIcon) { - potionEffect = potionEffect.withIcon(showIcon); - } - - return potionEffect; - - } else { - Log.warn("A potion effect should have at least a valid type, duration and amplifier"); - return null; - } - } else { - return null; - } - } -} diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java deleted file mode 100644 index 861e56a..0000000 --- a/src/main/java/re/sylfa/itemcreator/typeAdapters/ComponentAdapter.java +++ /dev/null @@ -1,21 +0,0 @@ -package re.sylfa.itemcreator.typeAdapters; - -import java.lang.reflect.Type; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; - -public class ComponentAdapter implements JsonDeserializer{ - private static final MiniMessage mm = MiniMessage.miniMessage(); - @Override - public Component deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - return mm.deserialize(json.getAsString()); - } - -} diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java deleted file mode 100644 index 859f4cf..0000000 --- a/src/main/java/re/sylfa/itemcreator/typeAdapters/KeyAdapter.java +++ /dev/null @@ -1,20 +0,0 @@ -package re.sylfa.itemcreator.typeAdapters; - -import java.lang.reflect.Type; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; - -import net.kyori.adventure.key.Key; - -public class KeyAdapter implements JsonDeserializer{ - - @Override - public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - return Key.key(json.getAsString()); - } - -} diff --git a/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java b/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java deleted file mode 100644 index b98183d..0000000 --- a/src/main/java/re/sylfa/itemcreator/typeAdapters/MaterialAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package re.sylfa.itemcreator.typeAdapters; - -import java.lang.StackWalker.Option; -import java.lang.reflect.Type; - -import org.bukkit.Material; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; - -public class MaterialAdapter implements JsonDeserializer { - - @Override - public Material deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - String rawMaterial = json.getAsString(); - Material material = Material.matchMaterial(rawMaterial); - if(material == null) { - throw new JsonParseException(String.format("Bad Material: %s", rawMaterial)); - } - - return material; - } -} From b04a226e59cff36b1528f8d236cdfd5aa800e12a Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 18:20:09 +0200 Subject: [PATCH 22/31] refactor: normalize Parsers names --- .../components/ConsumableDeserializer.java | 2 +- .../components/ConsumeEffectDeserializer.java | 8 ++++++-- .../components/EquippableDeserializer.java | 4 ++-- .../components/FoodDeserializer.java | 2 +- .../MusicInstrumentDeserializer.java | 4 ++-- .../components/PotionEffectDeserializer.java | 4 ++-- .../components/ProfileDeserializer.java | 2 +- .../components/ToolDeserializer.java | 2 +- .../deserializers/types/ColorDeserializer.java | 8 ++++---- .../types/CustomItemDeserializer.java | 16 ++++++++-------- .../types/ItemKeyDeserializer.java | 4 ++-- .../re/sylfa/itemcreator/util/Parsers.java | 18 +++++++++--------- 12 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java index a73c6df..98dbdf0 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java @@ -27,7 +27,7 @@ public Consumable deserialize(JsonParser p, DeserializationContext ctxt) throws .ifPresent(builder::animation); Parsers.getFloatNodeValue(node, "consumeSeconds").ifPresent(builder::consumeSeconds); Parsers.getBooleanNodeValue(node, "hasConsumeParticles").ifPresent(builder::hasConsumeParticles); - Parsers.getNodeKeyValue(node, "sound").ifPresent(builder::sound); + Parsers.getKeyNodeValue(node, "sound").ifPresent(builder::sound); JsonNode effects = node.get("effects"); if(effects != null) { if(effects.isArray()) { diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java index 35223c3..44903f1 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java @@ -15,7 +15,11 @@ import re.sylfa.itemcreator.util.Parsers; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; public class ConsumeEffectDeserializer extends StdDeserializer { @@ -48,7 +52,7 @@ public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) thro yield ConsumeEffect.applyStatusEffects(potionEffects, probability); } case CLEAR_ALL_STATUS_EFFECTS -> ConsumeEffect.clearAllStatusEffects(); - case PLAY_SOUND -> Parsers.getNodeKeyValue(node, "sound") + case PLAY_SOUND -> Parsers.getKeyNodeValue(node, "sound") .map(ConsumeEffect::playSoundConsumeEffect) .orElse(null); case REMOVE_STATUS_EFFECTS -> { diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java index 60e661c..4a2dd63 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java @@ -44,8 +44,8 @@ public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt Parsers.getBooleanNodeValue(node, "dispensable").ifPresent(equippableComponent::setDispensable); Parsers.getBooleanNodeValue(node, "damageOnHurt").ifPresent(damage -> equippableComponent.setDamageOnHurt(damage)); Parsers.getBooleanNodeValue(node, "equipOnInteract").ifPresent(equippableComponent::setEquipOnInteract); - Parsers.getNodeNamespacedKeyValue(node, "model").ifPresent(equippableComponent::setModel); - Parsers.getNodeNamespacedKeyValue(node, "cameraOverlay").ifPresent(equippableComponent::setCameraOverlay); + Parsers.getNamespacedKeyNodeValue(node, "model").ifPresent(equippableComponent::setModel); + Parsers.getNamespacedKeyNodeValue(node, "cameraOverlay").ifPresent(equippableComponent::setCameraOverlay); Optional.ofNullable(slot).ifPresent(equippableComponent::setSlot); return equippableComponent; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java index eba3cf0..bc5fb93 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java @@ -24,7 +24,7 @@ public FoodComponent deserialize(JsonParser p, DeserializationContext ctxt) thro FoodComponent component = new ItemStack(Material.STONE).getItemMeta().getFood(); Parsers.getBooleanNodeValue(node, "canAlwaysEat").ifPresent(component::setCanAlwaysEat); - Parsers.getNodeIntValue(node, "nutrition").ifPresent(component::setNutrition); + Parsers.getIntNodeValue(node, "nutrition").ifPresent(component::setNutrition); Parsers.getFloatNodeValue(node, "saturation").ifPresent(component::setSaturation); return component; } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java index ec108f5..0b2236b 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java @@ -35,13 +35,13 @@ public MusicInstrument deserialize(JsonParser p, DeserializationContext ctxt) th // by inline instrument if(node.isObject()) { - Optional soundKey = Parsers.getNodeKeyValue(node, "soundKey"); + Optional soundKey = Parsers.getKeyNodeValue(node, "soundKey"); if (soundKey.isEmpty()) { return null; } Optional duration = Parsers.getFloatNodeValue(node, "duration"); Optional range = Parsers.getFloatNodeValue(node, "range"); - Optional description = Parsers.getNodeComponentValue(node, "description"); + Optional description = Parsers.getComponentNodeValue(node, "description"); TypedKey typedKey = RegistryKey.SOUND_EVENT.typedKey(soundKey.get()); return MusicInstrument.create(builder -> builder.empty() diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java index 6ce9f32..243d624 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java @@ -22,8 +22,8 @@ public PotionEffect deserialize(JsonParser p, DeserializationContext ctxt) throw return null; } Optional type = Parsers.getNodeValue(node, "type", JsonNode::isTextual, PotionEffectType.class); - Optional duration = Parsers.getNodeIntValue(node, "duration"); - Optional amplifier = Parsers.getNodeIntValue(node, "amplifier"); + Optional duration = Parsers.getIntNodeValue(node, "duration"); + Optional amplifier = Parsers.getIntNodeValue(node, "amplifier"); boolean ambient = Parsers.getBooleanNodeValue(node, "ambient").orElse(true); boolean particles = Parsers.getBooleanNodeValue(node, "particles").orElse(true); boolean icon = Parsers.getBooleanNodeValue(node, "icon").orElse(true); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java index 99d4b60..5ff7fec 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java @@ -29,7 +29,7 @@ public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) ResolvableProfile.Builder builder = ResolvableProfile.resolvableProfile(); Parsers.getStringNodeValue(node, "name").ifPresent(builder::name); - Parsers.getNodeUUIDValue(node, "uuid").ifPresent(builder::uuid); + Parsers.getUUIDNodeValue(node, "uuid").ifPresent(builder::uuid); if(node.has("property")) { Parsers.getNodeValue(node, "property", JsonNode::isObject, ProfileProperty.class) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java index 37f0489..4658e9d 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -24,7 +24,7 @@ public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) thro return null; } ToolComponent toolComponent = new ItemStack(Material.STONE).getItemMeta().getTool(); - Parsers.getNodeIntValue(node, "damagePerBlock").ifPresent(toolComponent::setDamagePerBlock); + Parsers.getIntNodeValue(node, "damagePerBlock").ifPresent(toolComponent::setDamagePerBlock); Parsers.getFloatNodeValue(node, "defaultMiningSpeed").ifPresent(toolComponent::setDefaultMiningSpeed); Parsers.getToolRulesArrayNodeValue(node, "rules") .ifPresent(rulesList -> rulesList.forEach(ruleValue -> toolComponent.addRule(ruleValue.block(), ruleValue.speed(), ruleValue.correctForDrops()))); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java index 2372bcf..64013d6 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -98,10 +98,10 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc // as an object {"red": 255, "blue": 120, "green": 41, "alpha": 255} if(node.isObject()) { Map map = ItemCreator.getMapper().convertValue(node, Map.class); - Optional red = Parsers.getNodeIntValue(node, "red"); - Optional green = Parsers.getNodeIntValue(node, "green"); - Optional blue = Parsers.getNodeIntValue(node, "blue"); - Optional alpha = Parsers.getNodeIntValue(node, "alpha"); + Optional red = Parsers.getIntNodeValue(node, "red"); + Optional green = Parsers.getIntNodeValue(node, "green"); + Optional blue = Parsers.getIntNodeValue(node, "blue"); + Optional alpha = Parsers.getIntNodeValue(node, "alpha"); if(red.isEmpty() || blue.isEmpty() || green.isEmpty()) { return null; } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index f97f040..017c0fc 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -57,17 +57,17 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese ItemStack item = itemNms.asBukkitCopy(); - Parsers.getNodeComponentValue(node, "itemName") + Parsers.getComponentNodeValue(node, "itemName") .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); Parsers.getComponentArrayNodeValue(node, "lore") .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); - Parsers.getNodeIntValue(node, "maxStackSize") + Parsers.getIntNodeValue(node, "maxStackSize") .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); - Parsers.getNodeNamespacedKeyValue(node, "itemModel") + Parsers.getNamespacedKeyNodeValue(node, "itemModel") .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); - Parsers.getNodeIntValue(node, "maxDamage") + Parsers.getIntNodeValue(node, "maxDamage") .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); @@ -79,16 +79,16 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); - Parsers.getNodeIntValue(node, "amount").ifPresent(item::setAmount); + Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); - Parsers.getNodeIntValue(node, "damage") + Parsers.getIntNodeValue(node, "damage") .ifPresent(damage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setDamage(damage))); Parsers.getNodeValue(node, "deathProtection", JsonNode::isArray, DeathProtection.class) .ifPresent(deathProtection -> item.setData(DataComponentTypes.DEATH_PROTECTION, deathProtection)); - Parsers.getNodeColorValue(node, "dyedColor") + Parsers.getColorNodeValue(node, "dyedColor") .ifPresent(color -> item.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color))); Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); @@ -100,7 +100,7 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese CustomItem.Builder builder = CustomItem.builder() .item(item); - Parsers.getNodeItemKeyValue(node, "useRemainder").ifPresent(builder::useRemainder); + Parsers.getItemKeyNodeValue(node, "useRemainder").ifPresent(builder::useRemainder); return builder.build(); } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java index 7aa8cf3..dde87bd 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java @@ -24,8 +24,8 @@ public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOE Optional key; Optional amount; if(node.isObject()) { - key = Parsers.getNodeKeyValue(node, "id"); - amount = Parsers.getNodeIntValue(node, "amount"); + key = Parsers.getKeyNodeValue(node, "id"); + amount = Parsers.getIntNodeValue(node, "amount"); } else if (node.isTextual() && !node.asText().isBlank()){ key = Optional.of(Key.key(node.asText())); amount = Optional.of(1); diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index a6ea845..0e46f2c 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -79,39 +79,39 @@ static Optional> getToolRulesArrayNodeValu return Optional.of(ruleValues); } - static Optional getNodeIntValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getIntNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isIntegralNumber, Integer.class); } - static Optional getNodeComponentValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getComponentNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, Component.class); } - static Optional getNodeNamespacedKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getNamespacedKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, NamespacedKey.class); } - static Optional getNodeKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isTextual, Key.class); } - static Optional getNodeItemStackValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getItemStackNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemStack.class); } - static Optional getNodeItemKeyValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getItemKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemKey.class); } - static Optional getNodePotionEffectValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getPotionEffectNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isObject, PotionEffect.class); } - static Optional getNodeColorValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getColorNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, Objects::nonNull, Color.class); } - static Optional getNodeUUIDValue(@NonNull JsonNode node, @NonNull String valueName) { + static Optional getUUIDNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, n -> n.isTextual() && !n.isEmpty(), UUID.class); } } From 1c1ed9ddb3421edfc7bebd43723a58d7156d9544 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 19:07:06 +0200 Subject: [PATCH 23/31] refactor: disable recipe logs, for now --- src/main/java/re/sylfa/itemcreator/ItemCreator.java | 12 ++++++++++-- .../re/sylfa/itemcreator/recipes/RecipeReader.java | 6 +++--- .../re/sylfa/itemcreator/recipes/RecipeRegistry.java | 6 +++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 1f26b93..079e1ca 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -104,11 +104,19 @@ public void init(){ CustomRecipe::key, CustomRecipe::asRecipe )); - recipeRegistry.getAll().keySet().forEach(key -> Log.log("Removing %s: %b", key.asString(), Bukkit.removeRecipe(key))); + recipeRegistry.getAll().keySet() + .forEach(key -> { + boolean recipeRemoved = Bukkit.removeRecipe(key); + // TODO toggleable in configuration +// Log.log("Removing %s: %b", key.asString(), recipeRemoved); + } + ); recipeRegistry.removeAll(); readRecipes.forEach(recipeRegistry::add); - readRecipesMap.values().forEach(r -> Bukkit.addRecipe(r, true)); + // resendRecipes = false does not work for now + readRecipesMap.values().forEach(r -> Bukkit.addRecipe(r, false)); + Bukkit.addRecipe(null, true); } public void reload() { diff --git a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java index dc00211..698f2f0 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java @@ -35,9 +35,8 @@ public static List readAllRecipes() { String folderName = folder.getName(); return JavaUtils.arrayStream(folder.listFiles()) .filter(file -> { -// if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { - Log.warn("%s is not an item file in %s", file.getName(), folderName); + Log.warn("%s is not a recipe file in %s", file.getName(), folderName); return false; } else { return true; @@ -56,7 +55,8 @@ public static CustomRecipe readRecipe(File file) { } public static CustomRecipe readRecipe(YamlConfiguration config, String fileName) { - Log.log("Reading recipe %s", fileName); + // TODO toggleable in configuration +// Log.log("Reading recipe %s", fileName); Map shapedIngredients; if(config.isSet("shapedIngredients")) { shapedIngredients = config.getConfigurationSection("shapedIngredients").getValues(false) diff --git a/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java b/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java index 81e9f6f..2a5dfa4 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java @@ -1,12 +1,12 @@ package re.sylfa.itemcreator.recipes; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; - public class RecipeRegistry { Map recipes = new HashMap(); From 959d142921bdea0c7c1ff8f6040e6d3f6bc5f4db Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 20:01:37 +0200 Subject: [PATCH 24/31] refactor: CustomItemDeserializer - sort component alphabetically --- .../re/sylfa/itemcreator/ItemCreator.java | 3 +- .../types/CustomItemDeserializer.java | 59 ++++++++++--------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 079e1ca..1c7ef96 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -114,9 +114,8 @@ public void init(){ recipeRegistry.removeAll(); readRecipes.forEach(recipeRegistry::add); - // resendRecipes = false does not work for now + // FIXME resendRecipes = false does not work for now readRecipesMap.values().forEach(r -> Bukkit.addRecipe(r, false)); - Bukkit.addRecipe(null, true); } public void reload() { diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 017c0fc..60dd9ba 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -56,46 +56,51 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese itemNms.remove(DataComponents.ENCHANTABLE); ItemStack item = itemNms.asBukkitCopy(); - - Parsers.getComponentNodeValue(node, "itemName") - .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); - Parsers.getComponentArrayNodeValue(node, "lore") - .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); - Parsers.getIntNodeValue(node, "maxStackSize") - .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); - Parsers.getNamespacedKeyNodeValue(node, "itemModel") - .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); - Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) - .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); - Parsers.getIntNodeValue(node, "maxDamage") - .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); - Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) - .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); - Parsers.getBooleanNodeValue(node, "glider") - .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); - Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") - .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); - Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) - .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); - Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) - .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); - Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) - .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); + Parsers.getKeyNodeValue(node, "breakSound") + .ifPresent(sound -> item.setData(DataComponentTypes.BREAK_SOUND, sound)); Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); + Parsers.getComponentNodeValue(node, "customName") + .ifPresent(customName -> item.getItemMeta().displayName(customName)); Parsers.getIntNodeValue(node, "damage") .ifPresent(damage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setDamage(damage))); Parsers.getNodeValue(node, "deathProtection", JsonNode::isArray, DeathProtection.class) .ifPresent(deathProtection -> item.setData(DataComponentTypes.DEATH_PROTECTION, deathProtection)); Parsers.getColorNodeValue(node, "dyedColor") .ifPresent(color -> item.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color))); - Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) - .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); Parsers.getNodeValue(node, "enchantable", JsonNode::isObject, Enchantable.class) .ifPresent(enchantable -> item.setData(DataComponentTypes.ENCHANTABLE, enchantable)); + Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") + .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); + Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) + .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); + Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) + .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); + Parsers.getBooleanNodeValue(node, "glider") + .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); + Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) + .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); + Parsers.getNamespacedKeyNodeValue(node, "itemModel") + .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); + Parsers.getComponentNodeValue(node, "itemName") + .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); + Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) + .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + Parsers.getComponentArrayNodeValue(node, "lore") + .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); + Parsers.getIntNodeValue(node, "maxStackSize") + .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); + Parsers.getIntNodeValue(node, "maxDamage") + .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); + Parsers.getKeyNodeValue(node, "noteBlockSound") + .ifPresent(soundKey -> item.setData(DataComponentTypes.NOTE_BLOCK_SOUND, soundKey)); Parsers.getNodeValue(node, "profile", JsonNode::isObject, ResolvableProfile.class) .ifPresent(profile -> item.setData(DataComponentTypes.PROFILE, profile)); + Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) + .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); + Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) + .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); CustomItem.Builder builder = CustomItem.builder() From 7f87b1e99e2a23adc2c823349558c54f22948c98 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sat, 13 Sep 2025 20:12:06 +0200 Subject: [PATCH 25/31] feature: repairCost, tooltipStyle and unbreakable components --- .../deserializers/types/CustomItemDeserializer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 60dd9ba..55f289f 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -99,8 +99,14 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(profile -> item.setData(DataComponentTypes.PROFILE, profile)); Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); + Parsers.getIntNodeValue(node, "repairCost") + .ifPresent(cost -> item.setData(DataComponentTypes.REPAIR_COST, cost)); Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); + Parsers.getKeyNodeValue(node, "tooltipStyle") + .ifPresent(style -> item.setData(DataComponentTypes.TOOLTIP_STYLE, style)); + Parsers.getBooleanNodeValue(node, "unbreakable") + .ifPresent(unbreakable -> item.getItemMeta().setUnbreakable(unbreakable)); CustomItem.Builder builder = CustomItem.builder() From c19d01ee191c7d2e50e7910abeb435e1d150c6af Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:05:13 +0200 Subject: [PATCH 26/31] feature: attributes component --- .../re/sylfa/itemcreator/ItemCreator.java | 6 + .../AttributeModifiersDeserializer.java | 126 ++++++++++++++++++ .../types/CustomItemDeserializer.java | 3 + .../re/sylfa/itemcreator/util/Parsers.java | 4 + 4 files changed, 139 insertions(+) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index 1c7ef96..d7bb302 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -7,7 +7,9 @@ import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; import io.papermc.paper.datacomponent.item.ResolvableProfile; +import io.papermc.paper.datacomponent.item.attribute.AttributeModifierDisplay; import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; @@ -34,6 +36,7 @@ import org.bukkit.util.permissions.CommandPermissions; import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; +import re.sylfa.itemcreator.deserializers.components.AttributeModifiersDeserializer; import re.sylfa.itemcreator.deserializers.components.ConsumableDeserializer; import re.sylfa.itemcreator.deserializers.components.ConsumeEffectDeserializer; import re.sylfa.itemcreator.deserializers.components.DeathProtectionDeserializer; @@ -160,6 +163,9 @@ private void registerMappers() { .addDeserializer(Enchantable.class, new EnchantableDeserializer()) .addDeserializer(ResolvableProfile.class, new ProfileDeserializer()) .addDeserializer(ProfileProperty.class, new ProfileDeserializer.ProfilePropertyDeserializer()) + .addDeserializer(ItemAttributeModifiers.class, new AttributeModifiersDeserializer()) + .addDeserializer(AttributeModifiersDeserializer.AttributeModification.class, new AttributeModifiersDeserializer.AttributeModifierDeserializer()) + .addDeserializer(AttributeModifierDisplay.class, new AttributeModifiersDeserializer.AttributeModifierDisplayDeserializer()) ; mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java new file mode 100644 index 0000000..a3ab94a --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java @@ -0,0 +1,126 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; +import io.papermc.paper.datacomponent.item.attribute.AttributeModifierDisplay; +import net.kyori.adventure.text.Component; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.checkerframework.checker.nullness.qual.Nullable; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +public class AttributeModifiersDeserializer extends StdDeserializer { + + public AttributeModifiersDeserializer() { + super(ItemAttributeModifiers.class); + } + + @Override + public ItemAttributeModifiers deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(!node.isArray()) { + return null; + } + + ItemAttributeModifiers.Builder builder = ItemAttributeModifiers.itemAttributes(); + JavaUtils.iteratorStream(node.elements()) + .map(parsedAttribute -> ItemCreator.getMapper().convertValue(parsedAttribute, AttributeModification.class)) + .filter(Objects::nonNull) + .forEach(parsedAttribute -> parsedAttribute.addToBuilder(builder)); + + return builder.build(); + } + + public record AttributeModification( + Attribute attribute, + NamespacedKey key, + double amount, + AttributeModifier.Operation operation, + @Nullable AttributeModifierDisplay display, + EquipmentSlotGroup equipmentSlotGroup + ) { + public void addToBuilder(ItemAttributeModifiers.Builder builder) { + AttributeModifier modifier = new AttributeModifier( + key, + amount, + operation, + equipmentSlotGroup + ); + + if(display != null) { + builder.addModifier(attribute, modifier, display); + } else { + builder.addModifier(attribute, modifier); + } + } + } + + public static class AttributeModifierDeserializer extends StdDeserializer { + public AttributeModifierDeserializer() { + super(AttributeModification.class); + } + + @Override + public AttributeModification deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { + return null; + } + Optional attribute = Parsers.getNodeValue(node, "attribute", JsonNode::isTextual, Attribute.class); + Optional key = Parsers.getNamespacedKeyNodeValue(node, "key"); + Optional amount = Parsers.getDoubleNodeValue(node, "amount"); + Optional operation = Parsers.getNodeValue(node, "operation", JsonNode::isTextual, AttributeModifier.Operation.class); + + if(attribute.isEmpty() || key.isEmpty() || amount.isEmpty() || operation.isEmpty()) { + return null; + } + + Optional display = Parsers.getNodeValue(node, "display", JsonNode::isTextual, AttributeModifierDisplay.class); + EquipmentSlotGroup slotGroup = Parsers.getNodeValue(node, "slotGroup", JsonNode::isTextual, EquipmentSlotGroup.class) + .orElse(EquipmentSlotGroup.ANY); + return new AttributeModification( + attribute.get(), + key.get(), + amount.get(), + operation.get(), + display.orElse(null), + slotGroup + ); + } + } + + public static class AttributeModifierDisplayDeserializer extends StdDeserializer { + public AttributeModifierDisplayDeserializer() { + super(AttributeModifierDisplay.class); + } + + @Override + public AttributeModifierDisplay deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = p.getText(); + return switch (value) { + case "reset" -> AttributeModifierDisplay.reset(); + case "hidden" -> AttributeModifierDisplay.hidden(); + default -> { + // try to parse the display as a component + Component component = ItemCreator.getMapper().convertValue(value, Component.class); + if(component == null) { + yield null; + } + + yield AttributeModifierDisplay.override(component); + } + }; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 55f289f..34291dc 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -9,6 +9,7 @@ import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.DyedItemColor; import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; import io.papermc.paper.datacomponent.item.ResolvableProfile; import net.minecraft.core.component.DataComponents; import org.bukkit.Material; @@ -57,6 +58,8 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese ItemStack item = itemNms.asBukkitCopy(); Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); + Parsers.getNodeValue(node, "attributes", JsonNode::isArray, ItemAttributeModifiers.class) + .ifPresent(attributes -> item.setData(DataComponentTypes.ATTRIBUTE_MODIFIERS, attributes)); Parsers.getKeyNodeValue(node, "breakSound") .ifPresent(sound -> item.setData(DataComponentTypes.BREAK_SOUND, sound)); Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 0e46f2c..071a6cb 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -29,6 +29,10 @@ static Optional getNodeValue(@NonNull JsonNode node, @NonNull String valu : Optional.empty(); } + static Optional getDoubleNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isNumber, Double.class); + } + static Optional getFloatNodeValue(@NonNull JsonNode node, @NonNull String valueName) { return getNodeValue(node, valueName, JsonNode::isNumber, Float.class); } From 135472c0903d26bf303bfa280a5bee36b4f2fa8e Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:25:03 +0200 Subject: [PATCH 27/31] fix: set equipment slot in attribute builder instead of AttributeModifier --- .../AttributeModifiersDeserializer.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java index a3ab94a..8844e6e 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java @@ -54,14 +54,13 @@ public void addToBuilder(ItemAttributeModifiers.Builder builder) { AttributeModifier modifier = new AttributeModifier( key, amount, - operation, - equipmentSlotGroup + operation ); if(display != null) { - builder.addModifier(attribute, modifier, display); + builder.addModifier(attribute, modifier, equipmentSlotGroup, display); } else { - builder.addModifier(attribute, modifier); + builder.addModifier(attribute, modifier, equipmentSlotGroup); } } } @@ -87,15 +86,20 @@ public AttributeModification deserialize(JsonParser p, DeserializationContext ct } Optional display = Parsers.getNodeValue(node, "display", JsonNode::isTextual, AttributeModifierDisplay.class); - EquipmentSlotGroup slotGroup = Parsers.getNodeValue(node, "slotGroup", JsonNode::isTextual, EquipmentSlotGroup.class) + EquipmentSlotGroup slot = Optional.ofNullable(node.get("slot")) + .map(JsonNode::asText) + .filter(Objects::nonNull) + .map(EquipmentSlotGroup::getByName) + .filter(Objects::nonNull) .orElse(EquipmentSlotGroup.ANY); + return new AttributeModification( attribute.get(), key.get(), amount.get(), operation.get(), display.orElse(null), - slotGroup + slot ); } } From ac13690fcb9de79d8488b89d7481eb2ef1b3ad54 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:25:15 +0200 Subject: [PATCH 28/31] chore: update to MC 1.21.10 --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d36c82e..e6c6a72 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ # This file was generated by the Gradle 'init' task. # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -paper = "1.21.8-R0.1-SNAPSHOT" +paper = "1.21.10-R0.1-SNAPSHOT" paperweight = "2.0.0-beta.18" brigadier = "1.0.18" runPaper = "3.0.0-beta.1" -lombok = "1.18.38" -adventure = "4.24.0" +lombok = "1.18.42" +adventure = "4.25.0" commonsIo = "2.20.0" jackson = "2.20.0" From 4a0fede0c2aa79336990140ae404582f329ca36a Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Tue, 14 Oct 2025 23:43:22 +0200 Subject: [PATCH 29/31] feature: baseColor and customModelData components --- .../re/sylfa/itemcreator/ItemCreator.java | 3 ++ .../CustomModelDataDeserializer.java | 36 +++++++++++++++++++ .../types/CustomItemDeserializer.java | 6 ++++ 3 files changed, 45 insertions(+) create mode 100644 src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index d7bb302..f59698f 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.CustomModelData; import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.Enchantable; import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; @@ -39,6 +40,7 @@ import re.sylfa.itemcreator.deserializers.components.AttributeModifiersDeserializer; import re.sylfa.itemcreator.deserializers.components.ConsumableDeserializer; import re.sylfa.itemcreator.deserializers.components.ConsumeEffectDeserializer; +import re.sylfa.itemcreator.deserializers.components.CustomModelDataDeserializer; import re.sylfa.itemcreator.deserializers.components.DeathProtectionDeserializer; import re.sylfa.itemcreator.deserializers.components.EnchantableDeserializer; import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; @@ -166,6 +168,7 @@ private void registerMappers() { .addDeserializer(ItemAttributeModifiers.class, new AttributeModifiersDeserializer()) .addDeserializer(AttributeModifiersDeserializer.AttributeModification.class, new AttributeModifiersDeserializer.AttributeModifierDeserializer()) .addDeserializer(AttributeModifierDisplay.class, new AttributeModifiersDeserializer.AttributeModifierDisplayDeserializer()) + .addDeserializer(CustomModelData.class, new CustomModelDataDeserializer()) ; mapper.registerModule(module); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java new file mode 100644 index 0000000..bde280b --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java @@ -0,0 +1,36 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import io.papermc.paper.datacomponent.item.CustomModelData; +import org.bukkit.Color; +import re.sylfa.itemcreator.util.Parsers; + +import java.io.IOException; +import java.util.Arrays; + +public class CustomModelDataDeserializer extends StdDeserializer { + public CustomModelDataDeserializer() { + super(CustomModelData.class); + } + + @Override + public CustomModelData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + JsonNode node = p.readValueAsTree(); + CustomModelData.Builder builder = CustomModelData.customModelData(); + + Parsers.getNodeValue(node, "flags", JsonNode::isArray, Boolean[].class) + .ifPresent(flags -> builder.addFlags(Arrays.asList(flags))); + Parsers.getNodeValue(node, "floats", JsonNode::isArray, Float[].class) + .ifPresent(floats -> builder.addFloats(Arrays.asList(floats))); + Parsers.getNodeValue(node, "colors", JsonNode::isArray, Color[].class) + .ifPresent(colors -> builder.addColors(Arrays.asList(colors))); + Parsers.getNodeValue(node, "strings", JsonNode::isArray, String[].class) + .ifPresent(strings -> builder.addStrings(Arrays.asList(strings))); + + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index 34291dc..c0fb579 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -6,12 +6,14 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.CustomModelData; import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.DyedItemColor; import io.papermc.paper.datacomponent.item.Enchantable; import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; import io.papermc.paper.datacomponent.item.ResolvableProfile; import net.minecraft.core.component.DataComponents; +import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.MusicInstrument; import org.bukkit.craftbukkit.inventory.CraftItemStack; @@ -60,10 +62,14 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); Parsers.getNodeValue(node, "attributes", JsonNode::isArray, ItemAttributeModifiers.class) .ifPresent(attributes -> item.setData(DataComponentTypes.ATTRIBUTE_MODIFIERS, attributes)); + Parsers.getNodeValue(node, "baseColor", JsonNode::isTextual, DyeColor.class) + .ifPresent(color -> item.setData(DataComponentTypes.BASE_COLOR, color)); Parsers.getKeyNodeValue(node, "breakSound") .ifPresent(sound -> item.setData(DataComponentTypes.BREAK_SOUND, sound)); Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); + Parsers.getNodeValue(node, "customModelData", JsonNode::isObject, CustomModelData.class) + .ifPresent(modelData -> item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, modelData)); Parsers.getComponentNodeValue(node, "customName") .ifPresent(customName -> item.getItemMeta().displayName(customName)); Parsers.getIntNodeValue(node, "damage") From ae39111313a0d10c17e339b304a4d9b6efa4b611 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 17 May 2026 17:31:01 +0200 Subject: [PATCH 30/31] refactor: shadow jackson in plugin + dependencies version bump --- build.gradle.kts | 9 ++++++ gradle/libs.versions.toml | 16 +++++----- .../re/sylfa/itemcreator/ItemCreator.java | 12 +++++--- .../AttributeModifiersDeserializer.java | 29 +++++++++---------- .../components/ConsumableDeserializer.java | 14 ++++----- .../components/ConsumeEffectDeserializer.java | 15 +++++----- .../CustomModelDataDeserializer.java | 12 ++++---- .../DeathProtectionDeserializer.java | 11 ++++--- .../components/EnchantableDeserializer.java | 13 ++++----- .../components/EquippableDeserializer.java | 17 +++++------ .../components/FoodDeserializer.java | 12 ++++---- .../JukeboxPlayableDeserializer.java | 9 +++--- .../MusicInstrumentDeserializer.java | 17 +++++------ .../components/PotionEffectDeserializer.java | 13 ++++----- .../PotionEffectTypeDeserializer.java | 16 +++++----- .../components/ProfileDeserializer.java | 17 +++++------ .../components/ToolDeserializer.java | 12 ++++---- .../components/ToolRuleDeserializer.java | 11 ++++--- .../types/ColorDeserializer.java | 17 +++++------ .../types/CustomItemDeserializer.java | 17 +++++------ .../deserializers/types/ItemDeserializer.java | 12 ++++---- .../types/ItemKeyDeserializer.java | 17 +++++------ .../deserializers/types/KeyDeserializer.java | 10 +++---- .../types/MaterialDeserializer.java | 9 +++--- .../types/NamespacedKeyDeserializer.java | 10 +++---- .../types/TextComponentDeserializer.java | 9 +++--- .../sylfa/itemcreator/items/ItemReader.java | 11 ++----- .../re/sylfa/itemcreator/util/Parsers.java | 18 ++++++------ 28 files changed, 179 insertions(+), 206 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 56d2ac2..cef5c6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ plugins { `java-library` alias { libs.plugins.paperweight } alias { libs.plugins.runPaper } + alias { libs.plugins.shadow } } repositories { @@ -25,4 +26,12 @@ dependencies { java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +tasks.shadowJar { + dependencies { + include(dependency("tools.jackson.core:jackson-databind")) + include(dependency("tools.jackson.core:jackson-core")) + include(dependency("com.fasterxml.jackson.core:jackson-annotations")) + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6c6a72..afa616d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,24 @@ # This file was generated by the Gradle 'init' task. # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -paper = "1.21.10-R0.1-SNAPSHOT" +paper = "1.21.11-R0.1-SNAPSHOT" paperweight = "2.0.0-beta.18" -brigadier = "1.0.18" +brigadier = "1.0.500" runPaper = "3.0.0-beta.1" lombok = "1.18.42" -adventure = "4.25.0" -commonsIo = "2.20.0" -jackson = "2.20.0" +adventure = "5.1.1" +commonsIo = "2.22.0" +jackson = "3.1.3" +shadow = "9.3.0" [libraries] brigadier = {group = "com.mojang", name = "brigadier", version.ref = "brigadier"} lombok = {group = "org.projectlombok", name = "lombok", version.ref = "lombok"} miniMessage = {group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure"} commonsIo = {group = "commons-io", name = "commons-io", version.ref = "commonsIo"} -jackson = {group = "com.fasterxml.jackson.core", name = "jackson-core", version.ref = "jackson"} +jackson = {group = "tools.jackson.core", name = "jackson-databind", version.ref = "jackson"} [plugins] paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight"} -runPaper = { id = "xyz.jpenilla.run-paper", version.ref="runPaper"} \ No newline at end of file +runPaper = { id = "xyz.jpenilla.run-paper", version.ref="runPaper"} +shadow = { id = "com.gradleup.shadow", version.ref="shadow" } \ No newline at end of file diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index f59698f..ac9450a 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -1,8 +1,6 @@ package re.sylfa.itemcreator; import com.destroystokyo.paper.profile.ProfileProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.CustomModelData; @@ -68,6 +66,9 @@ import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; import re.sylfa.itemcreator.util.Log; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; import java.util.List; import java.util.Map; @@ -83,7 +84,7 @@ public class ItemCreator extends JavaPlugin { private static final RecipeRegistry recipeRegistry = new RecipeRegistry(); @Getter - private static final ObjectMapper mapper = new ObjectMapper(); + private static ObjectMapper mapper; @Override @@ -142,6 +143,7 @@ private void registerCommands() { } private void registerMappers() { + SimpleModule module = new SimpleModule(); module.addDeserializer(CustomItem.class, new CustomItemDeserializer()) .addDeserializer(ItemStack.class, new ItemDeserializer()) @@ -171,7 +173,9 @@ private void registerMappers() { .addDeserializer(CustomModelData.class, new CustomModelDataDeserializer()) ; - mapper.registerModule(module); + mapper = JsonMapper.builder() + .addModule(module) + .build(); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java index 8844e6e..8b70a05 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java @@ -1,9 +1,5 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; import io.papermc.paper.datacomponent.item.attribute.AttributeModifierDisplay; import net.kyori.adventure.text.Component; @@ -13,10 +9,12 @@ import org.bukkit.inventory.EquipmentSlotGroup; import org.checkerframework.checker.nullness.qual.Nullable; import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Objects; import java.util.Optional; @@ -27,14 +25,14 @@ public AttributeModifiersDeserializer() { } @Override - public ItemAttributeModifiers deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ItemAttributeModifiers deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(!node.isArray()) { return null; } ItemAttributeModifiers.Builder builder = ItemAttributeModifiers.itemAttributes(); - JavaUtils.iteratorStream(node.elements()) + node.valueStream() .map(parsedAttribute -> ItemCreator.getMapper().convertValue(parsedAttribute, AttributeModification.class)) .filter(Objects::nonNull) .forEach(parsedAttribute -> parsedAttribute.addToBuilder(builder)); @@ -71,24 +69,23 @@ public AttributeModifierDeserializer() { } @Override - public AttributeModification deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public AttributeModification deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null || !node.isObject()) { return null; } - Optional attribute = Parsers.getNodeValue(node, "attribute", JsonNode::isTextual, Attribute.class); + Optional attribute = Parsers.getNodeValue(node, "attribute", JsonNode::isString, Attribute.class); Optional key = Parsers.getNamespacedKeyNodeValue(node, "key"); Optional amount = Parsers.getDoubleNodeValue(node, "amount"); - Optional operation = Parsers.getNodeValue(node, "operation", JsonNode::isTextual, AttributeModifier.Operation.class); + Optional operation = Parsers.getNodeValue(node, "operation", JsonNode::isString, AttributeModifier.Operation.class); if(attribute.isEmpty() || key.isEmpty() || amount.isEmpty() || operation.isEmpty()) { return null; } - Optional display = Parsers.getNodeValue(node, "display", JsonNode::isTextual, AttributeModifierDisplay.class); + Optional display = Parsers.getNodeValue(node, "display", JsonNode::isString, AttributeModifierDisplay.class); EquipmentSlotGroup slot = Optional.ofNullable(node.get("slot")) - .map(JsonNode::asText) - .filter(Objects::nonNull) + .map(JsonNode::asString) .map(EquipmentSlotGroup::getByName) .filter(Objects::nonNull) .orElse(EquipmentSlotGroup.ANY); @@ -110,8 +107,8 @@ public AttributeModifierDisplayDeserializer() { } @Override - public AttributeModifierDisplay deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String value = p.getText(); + public AttributeModifierDisplay deserialize(JsonParser p, DeserializationContext ctxt) { + String value = p.getString(); return switch (value) { case "reset" -> AttributeModifierDisplay.reset(); case "hidden" -> AttributeModifierDisplay.hidden(); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java index 98dbdf0..242fd47 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java @@ -1,29 +1,27 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; import re.sylfa.itemcreator.util.Parsers; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class ConsumableDeserializer extends StdDeserializer { public ConsumableDeserializer() { super(Consumable.class); } @Override - public Consumable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Consumable deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; } Consumable.Builder builder = Consumable.consumable(); - Parsers.getNodeValue(node, "animation", JsonNode::isTextual, ItemUseAnimation.class) + Parsers.getNodeValue(node, "animation", JsonNode::isString, ItemUseAnimation.class) .ifPresent(builder::animation); Parsers.getFloatNodeValue(node, "consumeSeconds").ifPresent(builder::consumeSeconds); Parsers.getBooleanNodeValue(node, "hasConsumeParticles").ifPresent(builder::hasConsumeParticles); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java index 44903f1..c837bb2 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java @@ -1,9 +1,5 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.set.RegistrySet; @@ -13,8 +9,11 @@ import org.bukkit.potion.PotionEffectType; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -26,9 +25,9 @@ public class ConsumeEffectDeserializer extends StdDeserializer { public ConsumeEffectDeserializer() { super(ConsumeEffect.class); } @Override - public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); - Optional type = Parsers.getNodeValue(node, "type", JsonNode::isTextual, Type.class); + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isString, Type.class); if(type.isEmpty()) { return null; } @@ -63,7 +62,7 @@ public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) thro Set effectTypes = new HashSet<>(); for(JsonNode effect : effects) { - String rawKey = effect.asText(); + String rawKey = effect.asString(); if(rawKey == null || rawKey.isBlank()) { continue; } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java index bde280b..a7ecb8f 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java @@ -1,15 +1,13 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.CustomModelData; import org.bukkit.Color; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Arrays; public class CustomModelDataDeserializer extends StdDeserializer { @@ -18,7 +16,7 @@ public CustomModelDataDeserializer() { } @Override - public CustomModelData deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public CustomModelData deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); CustomModelData.Builder builder = CustomModelData.customModelData(); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java index 3734b5e..f09c089 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java @@ -1,21 +1,20 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.DeathProtection; import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; import re.sylfa.itemcreator.ItemCreator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class DeathProtectionDeserializer extends StdDeserializer { public DeathProtectionDeserializer() { super(DeathProtection.class); } @Override - public DeathProtection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public DeathProtection deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null || !node.isArray()) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java index 3491b94..88b6025 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java @@ -1,15 +1,12 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.Enchantable; import org.checkerframework.checker.nullness.qual.Nullable; import re.sylfa.itemcreator.util.Log; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class EnchantableDeserializer extends StdDeserializer { private static final int NOT_FOUND = -1; @@ -18,7 +15,7 @@ public EnchantableDeserializer() { } @Override @Nullable - public Enchantable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public Enchantable deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java index 4a2dd63..5fca660 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java @@ -1,9 +1,5 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; @@ -11,8 +7,11 @@ import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class EquippableDeserializer extends StdDeserializer { @@ -21,17 +20,17 @@ public EquippableDeserializer() { } @Override - public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; } JsonNode slotNode = node.get("slot"); EquipmentSlot slot; - if(slotNode != null && slotNode.isTextual()) { + if(slotNode != null && slotNode.isString()) { slot = ItemCreator.getMapper().convertValue(slotNode, EquipmentSlot.class); if(slot == null) { - Log.error("%s is not a valid slot type", slotNode.textValue()); + Log.error("%s is not a valid slot type", slotNode.stringValue()); } } else { @@ -42,7 +41,7 @@ public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt EquippableComponent equippableComponent = new ItemStack(Material.STONE).getItemMeta().getEquippable(); Parsers.getBooleanNodeValue(node, "dispensable").ifPresent(equippableComponent::setDispensable); - Parsers.getBooleanNodeValue(node, "damageOnHurt").ifPresent(damage -> equippableComponent.setDamageOnHurt(damage)); + Parsers.getBooleanNodeValue(node, "damageOnHurt").ifPresent(equippableComponent::setDamageOnHurt); Parsers.getBooleanNodeValue(node, "equipOnInteract").ifPresent(equippableComponent::setEquipOnInteract); Parsers.getNamespacedKeyNodeValue(node, "model").ifPresent(equippableComponent::setModel); Parsers.getNamespacedKeyNodeValue(node, "cameraOverlay").ifPresent(equippableComponent::setCameraOverlay); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java index bc5fb93..f1f4b1d 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java @@ -1,22 +1,20 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.components.FoodComponent; import re.sylfa.itemcreator.util.Parsers; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class FoodDeserializer extends StdDeserializer { public FoodDeserializer() { super(FoodComponent.class); } @Override - public FoodComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public FoodComponent deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java index 7a2a470..e35b11d 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java @@ -1,21 +1,20 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class JukeboxPlayableDeserializer extends StdDeserializer { public JukeboxPlayableDeserializer() { super(JukeboxPlayableComponent.class); } @Override - public JukeboxPlayableComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public JukeboxPlayableComponent deserialize(JsonParser p, DeserializationContext ctxt) { Optional songKey = Optional.ofNullable(NamespacedKey.fromString(p.getValueAsString())); if(songKey.isEmpty()) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java index 0b2236b..286423f 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java @@ -1,9 +1,5 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; @@ -12,8 +8,11 @@ import org.bukkit.MusicInstrument; import org.bukkit.Sound; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class MusicInstrumentDeserializer extends StdDeserializer { @@ -21,15 +20,15 @@ public class MusicInstrumentDeserializer extends StdDeserializer { public PotionEffectDeserializer() { super(PotionEffect.class); } @Override - public PotionEffect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public PotionEffect deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null || !node.isObject()) { return null; } - Optional type = Parsers.getNodeValue(node, "type", JsonNode::isTextual, PotionEffectType.class); + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isString, PotionEffectType.class); Optional duration = Parsers.getIntNodeValue(node, "duration"); Optional amplifier = Parsers.getIntNodeValue(node, "amplifier"); boolean ambient = Parsers.getBooleanNodeValue(node, "ambient").orElse(true); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java index aa88817..da5feb2 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java @@ -1,25 +1,23 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import net.kyori.adventure.key.Key; import org.bukkit.Registry; import org.bukkit.potion.PotionEffectType; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class PotionEffectTypeDeserializer extends StdDeserializer { public PotionEffectTypeDeserializer() { super(PotionEffectType.class); } @Override - public PotionEffectType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public PotionEffectType deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); - if(node == null || !node.isTextual()) { + if(node == null || !node.isString()) { return null; } - Key key = Key.key(node.asText()); + Key key = Key.key(node.asString()); return Registry.MOB_EFFECT.get(key); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java index 5ff7fec..eac3aff 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java @@ -1,17 +1,14 @@ package re.sylfa.itemcreator.deserializers.components; import com.destroystokyo.paper.profile.ProfileProperty; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.item.ResolvableProfile; import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Objects; import java.util.Optional; @@ -20,7 +17,7 @@ public ProfileDeserializer() { super(ResolvableProfile.class); } @Override - public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; @@ -36,7 +33,7 @@ public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) .ifPresent(builder::addProperty); } else if (node.has("properties")) { JsonNode properties = node.get("properties"); - JavaUtils.iteratorStream(properties.elements()) + properties.valueStream() .map(currentProperty -> ItemCreator.getMapper().convertValue(currentProperty, ProfileProperty.class)) .filter(Objects::nonNull) .forEach(builder::addProperty); @@ -51,7 +48,7 @@ public ProfilePropertyDeserializer() { } @Override - public ProfileProperty deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ProfileProperty deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java index 4658e9d..0fa8d9a 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -1,15 +1,13 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.components.ToolComponent; import re.sylfa.itemcreator.util.Parsers; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class ToolDeserializer extends StdDeserializer { @@ -18,7 +16,7 @@ public ToolDeserializer() { } @Override - public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java index 2234200..3ac802a 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java @@ -1,21 +1,20 @@ package re.sylfa.itemcreator.deserializers.components; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; import org.bukkit.inventory.meta.components.ToolComponent; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class ToolRuleDeserializer extends StdDeserializer { public ToolRuleDeserializer() { super(ToolComponent.ToolRule.class); } @Override - public RuleValues deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public RuleValues deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); Optional blockMaterial = Parsers.getMaterialNodeValue(node, "block"); Optional speed = Parsers.getFloatNodeValue(node, "speed"); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java index 64013d6..de6bfa0 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -1,15 +1,14 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Color; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -41,7 +40,7 @@ private Color getColorByName(String name) { public ColorDeserializer() { super(Color.class); } @Override - public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Color deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null) { return null; @@ -58,8 +57,8 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc return Color.fromRGB(colorNumber); } - if(node.isTextual()) { - String textValue = node.asText(); + if(node.isString()) { + String textValue = node.asString(); // as an hex value #RRGGBB or #AARRGGBB if(textValue.startsWith("#") && (textValue.length() == 7 || textValue.length() == 9)) { int rgbValue = Integer.valueOf(textValue.substring(1), 16); @@ -70,7 +69,7 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOExc } } else { // with color name - return getColorByName(node.asText()); + return getColorByName(node.asString()); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java index c0fb579..8369767 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -1,9 +1,5 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.item.Consumable; import io.papermc.paper.datacomponent.item.CustomModelData; @@ -27,8 +23,11 @@ import org.bukkit.inventory.meta.components.ToolComponent; import re.sylfa.itemcreator.items.CustomItem; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.List; import java.util.Objects; @@ -39,7 +38,7 @@ public CustomItemDeserializer() { } @Override - public CustomItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + public CustomItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { JsonNode node = jsonParser.readValueAsTree(); if(node == null) { return null; @@ -62,7 +61,7 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); Parsers.getNodeValue(node, "attributes", JsonNode::isArray, ItemAttributeModifiers.class) .ifPresent(attributes -> item.setData(DataComponentTypes.ATTRIBUTE_MODIFIERS, attributes)); - Parsers.getNodeValue(node, "baseColor", JsonNode::isTextual, DyeColor.class) + Parsers.getNodeValue(node, "baseColor", JsonNode::isString, DyeColor.class) .ifPresent(color -> item.setData(DataComponentTypes.BASE_COLOR, color)); Parsers.getKeyNodeValue(node, "breakSound") .ifPresent(sound -> item.setData(DataComponentTypes.BREAK_SOUND, sound)); @@ -94,7 +93,7 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); Parsers.getComponentNodeValue(node, "itemName") .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); - Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isTextual, JukeboxPlayableComponent.class) + Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isString, JukeboxPlayableComponent.class) .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); Parsers.getComponentArrayNodeValue(node, "lore") .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); @@ -106,7 +105,7 @@ public CustomItem deserialize(JsonParser jsonParser, DeserializationContext dese .ifPresent(soundKey -> item.setData(DataComponentTypes.NOTE_BLOCK_SOUND, soundKey)); Parsers.getNodeValue(node, "profile", JsonNode::isObject, ResolvableProfile.class) .ifPresent(profile -> item.setData(DataComponentTypes.PROFILE, profile)); - Parsers.getNodeValue(node, "rarity", JsonNode::isTextual, ItemRarity.class) + Parsers.getNodeValue(node, "rarity", JsonNode::isString, ItemRarity.class) .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); Parsers.getIntNodeValue(node, "repairCost") .ifPresent(cost -> item.setData(DataComponentTypes.REPAIR_COST, cost)); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java index 06261eb..4fe303f 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -1,20 +1,18 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.inventory.ItemStack; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.items.ItemKey; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; public class ItemDeserializer extends StdDeserializer { public ItemDeserializer() { super(ItemStack.class); } @Override - public ItemStack deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ItemStack deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); if(node == null || !node.isObject()) { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java index dde87bd..aca60c9 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java @@ -1,23 +1,22 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import net.kyori.adventure.key.Key; import re.sylfa.itemcreator.items.ItemKey; import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class ItemKeyDeserializer extends StdDeserializer { public ItemKeyDeserializer() { super(ItemKey.class);} @Override - public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = p.readValueAsTree(); - if(node == null || (!node.isTextual() && !node.isObject())) { + if(node == null || (!node.isString() && !node.isObject())) { return null; } @@ -26,8 +25,8 @@ public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOE if(node.isObject()) { key = Parsers.getKeyNodeValue(node, "id"); amount = Parsers.getIntNodeValue(node, "amount"); - } else if (node.isTextual() && !node.asText().isBlank()){ - key = Optional.of(Key.key(node.asText())); + } else if (node.isString() && !node.asString().isBlank()){ + key = Optional.of(Key.key(node.asString())); amount = Optional.of(1); } else { return null; diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java index ad52781..94c8895 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java @@ -1,18 +1,16 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import net.kyori.adventure.key.Key; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; public class KeyDeserializer extends StdDeserializer { public KeyDeserializer() { super(Key.class); } @Override - public Key deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Key deserialize(JsonParser p, DeserializationContext ctxt) { return Key.key(p.getValueAsString()); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java index f1091dc..5bde704 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java @@ -1,11 +1,10 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.Material; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class MaterialDeserializer extends StdDeserializer { @@ -14,7 +13,7 @@ public MaterialDeserializer() { } @Override - public Material deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public Material deserialize(JsonParser p, DeserializationContext ctxt) { return Optional.ofNullable(p.getValueAsString()) .map(Material::matchMaterial) .orElse(Material.STONE); diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java index d5ba363..3fcce69 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java @@ -1,12 +1,10 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.bukkit.NamespacedKey; import org.jetbrains.annotations.Nullable; - -import java.io.IOException; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; public class NamespacedKeyDeserializer extends StdDeserializer { public NamespacedKeyDeserializer() { @@ -14,7 +12,7 @@ public NamespacedKeyDeserializer() { } @Override - public @Nullable NamespacedKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + public @Nullable NamespacedKey deserialize(JsonParser p, DeserializationContext ctxt) { return NamespacedKey.fromString(p.getValueAsString()); } } diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java index 5ecdde8..678612e 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java @@ -1,12 +1,11 @@ package re.sylfa.itemcreator.deserializers.types; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; import java.util.Optional; public class TextComponentDeserializer extends StdDeserializer { @@ -17,7 +16,7 @@ public TextComponentDeserializer() { } @Override - public Component deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{ + public Component deserialize(JsonParser p, DeserializationContext ctxt){ return Optional.ofNullable(p.getValueAsString()) .map(message -> miniMessage.deserialize(""+message+"")) diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 3dfd63d..fca7e43 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -1,14 +1,13 @@ package re.sylfa.itemcreator.items; -import com.fasterxml.jackson.databind.ObjectMapper; import net.kyori.adventure.key.Key; import org.apache.commons.io.FilenameUtils; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; +import tools.jackson.databind.ObjectMapper; import java.io.File; -import java.io.IOException; import java.util.List; import java.util.Objects; @@ -68,11 +67,7 @@ public static CustomItem readItem(File file) { } Key key = Key.key(parentName, fileName); - try { - CustomItem customItem = mapper.readValue(file, CustomItem.class); - return customItem.withKey(key); - } catch (IOException e) { - throw new RuntimeException(e); - } + CustomItem customItem = mapper.readValue(file, CustomItem.class); + return customItem.withKey(key); } } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java index 071a6cb..50c6ef3 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Parsers.java +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -1,6 +1,5 @@ package re.sylfa.itemcreator.util; -import com.fasterxml.jackson.databind.JsonNode; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.bukkit.Color; @@ -12,6 +11,7 @@ import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; import re.sylfa.itemcreator.items.ItemKey; +import tools.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.List; @@ -38,7 +38,7 @@ static Optional getFloatNodeValue(@NonNull JsonNode node, @NonNull String } static Optional getStringNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, JsonNode::isTextual, String.class); + return getNodeValue(node, valueName, JsonNode::isString, String.class); } static Optional getBooleanNodeValue(@NonNull JsonNode node, @NonNull String valueName) { @@ -46,7 +46,7 @@ static Optional getBooleanNodeValue(@NonNull JsonNode node, @NonNull St } static Optional getMaterialNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, JsonNode::isTextual, Material.class); + return getNodeValue(node, valueName, JsonNode::isString, Material.class); } static Optional getComponentArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { @@ -88,23 +88,23 @@ static Optional getIntNodeValue(@NonNull JsonNode node, @NonNull String } static Optional getComponentNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, JsonNode::isTextual, Component.class); + return getNodeValue(node, valueName, JsonNode::isString, Component.class); } static Optional getNamespacedKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, JsonNode::isTextual, NamespacedKey.class); + return getNodeValue(node, valueName, JsonNode::isString, NamespacedKey.class); } static Optional getKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, JsonNode::isTextual, Key.class); + return getNodeValue(node, valueName, JsonNode::isString, Key.class); } static Optional getItemStackNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemStack.class); + return getNodeValue(node, valueName, jsonNode -> jsonNode.isString() || jsonNode.isObject(), ItemStack.class); } static Optional getItemKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, jsonNode -> jsonNode.isTextual() || jsonNode.isObject(), ItemKey.class); + return getNodeValue(node, valueName, jsonNode -> jsonNode.isString() || jsonNode.isObject(), ItemKey.class); } static Optional getPotionEffectNodeValue(@NonNull JsonNode node, @NonNull String valueName) { @@ -116,6 +116,6 @@ static Optional getColorNodeValue(@NonNull JsonNode node, @NonNull String } static Optional getUUIDNodeValue(@NonNull JsonNode node, @NonNull String valueName) { - return getNodeValue(node, valueName, n -> n.isTextual() && !n.isEmpty(), UUID.class); + return getNodeValue(node, valueName, n -> n.isString() && !n.isEmpty(), UUID.class); } } From 88198f202c63c8e22fd4bbcdf871adc995829559 Mon Sep 17 00:00:00 2001 From: Sylfare <12037446+Sylfare@users.noreply.github.com> Date: Sun, 17 May 2026 18:04:15 +0200 Subject: [PATCH 31/31] refactor: cleaning unused code --- .../components/AttributeModifiersDeserializer.java | 1 - .../itemcreator/deserializers/types/ColorDeserializer.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java index 8b70a05..e3b2b53 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java @@ -87,7 +87,6 @@ public AttributeModification deserialize(JsonParser p, DeserializationContext ct EquipmentSlotGroup slot = Optional.ofNullable(node.get("slot")) .map(JsonNode::asString) .map(EquipmentSlotGroup::getByName) - .filter(Objects::nonNull) .orElse(EquipmentSlotGroup.ANY); return new AttributeModification( diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java index de6bfa0..034ed09 100644 --- a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -1,7 +1,6 @@ package re.sylfa.itemcreator.deserializers.types; import org.bukkit.Color; -import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; import re.sylfa.itemcreator.util.Parsers; import tools.jackson.core.JsonParser; @@ -11,7 +10,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; public class ColorDeserializer extends StdDeserializer { @@ -96,7 +94,6 @@ public Color deserialize(JsonParser p, DeserializationContext ctxt) { // as an object {"red": 255, "blue": 120, "green": 41, "alpha": 255} if(node.isObject()) { - Map map = ItemCreator.getMapper().convertValue(node, Map.class); Optional red = Parsers.getIntNodeValue(node, "red"); Optional green = Parsers.getIntNodeValue(node, "green"); Optional blue = Parsers.getIntNodeValue(node, "blue");