From d15399b870d402d7088e7fefd576306dfa194ca5 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 30 Jul 2024 18:41:21 +0200 Subject: [PATCH 001/182] Add paperweight userdev plugin and paperDevBundle dependency --- plugin/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 3dd718f0..8a7b60ad 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -4,6 +4,7 @@ import net.minecrell.pluginyml.bukkit.BukkitPluginDescription plugins { id("java") id("io.papermc.hangar-publish-plugin") version "0.1.2" + id("io.papermc.paperweight.userdev") version "1.7.1" id("net.minecrell.plugin-yml.paper") version "0.6.0" id("io.github.goooler.shadow") version "8.1.7" id("com.modrinth.minotaur") version "2.+" @@ -25,9 +26,10 @@ repositories { } dependencies { + paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") + compileOnly("org.projectlombok:lombok:1.18.32") compileOnly("net.thenextlvl.core:annotations:2.0.1") - compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") implementation("org.bstats:bstats-bukkit:3.0.2") implementation("org.incendo:cloud-paper:2.0.0-beta.9") From ef775944a5c3f5af4bc84aac8d8fae72566183f9 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 30 Jul 2024 18:53:52 +0200 Subject: [PATCH 002/182] Upgrade Gradle to 8.8 --- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 31 +++++++++++++---------- gradlew.bat | 20 +++++++-------- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 zcma&N1CXTcmMvW9vTb(Rwr$&4wr$(C?dmSu>@vG-+vuvg^_??!{yS%8zW-#zn-LkA z5&1^$^{lnmUON?}LBF8_K|(?T0Ra(xUH{($5eN!MR#ZihR#HxkUPe+_R8Cn`RRs(P z_^*#_XlXmGv7!4;*Y%p4nw?{bNp@UZHv1?Um8r6)Fei3p@ClJn0ECfg1hkeuUU@Or zDaPa;U3fE=3L}DooL;8f;P0ipPt0Z~9P0)lbStMS)ag54=uL9ia-Lm3nh|@(Y?B`; zx_#arJIpXH!U{fbCbI^17}6Ri*H<>OLR%c|^mh8+)*h~K8Z!9)DPf zR2h?lbDZQ`p9P;&DQ4F0sur@TMa!Y}S8irn(%d-gi0*WxxCSk*A?3lGh=gcYN?FGl z7D=Js!i~0=u3rox^eO3i@$0=n{K1lPNU zwmfjRVmLOCRfe=seV&P*1Iq=^i`502keY8Uy-WNPwVNNtJFx?IwAyRPZo2Wo1+S(xF37LJZ~%i)kpFQ3Fw=mXfd@>%+)RpYQLnr}B~~zoof(JVm^^&f zxKV^+3D3$A1G;qh4gPVjhrC8e(VYUHv#dy^)(RoUFM?o%W-EHxufuWf(l*@-l+7vt z=l`qmR56K~F|v<^Pd*p~1_y^P0P^aPC##d8+HqX4IR1gu+7w#~TBFphJxF)T$2WEa zxa?H&6=Qe7d(#tha?_1uQys2KtHQ{)Qco)qwGjrdNL7thd^G5i8Os)CHqc>iOidS} z%nFEDdm=GXBw=yXe1W-ShHHFb?Cc70+$W~z_+}nAoHFYI1MV1wZegw*0y^tC*s%3h zhD3tN8b=Gv&rj}!SUM6|ajSPp*58KR7MPpI{oAJCtY~JECm)*m_x>AZEu>DFgUcby z1Qaw8lU4jZpQ_$;*7RME+gq1KySGG#Wql>aL~k9tLrSO()LWn*q&YxHEuzmwd1?aAtI zBJ>P=&$=l1efe1CDU;`Fd+_;&wI07?V0aAIgc(!{a z0Jg6Y=inXc3^n!U0Atk`iCFIQooHqcWhO(qrieUOW8X(x?(RD}iYDLMjSwffH2~tB z)oDgNBLB^AJBM1M^c5HdRx6fBfka`(LD-qrlh5jqH~);#nw|iyp)()xVYak3;Ybik z0j`(+69aK*B>)e_p%=wu8XC&9e{AO4c~O1U`5X9}?0mrd*m$_EUek{R?DNSh(=br# z#Q61gBzEpmy`$pA*6!87 zSDD+=@fTY7<4A?GLqpA?Pb2z$pbCc4B4zL{BeZ?F-8`s$?>*lXXtn*NC61>|*w7J* z$?!iB{6R-0=KFmyp1nnEmLsA-H0a6l+1uaH^g%c(p{iT&YFrbQ$&PRb8Up#X3@Zsk zD^^&LK~111%cqlP%!_gFNa^dTYT?rhkGl}5=fL{a`UViaXWI$k-UcHJwmaH1s=S$4 z%4)PdWJX;hh5UoK?6aWoyLxX&NhNRqKam7tcOkLh{%j3K^4Mgx1@i|Pi&}<^5>hs5 zm8?uOS>%)NzT(%PjVPGa?X%`N2TQCKbeH2l;cTnHiHppPSJ<7y-yEIiC!P*ikl&!B z%+?>VttCOQM@ShFguHVjxX^?mHX^hSaO_;pnyh^v9EumqSZTi+#f&_Vaija0Q-e*| z7ulQj6Fs*bbmsWp{`auM04gGwsYYdNNZcg|ph0OgD>7O}Asn7^Z=eI>`$2*v78;sj-}oMoEj&@)9+ycEOo92xSyY344^ z11Hb8^kdOvbf^GNAK++bYioknrpdN>+u8R?JxG=!2Kd9r=YWCOJYXYuM0cOq^FhEd zBg2puKy__7VT3-r*dG4c62Wgxi52EMCQ`bKgf*#*ou(D4-ZN$+mg&7$u!! z-^+Z%;-3IDwqZ|K=ah85OLwkO zKxNBh+4QHh)u9D?MFtpbl)us}9+V!D%w9jfAMYEb>%$A;u)rrI zuBudh;5PN}_6J_}l55P3l_)&RMlH{m!)ai-i$g)&*M`eN$XQMw{v^r@-125^RRCF0 z^2>|DxhQw(mtNEI2Kj(;KblC7x=JlK$@78`O~>V!`|1Lm-^JR$-5pUANAnb(5}B}JGjBsliK4& zk6y(;$e&h)lh2)L=bvZKbvh@>vLlreBdH8No2>$#%_Wp1U0N7Ank!6$dFSi#xzh|( zRi{Uw%-4W!{IXZ)fWx@XX6;&(m_F%c6~X8hx=BN1&q}*( zoaNjWabE{oUPb!Bt$eyd#$5j9rItB-h*5JiNi(v^e|XKAj*8(k<5-2$&ZBR5fF|JA z9&m4fbzNQnAU}r8ab>fFV%J0z5awe#UZ|bz?Ur)U9bCIKWEzi2%A+5CLqh?}K4JHi z4vtM;+uPsVz{Lfr;78W78gC;z*yTch~4YkLr&m-7%-xc ztw6Mh2d>_iO*$Rd8(-Cr1_V8EO1f*^@wRoSozS) zy1UoC@pruAaC8Z_7~_w4Q6n*&B0AjOmMWa;sIav&gu z|J5&|{=a@vR!~k-OjKEgPFCzcJ>#A1uL&7xTDn;{XBdeM}V=l3B8fE1--DHjSaxoSjNKEM9|U9#m2<3>n{Iuo`r3UZp;>GkT2YBNAh|b z^jTq-hJp(ebZh#Lk8hVBP%qXwv-@vbvoREX$TqRGTgEi$%_F9tZES@z8Bx}$#5eeG zk^UsLBH{bc2VBW)*EdS({yw=?qmevwi?BL6*=12k9zM5gJv1>y#ML4!)iiPzVaH9% zgSImetD@dam~e>{LvVh!phhzpW+iFvWpGT#CVE5TQ40n%F|p(sP5mXxna+Ev7PDwA zamaV4m*^~*xV+&p;W749xhb_X=$|LD;FHuB&JL5?*Y2-oIT(wYY2;73<^#46S~Gx| z^cez%V7x$81}UWqS13Gz80379Rj;6~WdiXWOSsdmzY39L;Hg3MH43o*y8ibNBBH`(av4|u;YPq%{R;IuYow<+GEsf@R?=@tT@!}?#>zIIn0CoyV!hq3mw zHj>OOjfJM3F{RG#6ujzo?y32m^tgSXf@v=J$ELdJ+=5j|=F-~hP$G&}tDZsZE?5rX ztGj`!S>)CFmdkccxM9eGIcGnS2AfK#gXwj%esuIBNJQP1WV~b~+D7PJTmWGTSDrR` zEAu4B8l>NPuhsk5a`rReSya2nfV1EK01+G!x8aBdTs3Io$u5!6n6KX%uv@DxAp3F@{4UYg4SWJtQ-W~0MDb|j-$lwVn znAm*Pl!?Ps&3wO=R115RWKb*JKoexo*)uhhHBncEDMSVa_PyA>k{Zm2(wMQ(5NM3# z)jkza|GoWEQo4^s*wE(gHz?Xsg4`}HUAcs42cM1-qq_=+=!Gk^y710j=66(cSWqUe zklbm8+zB_syQv5A2rj!Vbw8;|$@C!vfNmNV!yJIWDQ>{+2x zKjuFX`~~HKG~^6h5FntRpnnHt=D&rq0>IJ9#F0eM)Y-)GpRjiN7gkA8wvnG#K=q{q z9dBn8_~wm4J<3J_vl|9H{7q6u2A!cW{bp#r*-f{gOV^e=8S{nc1DxMHFwuM$;aVI^ zz6A*}m8N-&x8;aunp1w7_vtB*pa+OYBw=TMc6QK=mbA-|Cf* zvyh8D4LRJImooUaSb7t*fVfih<97Gf@VE0|z>NcBwBQze);Rh!k3K_sfunToZY;f2 z^HmC4KjHRVg+eKYj;PRN^|E0>Gj_zagfRbrki68I^#~6-HaHg3BUW%+clM1xQEdPYt_g<2K+z!$>*$9nQ>; zf9Bei{?zY^-e{q_*|W#2rJG`2fy@{%6u0i_VEWTq$*(ZN37|8lFFFt)nCG({r!q#9 z5VK_kkSJ3?zOH)OezMT{!YkCuSSn!K#-Rhl$uUM(bq*jY? zi1xbMVthJ`E>d>(f3)~fozjg^@eheMF6<)I`oeJYx4*+M&%c9VArn(OM-wp%M<-`x z7sLP1&3^%Nld9Dhm@$3f2}87!quhI@nwd@3~fZl_3LYW-B?Ia>ui`ELg z&Qfe!7m6ze=mZ`Ia9$z|ARSw|IdMpooY4YiPN8K z4B(ts3p%2i(Td=tgEHX z0UQ_>URBtG+-?0E;E7Ld^dyZ;jjw0}XZ(}-QzC6+NN=40oDb2^v!L1g9xRvE#@IBR zO!b-2N7wVfLV;mhEaXQ9XAU+>=XVA6f&T4Z-@AX!leJ8obP^P^wP0aICND?~w&NykJ#54x3_@r7IDMdRNy4Hh;h*!u(Ol(#0bJdwEo$5437-UBjQ+j=Ic>Q2z` zJNDf0yO6@mr6y1#n3)s(W|$iE_i8r@Gd@!DWDqZ7J&~gAm1#~maIGJ1sls^gxL9LLG_NhU!pTGty!TbhzQnu)I*S^54U6Yu%ZeCg`R>Q zhBv$n5j0v%O_j{QYWG!R9W?5_b&67KB$t}&e2LdMvd(PxN6Ir!H4>PNlerpBL>Zvyy!yw z-SOo8caEpDt(}|gKPBd$qND5#a5nju^O>V&;f890?yEOfkSG^HQVmEbM3Ugzu+UtH zC(INPDdraBN?P%kE;*Ae%Wto&sgw(crfZ#Qy(<4nk;S|hD3j{IQRI6Yq|f^basLY; z-HB&Je%Gg}Jt@={_C{L$!RM;$$|iD6vu#3w?v?*;&()uB|I-XqEKqZPS!reW9JkLewLb!70T7n`i!gNtb1%vN- zySZj{8-1>6E%H&=V}LM#xmt`J3XQoaD|@XygXjdZ1+P77-=;=eYpoEQ01B@L*a(uW zrZeZz?HJsw_4g0vhUgkg@VF8<-X$B8pOqCuWAl28uB|@r`19DTUQQsb^pfqB6QtiT z*`_UZ`fT}vtUY#%sq2{rchyfu*pCg;uec2$-$N_xgjZcoumE5vSI{+s@iLWoz^Mf; zuI8kDP{!XY6OP~q5}%1&L}CtfH^N<3o4L@J@zg1-mt{9L`s^z$Vgb|mr{@WiwAqKg zp#t-lhrU>F8o0s1q_9y`gQNf~Vb!F%70f}$>i7o4ho$`uciNf=xgJ>&!gSt0g;M>*x4-`U)ysFW&Vs^Vk6m%?iuWU+o&m(2Jm26Y(3%TL; zA7T)BP{WS!&xmxNw%J=$MPfn(9*^*TV;$JwRy8Zl*yUZi8jWYF>==j~&S|Xinsb%c z2?B+kpet*muEW7@AzjBA^wAJBY8i|#C{WtO_or&Nj2{=6JTTX05}|H>N2B|Wf!*3_ z7hW*j6p3TvpghEc6-wufFiY!%-GvOx*bZrhZu+7?iSrZL5q9}igiF^*R3%DE4aCHZ zqu>xS8LkW+Auv%z-<1Xs92u23R$nk@Pk}MU5!gT|c7vGlEA%G^2th&Q*zfg%-D^=f z&J_}jskj|Q;73NP4<4k*Y%pXPU2Thoqr+5uH1yEYM|VtBPW6lXaetokD0u z9qVek6Q&wk)tFbQ8(^HGf3Wp16gKmr>G;#G(HRBx?F`9AIRboK+;OfHaLJ(P>IP0w zyTbTkx_THEOs%Q&aPrxbZrJlio+hCC_HK<4%f3ZoSAyG7Dn`=X=&h@m*|UYO-4Hq0 z-Bq&+Ie!S##4A6OGoC~>ZW`Y5J)*ouaFl_e9GA*VSL!O_@xGiBw!AF}1{tB)z(w%c zS1Hmrb9OC8>0a_$BzeiN?rkPLc9%&;1CZW*4}CDDNr2gcl_3z+WC15&H1Zc2{o~i) z)LLW=WQ{?ricmC`G1GfJ0Yp4Dy~Ba;j6ZV4r{8xRs`13{dD!xXmr^Aga|C=iSmor% z8hi|pTXH)5Yf&v~exp3o+sY4B^^b*eYkkCYl*T{*=-0HniSA_1F53eCb{x~1k3*`W zr~};p1A`k{1DV9=UPnLDgz{aJH=-LQo<5%+Em!DNN252xwIf*wF_zS^!(XSm(9eoj z=*dXG&n0>)_)N5oc6v!>-bd(2ragD8O=M|wGW z!xJQS<)u70m&6OmrF0WSsr@I%T*c#Qo#Ha4d3COcX+9}hM5!7JIGF>7<~C(Ear^Sn zm^ZFkV6~Ula6+8S?oOROOA6$C&q&dp`>oR-2Ym3(HT@O7Sd5c~+kjrmM)YmgPH*tL zX+znN>`tv;5eOfX?h{AuX^LK~V#gPCu=)Tigtq9&?7Xh$qN|%A$?V*v=&-2F$zTUv z`C#WyIrChS5|Kgm_GeudCFf;)!WH7FI60j^0o#65o6`w*S7R@)88n$1nrgU(oU0M9 zx+EuMkC>(4j1;m6NoGqEkpJYJ?vc|B zOlwT3t&UgL!pX_P*6g36`ZXQ; z9~Cv}ANFnJGp(;ZhS(@FT;3e)0)Kp;h^x;$*xZn*k0U6-&FwI=uOGaODdrsp-!K$Ac32^c{+FhI-HkYd5v=`PGsg%6I`4d9Jy)uW0y%) zm&j^9WBAp*P8#kGJUhB!L?a%h$hJgQrx!6KCB_TRo%9{t0J7KW8!o1B!NC)VGLM5! zpZy5Jc{`r{1e(jd%jsG7k%I+m#CGS*BPA65ZVW~fLYw0dA-H_}O zrkGFL&P1PG9p2(%QiEWm6x;U-U&I#;Em$nx-_I^wtgw3xUPVVu zqSuKnx&dIT-XT+T10p;yjo1Y)z(x1fb8Dzfn8e yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20db9ad5..a4413138 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d42..b740cf13 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# 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 # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +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, +# 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. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..25da30db 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From e650e44b18dac80ca94403c8f199a76a81f12022 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 30 Jul 2024 18:54:05 +0200 Subject: [PATCH 003/182] Update Java version configuration to use toolchain options --- plugin/build.gradle.kts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 8a7b60ad..28653bee 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -11,8 +11,11 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + toolchain.languageVersion = JavaLanguageVersion.of(21) +} + +tasks.compileJava { + options.release.set(21) } group = project(":api").group From d5c9715b23740356e6c7331b0b6e2ebea5c41f75 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:53:22 +0200 Subject: [PATCH 004/182] Remove unused cloud dependencies from build script --- plugin/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 28653bee..1349869f 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -35,8 +35,6 @@ dependencies { compileOnly("net.thenextlvl.core:annotations:2.0.1") implementation("org.bstats:bstats-bukkit:3.0.2") - implementation("org.incendo:cloud-paper:2.0.0-beta.9") - implementation("org.incendo:cloud-minecraft-extras:2.0.0-beta.9") implementation(project(":api")) implementation("net.thenextlvl.core:nbt:1.4.2") From a67089be92256bf0d4faf46c43f1c790443d5bde Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:53:27 +0200 Subject: [PATCH 005/182] Update main class path in build.gradle.kts --- plugin/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 1349869f..fc085afd 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -55,7 +55,7 @@ tasks.shadowJar { paper { name = "Worlds" - main = "net.thenextlvl.worlds.Worlds" + main = "net.thenextlvl.worlds.WorldsPlugin" apiVersion = "1.20" description = "Create, delete and manage your worlds" load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD From 5256a65faa1ec6109446fff75041c1517df2fdf0 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:54:36 +0200 Subject: [PATCH 006/182] Rename Worlds.java to WorldsPlugin.java to improve clarity --- .../net/thenextlvl/worlds/{Worlds.java => WorldsPlugin.java} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename plugin/src/main/java/net/thenextlvl/worlds/{Worlds.java => WorldsPlugin.java} (97%) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/Worlds.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java similarity index 97% rename from plugin/src/main/java/net/thenextlvl/worlds/Worlds.java rename to plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index e451733b..b36e5ffe 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/Worlds.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -30,8 +30,7 @@ @Accessors(fluent = true) @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault -public class Worlds extends JavaPlugin { - private final CraftImageProvider imageProvider = new CraftImageProvider(this); +public class WorldsPlugin extends JavaPlugin { private final CraftLinkRegistry linkRegistry = new CraftLinkRegistry(this); private final File presetsFolder = new File(getDataFolder(), "presets"); From 14e99cfc665e64d64693eece36b958c68ec2ecd8 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:56:42 +0200 Subject: [PATCH 007/182] Add LevelView and load different world levels on enable --- .../net/thenextlvl/worlds/WorldsPlugin.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index b36e5ffe..7a859d56 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -9,13 +9,12 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.thenextlvl.worlds.command.WorldCommand; -import net.thenextlvl.worlds.image.CraftImageProvider; -import net.thenextlvl.worlds.image.WorldImage; import net.thenextlvl.worlds.link.CraftLinkRegistry; import net.thenextlvl.worlds.link.LinkRegistry; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.WorldListener; import net.thenextlvl.worlds.preset.Presets; +import net.thenextlvl.worlds.view.LevelView; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -24,7 +23,6 @@ import java.io.File; import java.util.Locale; -import java.util.Objects; @Getter @Accessors(fluent = true) @@ -33,6 +31,8 @@ public class WorldsPlugin extends JavaPlugin { private final CraftLinkRegistry linkRegistry = new CraftLinkRegistry(this); + private final LevelView levelView = new LevelView(this); + private final File presetsFolder = new File(getDataFolder(), "presets"); private final File translations = new File(getDataFolder(), "translations"); @@ -43,22 +43,28 @@ public class WorldsPlugin extends JavaPlugin { .miniMessage(bundle -> MiniMessage.builder().tags(TagResolver.resolver( TagResolver.standard(), Placeholder.component("prefix", bundle.component(Locale.US, "prefix")) - )).build());; + )).build()); private final Metrics metrics = new Metrics(this, 19652); @Override public void onLoad() { Bukkit.getServicesManager().register(LinkRegistry.class, linkRegistry(), this, ServicePriority.Highest); - - saveDefaultPresets(); + if (presetsFolder().list() == null) saveDefaultPresets(); } @Override public void onEnable() { - imageProvider().findImages().stream() - .filter(WorldImage::loadOnStart) - .forEach(imageProvider()::load); + levelView().listOverworldLevels() + .filter(levelView()::canLoad) + .forEach(levelView()::loadOverworldLevel); + levelView().listNetherLevels() + .filter(levelView()::canLoad) + .forEach(levelView()::loadNetherLevel); + levelView().listEndLevels() + .filter(levelView()::canLoad) + .forEach(levelView()::loadEndLevel); + linkRegistry().loadLinks(); registerListeners(); registerCommands(); From 29647b0166c751138b5d4e0353a3cc44ca19f400 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:57:33 +0200 Subject: [PATCH 008/182] Add new LevelView class for managing game level operations --- .../net/thenextlvl/worlds/view/LevelView.java | 159 ++++++++++++++++++ .../thenextlvl/worlds/view/package-info.java | 10 ++ 2 files changed, 169 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/view/package-info.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java new file mode 100644 index 00000000..41539120 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -0,0 +1,159 @@ +package net.thenextlvl.worlds.view; + +import core.io.IO; +import core.nbt.file.NBTFile; +import core.nbt.tag.CompoundTag; +import core.nbt.tag.StringTag; +import core.nbt.tag.Tag; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.model.LevelExtras; +import net.thenextlvl.worlds.preset.Biome; +import net.thenextlvl.worlds.preset.Layer; +import net.thenextlvl.worlds.preset.Preset; +import net.thenextlvl.worlds.preset.Structure; +import org.bukkit.*; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class LevelView { + private final WorldsPlugin plugin; + + public Stream listLevels() { + return Optional.ofNullable(plugin.getServer().getWorldContainer() + .listFiles(File::isDirectory)).stream() + .flatMap(files -> Arrays.stream(files).filter(level -> + new File(level, "level.dat").isFile())); + } + + public Stream listOverworldLevels() { + return listLevels() + .filter(level -> !new File(level, "DIM1").exists()) + .filter(level -> !new File(level, "DIM-1").exists()); + } + + public boolean canLoad(File level) { + return Bukkit.getWorld(level.getName()) == null; + } + + public Stream listNetherLevels() { + return listLevels().filter(level -> new File(level, "DIM-1").exists()); + } + + public Stream listEndLevels() { + return listLevels().filter(level -> new File(level, "DIM1").exists()); + } + + public @Nullable World loadOverworldLevel(File level) { + return loadLevel(level, World.Environment.NORMAL); + } + + public @Nullable World loadNetherLevel(File level) { + return loadLevel(level, World.Environment.NETHER); + } + + public @Nullable World loadEndLevel(File level) { + return loadLevel(level, World.Environment.THE_END); + } + + private @Nullable World loadLevel(File level, World.Environment environment) { + var root = new NBTFile<>(IO.of(level, "level.dat"), new CompoundTag()).getRoot(); + + var data = root.getAsCompound("Data"); + var settings = data.getAsCompound("WorldGenSettings"); + var dimensions = settings.getAsCompound("dimensions"); + + var dimension = dimensions.optional(switch (environment) { + case NORMAL -> "minecraft:overworld"; + case NETHER -> "minecraft:the_nether"; + case THE_END -> "minecraft:the_end"; + case CUSTOM -> throw new UnsupportedOperationException("Custom dimensions are not yet supported"); + }).orElseThrow().getAsCompound(); + + var generator = dimension.getAsCompound("generator"); + + var type = generator.optional("type") + .map(Tag::getAsString) + .map(string -> switch (string) { + case "minecraft:noise" -> WorldType.NORMAL; + case "minecraft:flat" -> WorldType.FLAT; + case "minecraft:debug" -> throw new IllegalArgumentException("Debug worlds are not yet supported"); + default -> throw new IllegalArgumentException("Unexpected generator type: " + string); + }).orElseThrow(() -> new NoSuchElementException("type")); + + var generatorSettings = type.equals(WorldType.FLAT) ? readSettings(generator) : null; + + var hardcore = data.optional("hardcore") + .orElseThrow(() -> new NoSuchElementException("hardcore")) + .getAsBoolean(); + var seed = settings.optional("seed") + .orElseThrow(() -> new NoSuchElementException("seed")) + .getAsInt(); + var structures = settings.optional("generate_features") + .orElseThrow(() -> new NoSuchElementException("generate_features")) + .getAsBoolean(); + + var extras = readExtras(root); + + var namespace = level.getName().toLowerCase().replace(" ", "_"); + var creator = new WorldCreator(level.getName(), new NamespacedKey("worlds", namespace)) + .environment(environment) + .generateStructures(structures) + .hardcore(hardcore) + .seed(seed) + .type(type); + + if (generatorSettings != null) creator.generatorSettings( + Preset.serialize(generatorSettings).toString() + ); + + return creator.createWorld(); + } + + private LevelExtras readExtras(CompoundTag root) { + return null; + } + + private Preset readSettings(CompoundTag generator) { + var settings = generator.getAsCompound("settings"); + + var biome = settings.optional("biome") + .orElseThrow(() -> new NoSuchElementException("biome")) + .getAsString(); + var features = settings.optional("features") + .orElseThrow(() -> new NoSuchElementException("features")) + .getAsBoolean(); + var lakes = settings.optional("lakes") + .orElseThrow(() -> new NoSuchElementException("lakes")) + .getAsBoolean(); + + var layers = settings.optional("layers") + .orElseThrow(() -> new NoSuchElementException("layers")) + .getAsList().stream() + .map(layer -> { + var block = layer.optional("block").orElseThrow().getAsString(); + var height = layer.optional("height").orElseThrow().getAsInt(); + return new Layer(block, height); + }).collect(Collectors.toSet()); + + var structures = settings.optional("structure_overrides") + .orElseThrow(() -> new NoSuchElementException("structure_overrides")) + .getAsList().stream() + .map(structure -> new Structure(structure.getAsString())) + .collect(Collectors.toSet()); + + return new Preset() + .biome(Biome.literal(biome)) + .features(features) + .lakes(lakes) + .layers(layers) + .structures(structures); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/view/package-info.java new file mode 100644 index 00000000..a1c0fbb6 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.view; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From cc76e8e01e6b9c2b4dbf397d2a168e6dca9da330 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:58:26 +0200 Subject: [PATCH 009/182] Remove image-related classes and interfaces --- .../thenextlvl/worlds/image/DeletionType.java | 9 - .../net/thenextlvl/worlds/image/Image.java | 44 ---- .../worlds/image/ImageProvider.java | 38 ---- .../thenextlvl/worlds/image/WorldImage.java | 209 ------------------ .../worlds/image/CraftImageProvider.java | 106 --------- .../worlds/image/CraftWorldImage.java | 95 -------- .../thenextlvl/worlds/util/WorldReader.java | 53 ----- 7 files changed, 554 deletions(-) delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/DeletionType.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/Image.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/ImageProvider.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/WorldImage.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/CraftImageProvider.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/CraftWorldImage.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/util/WorldReader.java diff --git a/api/src/main/java/net/thenextlvl/worlds/image/DeletionType.java b/api/src/main/java/net/thenextlvl/worlds/image/DeletionType.java deleted file mode 100644 index 7a2aeb20..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/DeletionType.java +++ /dev/null @@ -1,9 +0,0 @@ -package net.thenextlvl.worlds.image; - -public enum DeletionType { - WORLD, WORLD_AND_IMAGE; - - public boolean keepImage() { - return equals(WORLD); - } -} diff --git a/api/src/main/java/net/thenextlvl/worlds/image/Image.java b/api/src/main/java/net/thenextlvl/worlds/image/Image.java deleted file mode 100644 index 7d559b3c..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/Image.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.thenextlvl.worlds.image; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.bukkit.World; - -public interface Image { - - Image save(); - - World getWorld(); - - @Deprecated - WorldImage getWorldImage(); - - boolean unload(); - - boolean canUnload(); - - boolean canDelete(); - - DeleteResult delete(boolean keepImage, boolean keepWorld, boolean schedule); - - DeleteResult deleteNow(boolean keepImage, boolean keepWorld); - - DeleteResult scheduleDeletion(boolean keepImage, boolean keepWorld); - - @Getter - @RequiredArgsConstructor - enum DeleteResult { - WORLD_DELETE_SCHEDULED("world.delete.scheduled"), - WORLD_DELETE_ILLEGAL("world.delete.disallowed"), - WORLD_DELETE_NOTHING("world.delete.nothing"), - WORLD_DELETE_FAILED("world.delete.failed"), - WORLD_DELETED("world.delete.success"), - - IMAGE_DELETE_FAILED("image.delete.failed"), - - WORLD_UNLOAD_FAILED("world.unload.failed"), - WORLD_UNLOADED("world.unload.success"); - - private final String message; - } -} diff --git a/api/src/main/java/net/thenextlvl/worlds/image/ImageProvider.java b/api/src/main/java/net/thenextlvl/worlds/image/ImageProvider.java deleted file mode 100644 index b4cc23b1..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/ImageProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.thenextlvl.worlds.image; - -import org.bukkit.World; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.List; - -public interface ImageProvider { - - @Nullable - @Deprecated - Image load(@Nullable WorldImage image); - - @Nullable - Image get(World world); - - void register(Image image); - - Image getOrDefault(World world); - - List findImageFiles(); - - List findWorldFiles(); - - @Deprecated - List findImages(); - - @Deprecated - WorldImage createWorldImage(); - - @Deprecated - WorldImage of(World world); - - @Nullable - @Deprecated - WorldImage of(File file); -} diff --git a/api/src/main/java/net/thenextlvl/worlds/image/WorldImage.java b/api/src/main/java/net/thenextlvl/worlds/image/WorldImage.java deleted file mode 100644 index e5dc3df0..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/WorldImage.java +++ /dev/null @@ -1,209 +0,0 @@ -package net.thenextlvl.worlds.image; - -import com.google.gson.JsonObject; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.WorldType; -import org.jetbrains.annotations.Nullable; - -@Deprecated -public interface WorldImage { - /** - * Retrieves the name of the WorldImage. - * - * @return The name of the WorldImage. - */ - String name(); - - /** - * Sets the name of the WorldImage. - * - * @param name The new name for the WorldImage. - * @return The updated WorldImage instance. - */ - WorldImage name(String name); - - /** - * Retrieves the NamespacedKey associated with the WorldImage. - * - * @return The NamespacedKey associated with the WorldImage. - */ - NamespacedKey key(); - - /** - * Sets the NamespacedKey associated with the WorldImage. - * - * @param key The NamespacedKey to set. - * @return The updated WorldImage instance. - */ - WorldImage key(NamespacedKey key); - - /** - * Retrieves the settings associated with the WorldImage. - * - * @return The settings associated with the WorldImage, or null if no settings are specified. - */ - @Nullable - JsonObject settings(); - - /** - * Set the settings associated with the WorldImage. - * - * @param object The JsonObject representing the settings to set. - * @return The updated WorldImage instance. - */ - WorldImage settings(@Nullable JsonObject object); - - /** - * Retrieves the Generator associated with the WorldImage. - * - * @return The Generator associated with the WorldImage, or null if no Generator is specified. - */ - @Nullable - Generator generator(); - - /** - * Sets the world generator for the WorldImage. - * - * @param generator The Generator to set as the world generator. - * @return The updated WorldImage instance. - */ - WorldImage generator(@Nullable Generator generator); - - /** - * Retrieves the DeletionType associated with the WorldImage. - * - * @return The DeletionType associated with the WorldImage, or null if no DeletionType is specified. - */ - @Nullable - DeletionType deletionType(); - - /** - * Retrieves the DeletionType associated with the WorldImage. - * - * @param deletionType The DeletionType to associate with the WorldImage. - * @return The updated WorldImage instance. - */ - WorldImage deletionType(@Nullable DeletionType deletionType); - - - /** - * Retrieves the environment of the WorldImage. - * - * @return The environment of the WorldImage. - */ - World.Environment environment(); - - /** - * Retrieves the environment of the WorldImage. - * - * @param environment The environment to set for the WorldImage. - * @return The updated WorldImage instance. - */ - WorldImage environment(World.Environment environment); - - /** - * Retrieves the world type of the WorldImage. - * - * @return The world type of the WorldImage. - */ - WorldType worldType(); - - /** - * Sets the world type of the WorldImage. - * - * @param worldType The world type to set for the WorldImage. - * @return The updated WorldImage instance. - */ - WorldImage worldType(WorldType worldType); - - /** - * Returns whether the WorldImage has auto save enabled or not. - * - * @return true if auto save is enabled, false otherwise. - */ - boolean autoSave(); - - /** - * Enables or disables the auto save feature for the WorldImage. - * - * @param autoSave true to enable auto save, false to disable auto save. - * @return The updated WorldImage instance. - */ - WorldImage autoSave(boolean autoSave); - - /** - * Checks if structures are generated in the world. - * - * @return true if structures are generated, false otherwise. - */ - @Deprecated - boolean generateStructures(); - - /** - * Generates structures in the world based on the given flag. - * - * @param generateStructures Specifies whether structures should be generated in the world. - * Set to true to generate structures, false otherwise. - * @return The updated WorldImage instance. - */ - @Deprecated - WorldImage generateStructures(boolean generateStructures); - - /** - * Checks if the WorldImage is set to hardcore mode. - * - * @return true if the WorldImage is set to hardcore mode, false otherwise. - */ - @Deprecated - boolean hardcore(); - - /** - * Sets the hardcore mode for the WorldImage. - * - * @param hardcore true to enable hardcore mode, false to disable hardcore mode. - * @return The updated WorldImage instance. - */ - @Deprecated - WorldImage hardcore(boolean hardcore); - - /** - * Returns whether the WorldImage is set to load on start. - * - * @return true if the WorldImage is set to load on start, false otherwise. - */ - boolean loadOnStart(); - - /** - * Sets whether the WorldImage should be loaded on start. - * - * @param loadOnStart true if the WorldImage should be loaded on start, false otherwise. - * @return The updated WorldImage instance. - */ - WorldImage loadOnStart(boolean loadOnStart); - - /** - * Retrieves the seed of the WorldImage. - * - * @return The seed of the WorldImage. - */ - @Deprecated - long seed(); - - /** - * Sets the seed of the WorldImage. - * - * @param seed The seed to set for the WorldImage. - * @return The updated WorldImage instance. - */ - @Deprecated - WorldImage seed(long seed); - - /** - * Builds and returns a World object based on the configuration of the WorldImage. - * - * @return The built World object, or null if the build fails. - */ - @Nullable - World build(); -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImageProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImageProvider.java deleted file mode 100644 index a71fff9f..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImageProvider.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.thenextlvl.worlds.image; - -import com.google.gson.GsonBuilder; -import core.file.FileIO; -import core.file.format.GsonFile; -import core.io.IO; -import core.paper.adapters.key.KeyAdapter; -import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.WorldType; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.*; - -@RequiredArgsConstructor -public class CraftImageProvider implements ImageProvider { - private static final Map images = new HashMap<>(); - private final Worlds plugin; - - @Override - public @Nullable Image load(@Nullable WorldImage image) { - if (image == null || Bukkit.getWorld(image.name()) != null) return null; - var build = image.build(); - if (build == null) return null; - var saved = new CraftImage(plugin, build, image).save(); - register(saved); - return saved; - } - - @Override - public @Nullable Image get(World world) { - return images.get(world.getUID()); - } - - @Override - public void register(Image image) { - images.put(image.getWorld().getUID(), image); - } - - @Override - public Image getOrDefault(World world) { - return images.getOrDefault(world.getUID(), new CraftImage(plugin, world)); - } - - @Override - public List findImageFiles() { - var files = Bukkit.getWorldContainer().listFiles(file -> - file.isFile() && file.getName().endsWith(".image")); - return files != null ? Arrays.asList(files) : Collections.emptyList(); - } - - @Override - public List findWorldFiles() { - var files = Bukkit.getWorldContainer().listFiles(file -> - file.isDirectory() && new File(file, "level.dat").isFile()); - return files != null ? Arrays.asList(files) : Collections.emptyList(); - } - - @Override - public List findImages() { - return findImageFiles().stream() - .map(this::of) - .filter(Objects::nonNull) - .toList(); - } - - @Override - public WorldImage createWorldImage() { - return new CraftWorldImage(true); - } - - @Override - @SuppressWarnings("deprecation") - public CraftWorldImage of(World world) { - return new CraftWorldImage( - world.getName(), - world.getKey(), - null, null, null, - world.getEnvironment(), - Objects.requireNonNullElse(world.getWorldType(), WorldType.NORMAL), - world.isAutoSave(), - world.canGenerateStructures(), - world.isHardcore(), - true, - world.getSeed(), - true - ); - } - - @Override - public @Nullable WorldImage of(File file) { - return file.isFile() ? loadFile(IO.of(file), null).getRoot() : null; - } - - public static FileIO loadFile(IO file, @Nullable WorldImage root) { - var gson = new GsonBuilder() - .registerTypeHierarchyAdapter(NamespacedKey.class, KeyAdapter.Bukkit.INSTANCE) - .setPrettyPrinting() - .create(); - return root != null ? new GsonFile<>(file, root, gson) : new GsonFile<>(file, CraftWorldImage.class, gson); - } -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftWorldImage.java b/plugin/src/main/java/net/thenextlvl/worlds/image/CraftWorldImage.java deleted file mode 100644 index f1e40626..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftWorldImage.java +++ /dev/null @@ -1,95 +0,0 @@ -package net.thenextlvl.worlds.image; - -import com.google.gson.JsonObject; -import core.io.IO; -import core.nbt.tag.CompoundTag; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import net.thenextlvl.worlds.util.WorldReader; -import org.bukkit.*; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.ChunkGenerator; -import org.jetbrains.annotations.Nullable; - -import java.io.File; - -@Getter -@Setter -@AllArgsConstructor -@Accessors(fluent = true, chain = true) -public class CraftWorldImage implements WorldImage { - private String name; - private NamespacedKey key; - private @Nullable JsonObject settings; - private @Nullable Generator generator; - private @Nullable DeletionType deletionType; - private World.Environment environment; - private WorldType worldType; - private boolean autoSave; - private boolean loadOnStart; - - private volatile boolean generateStructures; - private volatile boolean hardcore; - private volatile long seed; - - private volatile boolean skipValidation; - - public CraftWorldImage(boolean skipValidation) { - this.skipValidation = skipValidation; - } - - @Override - public @Nullable World build() { - try { - preValidate(); - } catch (Exception e) { - e.printStackTrace(); - } - var creator = new WorldCreator(name(), key()) - .generator(resolveChunkGenerator()) - .biomeProvider(resolveBiomeProvider()) - .generateStructures(generateStructures()) - .generatorSettings(settings() != null ? settings().toString() : "") - .environment(environment()) - .hardcore(hardcore()) - .type(worldType()) - .seed(seed()); - var world = creator.createWorld(); - if (world != null) world.setAutoSave(autoSave()); - return world; - } - - private void preValidate() { - var worldFolder = new File(Bukkit.getWorldContainer(), name()); - var dataFile = IO.of(worldFolder, "level.dat"); - if (!dataFile.exists()) return; - var reader = new WorldReader(dataFile); - if (!skipValidation) { - reader.generateStructures().ifPresent(this::generateStructures); - reader.hardcore().ifPresent(this::hardcore); - reader.seed().ifPresent(this::seed); - } - var data = reader.file().getRoot().getOrAdd("Data", new CompoundTag()); - var worldGenSettings = data.getOrAdd("WorldGenSettings", new CompoundTag()); - worldGenSettings.add("seed", seed()); - worldGenSettings.add("generate_features", generateStructures()); - data.add("hardcore", hardcore()); - reader.file().save(); - } - - private @Nullable ChunkGenerator resolveChunkGenerator() { - if (generator() == null) return null; - var plugin = Bukkit.getPluginManager().getPlugin(generator().plugin()); - if (plugin == null || !plugin.isEnabled()) throw new IllegalArgumentException(); - return plugin.getDefaultWorldGenerator(name(), generator().id()); - } - - private @Nullable BiomeProvider resolveBiomeProvider() { - if (generator() == null) return null; - var plugin = Bukkit.getPluginManager().getPlugin(generator().plugin()); - if (plugin == null || !plugin.isEnabled()) throw new IllegalArgumentException(); - return plugin.getDefaultBiomeProvider(name(), generator().id()); - } -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/util/WorldReader.java b/plugin/src/main/java/net/thenextlvl/worlds/util/WorldReader.java deleted file mode 100644 index d7d45149..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/util/WorldReader.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.thenextlvl.worlds.util; - -import core.annotation.TypesAreNotNullByDefault; -import core.io.IO; -import core.nbt.file.NBTFile; -import core.nbt.tag.CompoundTag; -import lombok.Getter; -import lombok.experimental.Accessors; -import org.bukkit.Bukkit; - -import java.io.File; -import java.util.Optional; -import java.util.OptionalLong; - -@Getter -@Accessors(fluent = true) -@TypesAreNotNullByDefault -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public class WorldReader { - private final NBTFile file; - private OptionalLong seed = OptionalLong.empty(); - private Optional generateStructures = Optional.empty(); - private Optional hardcore = Optional.empty(); - - public WorldReader(IO dataFile) { - this.file = new NBTFile<>(dataFile, new CompoundTag()); - - if (!file.getRoot().containsKey("Data")) return; - var data = file.getRoot().getAsCompound("Data"); - - var oldSeed = data.get("RandomSeed"); - if (oldSeed != null) this.seed = OptionalLong.of(oldSeed.getAsLong()); - - var oldStructures = data.get("MapFeatures"); - if (oldStructures != null) this.seed = OptionalLong.of(oldStructures.getAsLong()); - - var hardcore = data.get("hardcore"); - if (hardcore != null) this.hardcore = Optional.of(hardcore.getAsBoolean()); - - if (!data.containsKey("WorldGenSettings")) return; - var worldGenSettings = data.getAsCompound("WorldGenSettings"); - - var seed = worldGenSettings.get("seed"); - if (seed != null) this.seed = OptionalLong.of(seed.getAsLong()); - - var structures = worldGenSettings.get("generate_features"); - if (structures != null) generateStructures = Optional.of(structures.getAsBoolean()); - } - - public WorldReader(String name) { - this(IO.of(new File(Bukkit.getWorldContainer(), name), "level.dat")); - } -} From f059dfff14524938fd15ab9fc67b38039d42b738 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 2 Aug 2024 23:58:43 +0200 Subject: [PATCH 010/182] Simplify Structure class and remove provider field --- .../thenextlvl/worlds/preset/Structure.java | 23 +++++-------------- .../preset/adapter/StructureTypeAdapter.java | 2 +- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java b/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java index 741c10c7..ee8bfc05 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java @@ -1,27 +1,16 @@ package net.thenextlvl.worlds.preset; -import com.google.common.base.Preconditions; - -public record Structure(String provider, String structure) { - - public static Structure minecraft(String structure) { - return new Structure("minecraft", structure); +public record Structure(String structure) { + Structure(org.bukkit.generator.structure.Structure structure) { + this(structure.key().asString()); } - public static Structure bukkit(org.bukkit.generator.structure.Structure structure) { - return new Structure(structure.key().namespace(), structure.key().value()); - } - - public static Structure literal(String string) { - var split = string.split(":", 2); - Preconditions.checkArgument(split.length == 2, "Not a valid structure: " + string); - Preconditions.checkArgument(!split[0].isBlank(), "Structure provider cannot be empty"); - Preconditions.checkArgument(!split[1].isBlank(), "Structure name cannot be empty"); - return new Structure(split[0], split[1]); + public static Structure minecraft(String structure) { + return new Structure("minecraft:" + structure); } @Override public String toString() { - return provider() + ":" + structure(); + return structure(); } } diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java b/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java index 4a061a38..02a2c179 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java @@ -8,7 +8,7 @@ public class StructureTypeAdapter implements JsonSerializer, JsonDeserializer { @Override public Structure deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException { - return Structure.literal(element.getAsString()); + return new Structure(element.getAsString()); } @Override From 64662a13f892efb47715b62bc8234ee6e20c80c1 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:00:11 +0200 Subject: [PATCH 011/182] Switch to Set and update Layer/Biome constructors --- api/src/main/java/net/thenextlvl/worlds/preset/Biome.java | 7 +++---- api/src/main/java/net/thenextlvl/worlds/preset/Layer.java | 6 ++++-- .../main/java/net/thenextlvl/worlds/preset/Preset.java | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java b/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java index a81d2bbc..2b6f61d5 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java @@ -3,15 +3,14 @@ import com.google.common.base.Preconditions; public record Biome(String provider, String biome) { + Biome(org.bukkit.block.Biome biome) { + this(biome.key().namespace(), biome.key().value()); + } public static Biome minecraft(String biome) { return new Biome("minecraft", biome); } - public static Biome bukkit(org.bukkit.block.Biome biome) { - return new Biome(biome.key().namespace(), biome.key().value()); - } - public static Biome literal(String string) { var split = string.split(":", 2); Preconditions.checkArgument(split.length == 2, "Not a valid biome: " + string); diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java b/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java index e5ff601b..a38a0954 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java @@ -1,7 +1,9 @@ package net.thenextlvl.worlds.preset; import org.bukkit.Material; -import org.jetbrains.annotations.Range; -public record Layer(Material block, @Range(from = 1, to = Long.MAX_VALUE) int height) { +public record Layer(String block, int height) { + Layer(Material material, int height) { + this(material.key().asString(), height); + } } diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java index 90c45dbe..0f4cd30f 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java @@ -15,8 +15,8 @@ import org.bukkit.Material; import java.io.File; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; @Getter @Setter @@ -27,9 +27,9 @@ public class Preset { private boolean features; private boolean decoration; - private final List layers = new ArrayList<>(); + private Set layers = new HashSet<>(); @SerializedName("structure_overrides") - private final List structures = new ArrayList<>(); + private Set structures = new HashSet<>(); /** * Add a layer to the preset From 5a61f8728cd4b6866909436ec1efe7c55027abc5 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:00:19 +0200 Subject: [PATCH 012/182] Remove unnecessary blank line in LinkRegistry interface --- api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java b/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java index 4c98bbb1..ccfe7ae7 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java +++ b/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java @@ -5,7 +5,6 @@ import java.util.stream.Stream; public interface LinkRegistry { - Stream getLinks(); boolean isRegistered(Link link); From cb15257b8eea826809eb4979ccfcbac956c87d4e Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:04:26 +0200 Subject: [PATCH 013/182] Simplify command registration using Paper's Brigadier API --- .../thenextlvl/worlds/image/DeleteResult.java | 17 ++ .../worlds/command/WorldCommand.java | 82 ++---- .../worlds/command/WorldCreateCommand.java | 240 +----------------- .../worlds/command/WorldDeleteCommand.java | 50 +--- .../worlds/command/WorldExportCommand.java | 44 +--- .../worlds/command/WorldImportCommand.java | 71 +----- .../worlds/command/WorldInfoCommand.java | 77 +----- .../worlds/command/WorldListCommand.java | 41 +-- .../worlds/command/WorldSetSpawnCommand.java | 64 +---- .../worlds/command/WorldTeleportCommand.java | 44 +--- 10 files changed, 143 insertions(+), 587 deletions(-) create mode 100644 api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java diff --git a/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java b/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java new file mode 100644 index 00000000..a76ffac1 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java @@ -0,0 +1,17 @@ +package net.thenextlvl.worlds.image; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public +enum DeleteResult { + EXEMPTED("world.delete.disallowed"), + SCHEDULED("world.delete.scheduled"), + FAILED("world.delete.failed"), + SUCCESS("world.delete.success"), + UNLOAD_FAILED("world.unload.failed"); + + private final String message; +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java index ebcda41f..4436833b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java @@ -1,69 +1,31 @@ package net.thenextlvl.worlds.command; -import com.google.gson.JsonParseException; -import io.papermc.paper.command.brigadier.CommandSourceStack; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.incendo.cloud.bukkit.parser.PlayerParser; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.exception.ArgumentParseException; -import org.incendo.cloud.exception.InvalidCommandSenderException; -import org.incendo.cloud.exception.InvalidSyntaxException; -import org.incendo.cloud.exception.NoPermissionException; -import org.incendo.cloud.execution.ExecutionCoordinator; -import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; -import org.incendo.cloud.paper.PaperCommandManager; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +@RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") public class WorldCommand { - private final PaperCommandManager commandManager; - private final Worlds plugin; - - public WorldCommand(Worlds plugin) { - this.commandManager = PaperCommandManager.builder() - .executionCoordinator(ExecutionCoordinator.simpleCoordinator()) - .buildOnEnable(plugin); - MinecraftExceptionHandler.create(CommandSourceStack::getSender) - .handler(InvalidSyntaxException.class, (formatter, context) -> { - var syntax = context.exception().correctSyntax() - .replace("[", "[").replace("]", "]") - .replace("(", "(").replace(")", ")") - .replace("|", "|").replace("--", "--"); - return plugin.bundle().deserialize(" /" + syntax); - }) - .handler(InvalidCommandSenderException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "command.sender")) - .handler(NoPermissionException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "command.permission", - Placeholder.parsed("permission", context.exception().missingPermission().permissionString()))) - .handler(ArgumentParseException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "command.argument")) - .handler(PlayerParser.PlayerParseException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "player.unknown", - Placeholder.parsed("player", context.exception().input()))) - .handler(WorldParser.WorldParseException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "world.unknown", - Placeholder.parsed("world", context.exception().input()))) - .handler(JsonParseException.class, (formatter, context) -> - plugin.bundle().component(context.context().sender().getSender(), "world.preset.invalid")) - .defaultCommandExecutionHandler() - .registerTo(commandManager); - commandManager.commandSyntaxFormatter(new CustomSyntaxFormatter<>(commandManager)); - this.plugin = plugin; - } + private final WorldsPlugin plugin; public void register() { - var world = commandManager.commandBuilder("world"); - commandManager.command(new WorldCreateCommand(plugin, world).create()); - commandManager.command(new WorldDeleteCommand(plugin, world).create()); - commandManager.command(new WorldExportCommand(plugin, world).create()); - commandManager.command(new WorldImportCommand(plugin, world).create()); - commandManager.command(new WorldInfoCommand(plugin, world).create()); - commandManager.command(new WorldLinkCommand.Create(plugin, world).create()); - commandManager.command(new WorldLinkCommand.Delete(plugin, world).create()); - commandManager.command(new WorldLinkCommand.List(plugin, world).create()); - commandManager.command(new WorldListCommand(plugin, world).create()); - commandManager.command(new WorldSetSpawnCommand(plugin, world).create()); - commandManager.command(new WorldTeleportCommand(plugin, world).create()); + var command = Commands.literal("world") + .then(new WorldCloneCommand(plugin).create()) + .then(new WorldCreateCommand(plugin).create()) + .then(new WorldDeleteCommand(plugin).create()) + .then(new WorldExportCommand(plugin).create()) + .then(new WorldImportCommand(plugin).create()) + .then(new WorldInfoCommand(plugin).create()) + .then(new WorldListCommand(plugin).create()) + .then(new WorldLoadCommand(plugin).create()) + .then(new WorldSetSpawnCommand(plugin).create()) + .then(new WorldSpawnCommand(plugin).create()) + .then(new WorldTeleportCommand(plugin).create()) + .then(new WorldUnloadCommand(plugin).create()) + .build(); + plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> + event.registrar().register(command))); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index 224d775a..7ed3125e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -1,236 +1,24 @@ package net.thenextlvl.worlds.command; -import com.google.gson.JsonObject; -import core.io.IO; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import net.thenextlvl.worlds.image.DeletionType; -import net.thenextlvl.worlds.image.Generator; -import net.thenextlvl.worlds.preset.Preset; -import net.thenextlvl.worlds.preset.PresetFile; -import net.thenextlvl.worlds.preset.Presets; -import net.thenextlvl.worlds.util.WorldReader; -import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.World.Environment; -import org.bukkit.WorldType; -import org.bukkit.entity.Entity; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.generator.WorldInfo; -import org.bukkit.plugin.Plugin; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.component.TypedCommandComponent; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.minecraft.extras.RichDescription; -import org.incendo.cloud.parser.flag.CommandFlag; -import org.incendo.cloud.parser.standard.BooleanParser; -import org.incendo.cloud.parser.standard.EnumParser; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldCreateCommand { - private final Worlds plugin; - private final Command.Builder builder; - - Command.Builder create() { - return builder.literal("create") - .permission("worlds.command.world.create") - .required("name", StringParser.stringParser(), - SuggestionProvider.blocking((context, input) -> - plugin.imageProvider().findWorldFiles().stream() - .map(File::getName) - .filter(s -> Bukkit.getWorld(s) == null) - .map(Suggestion::suggestion) - .toList())) - .flag(CommandFlag.builder("type").withAliases("t") - .withDescription(RichDescription.of(Component.text("The world type"))) - .withComponent(TypedCommandComponent.ofType(WorldType.class, "type") - .parser(EnumParser.enumParser(WorldType.class)))) - .flag(CommandFlag.builder("environment").withAliases("e") - .withDescription(RichDescription.of(Component.text("The environment"))) - .withComponent(TypedCommandComponent.ofType(Environment.class, "environment") - .parser(EnumParser.enumParser(Environment.class)))) - .flag(CommandFlag.builder("generator").withAliases("g") - .withDescription(RichDescription.of(Component.text("The generator plugin"))) - .withComponent(TypedCommandComponent.ofType(String.class, "generator") - .parser(StringParser.greedyFlagYieldingStringParser()) - .suggestionProvider(SuggestionProvider.blocking((context, input) -> - Arrays.stream(Bukkit.getPluginManager().getPlugins()) - .filter(plugin -> Generator.hasChunkGenerator(plugin.getClass()) - || Generator.hasBiomeProvider(plugin.getClass())) - .map(Plugin::getName) - .map(Suggestion::suggestion) - .toList())))) - .flag(CommandFlag.builder("base").withAliases("b") - .withDescription(RichDescription.of(Component.text("The world to clone"))) - .withComponent(TypedCommandComponent.ofType(World.class, "world") - .parser(WorldParser.worldParser()))) - .flag(CommandFlag.builder("preset") - .withDescription(RichDescription.of(Component.text("The preset to use"))) - .withComponent(TypedCommandComponent.ofType(String.class, "preset") - .parser(StringParser.greedyFlagYieldingStringParser()) - .suggestionProvider(SuggestionProvider.blocking((context, input) -> - PresetFile.findPresets(plugin.presetsFolder()).stream() - .map(file -> file.getName().substring(0, file.getName().length() - 5)) - .map(name -> name.contains(" ") ? "\"" + name + "\"" : name) - .map(Suggestion::suggestion) - .toList())))) - .flag(CommandFlag.builder("deletion").withAliases("d") - .withDescription(RichDescription.of(Component.text("What to do with the world on shutdown"))) - .withComponent(TypedCommandComponent.ofType(DeletionType.class, "deletion") - .parser(EnumParser.enumParser(DeletionType.class)))) - .flag(CommandFlag.builder("identifier").withAliases("i") - .withDescription(RichDescription.of(Component.text("The identifier of the world generator"))) - .withComponent(TypedCommandComponent.ofType(String.class, "identifier") - .parser(StringParser.greedyFlagYieldingStringParser()))) - .flag(CommandFlag.builder("key") - .withDescription(RichDescription.of(Component.text("The namespaced key"))) - .withComponent(TypedCommandComponent.ofType(NamespacedKey.class, "key") - .parser(NamespacedKeyParser.namespacedKeyParser(true)))) - .flag(CommandFlag.builder("seed").withAliases("s") - .withDescription(RichDescription.of(Component.text("The seed"))) - .withComponent(TypedCommandComponent.ofType(String.class, "seed") - .parser(StringParser.greedyFlagYieldingStringParser()))) - .flag(CommandFlag.builder("auto-save") - .withDescription(RichDescription.of(Component.text("Whether the world should auto-save"))) - .withComponent(TypedCommandComponent.ofType(boolean.class, "auto-save") - .parser(BooleanParser.booleanParser()))) - .flag(CommandFlag.builder("structures") - .withDescription(RichDescription.of(Component.text("Whether structures should generate"))) - .withComponent(TypedCommandComponent.ofType(boolean.class, "structures") - .parser(BooleanParser.booleanParser()))) - .flag(CommandFlag.builder("hardcore") - .withDescription(RichDescription.of(Component.text("Whether hardcore is enabled")))) - .flag(CommandFlag.builder("load-manual") - .withDescription(RichDescription.of(Component.text("Whether the world must be loaded manual on startup")))) - .handler(this::execute); - } - - @SuppressWarnings("deprecation") - private void execute(CommandContext context) { - var name = context.get("name"); - - var sender = context.sender().getSender(); - - if (Bukkit.getWorld(name) != null) { - plugin.bundle().sendMessage(sender, "world.known", Placeholder.parsed("world", name)); - return; - } - - var worldReader = new WorldReader(name); - - var environment = context.flags().getValue("environment").orElse(Environment.NORMAL); - if (environment.equals(Environment.CUSTOM)) { - plugin.bundle().sendMessage(sender, "environment.custom"); - return; - } - - var base = context.flags().getValue("base"); - var key = context.flags().getValue("key").orElse(new NamespacedKey("worlds", name.toLowerCase())); - var type = context.flags().getValue("type") - .orElse(context.flags().contains("preset") ? WorldType.FLAT : WorldType.NORMAL); - var identifier = context.flags().getValue("identifier", null); - var generator = context.flags().getValue("generator") - .map(string -> new Generator(string, identifier)).orElse(null); - var seed = context.flags().getValue("seed").map(s -> { - try { - return Long.parseLong(s); - } catch (NumberFormatException e) { - return s.hashCode(); - } - }).orElse(worldReader.seed().orElse(base.map(WorldInfo::getSeed) - .orElse(ThreadLocalRandom.current().nextLong()))) - .longValue(); - var deletion = context.flags().getValue("deletion", null); - var loadManual = context.flags().contains("load-manual"); - var structures = context.flags().getValue("structures") - .orElse(worldReader.generateStructures().orElse(base.map(World::canGenerateStructures).orElse(true))); - var autoSave = context.flags().getValue("auto-save") - .orElse(base.map(World::isAutoSave).orElse(true)); - var hardcore = context.flags().contains("hardcore") || worldReader.hardcore() - .orElse(base.map(World::isHardcore).orElse(false)); - var preset = context.flags().getValue("preset", null); - JsonObject settings = null; - - if (preset != null && generator != null) { - plugin.bundle().sendMessage(sender, "command.flag.combination", - Placeholder.parsed("flag-1", "generator"), - Placeholder.parsed("flag-2", "preset")); - return; - } else if (preset != null && !Objects.equals(type, WorldType.FLAT)) { - plugin.bundle().sendMessage(sender, "world.preset.flat"); - return; - } else if (preset != null) { - final var fileName = preset + ".json"; - var match = PresetFile.of(IO.of(plugin.presetsFolder(), fileName)); - if (match != null) settings = match.settings(); - } else if (type.equals(WorldType.FLAT)) { - settings = Preset.serialize(Presets.CLASSIC_FLAT); - } - - base.ifPresent(world -> { - var placeholder = Placeholder.parsed("world", world.getName()); - if (copy(world.getWorldFolder(), new File(Bukkit.getWorldContainer(), name))) - plugin.bundle().sendMessage(sender, "world.clone.success", placeholder); - else plugin.bundle().sendMessage(sender, "world.clone.failed", placeholder); - }); - - var image = plugin.imageProvider().load(plugin.imageProvider().createWorldImage() - .name(name).key(key).settings(settings).generator(generator).deletionType(deletion) - .environment(environment).worldType(type).autoSave(autoSave).generateStructures(structures) - .hardcore(hardcore).loadOnStart(!loadManual).seed(seed)); - - var message = image != null ? "world.create.success" : "world.create.failed"; - plugin.bundle().sendMessage(sender, message, Placeholder.parsed("world", name)); - if (image == null || !(sender instanceof Entity entity)) return; - entity.teleportAsync(image.getWorld().getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND); - } - - private static boolean copy(File source, File destination) { - return source.isDirectory() ? copyDirectory(source, destination) : copyFile(source, destination); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private static boolean copyDirectory(File source, File destination) { - if (!destination.exists()) destination.mkdir(); - var list = source.list(); - if (list == null) return false; - List.of(list).forEach(file -> copy( - new File(source, file), - new File(destination, file) - )); - return true; - } - - private static boolean copyFile(File source, File destination) { - if (source.getName().equals("uid.dat")) return true; - try (var in = new FileInputStream(source); - var out = new FileOutputStream(destination)) { - int length; - var buf = new byte[1024]; - while ((length = in.read(buf)) > 0) - out.write(buf, 0, length); - return true; - } catch (IOException ignored) { - return false; - } + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("create") + .requires(source -> source.getSender().hasPermission("worlds.command.create")) + .then(Commands.argument("name", StringArgumentType.string()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java index 2ceb581b..ac4ad936 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java @@ -1,48 +1,24 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.World; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.parser.flag.CommandFlag; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldDeleteCommand { - private final Worlds plugin; - private final Command.Builder builder; + private final WorldsPlugin plugin; - Command.Builder create() { - return builder.literal("delete") - .permission("worlds.command.world.delete") - .required("world", WorldParser.worldParser()) - .flag(CommandFlag.builder("keep-image")) - .flag(CommandFlag.builder("keep-world")) - .flag(CommandFlag.builder("schedule")) - .flag(CommandFlag.builder("confirm")) - .handler(this::execute); - } - - @SuppressWarnings("deprecation") - private void execute(CommandContext context) { - if (!context.flags().contains("confirm")) { - plugin.bundle().sendMessage(context.sender().getSender(), "command.confirmation", - Placeholder.parsed("action", "/" + context.rawInput().input()), - Placeholder.parsed("confirmation", "/" + context.rawInput().input() + " --confirm")); - return; - } - var world = context.get("world"); - var keepImage = context.flags().contains("keep-image"); - var keepWorld = context.flags().contains("keep-world"); - var schedule = context.flags().contains("schedule"); - var image = plugin.imageProvider().getOrDefault(world); - var result = image.delete(keepImage, keepWorld, schedule); - plugin.bundle().sendMessage(context.sender().getSender(), result.getMessage(), - Placeholder.parsed("world", world.getName()), - Placeholder.parsed("image", image.getWorldImage().name())); + ArgumentBuilder create() { + return Commands.literal("delete") + .requires(source -> source.getSender().hasPermission("worlds.command.delete")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java index f5e71d79..2b0e34cb 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java @@ -1,42 +1,24 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.exception.InvalidSyntaxException; - -import java.util.List; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldExportCommand { - private final Worlds plugin; - private final Command.Builder builder; - - Command.Builder create() { - return builder.literal("export", "save") - .permission("worlds.command.world.export") - .optional("world", WorldParser.worldParser()) - .handler(this::execute); - } + private final WorldsPlugin plugin; - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var world = context.optional("world").orElse(sender instanceof Player self ? self.getWorld() : null); - if (world == null) throw new InvalidSyntaxException("world export [world]", context.sender(), List.of()); - var placeholder = Placeholder.parsed("world", world.getName()); - try { - world.save(); - plugin.bundle().sendMessage(sender, "world.save.success", placeholder); - } catch (Exception e) { - plugin.bundle().sendMessage(sender, "world.save.failed", placeholder); - plugin.getComponentLogger().error("Failed to save world {}", world.getName(), e); - } + ArgumentBuilder create() { + return Commands.literal("export") + .requires(source -> source.getSender().hasPermission("worlds.command.export")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 226a1473..c7222530 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -1,67 +1,24 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import net.thenextlvl.worlds.image.WorldImage; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.incendo.cloud.Command; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldImportCommand { - private final Worlds plugin; - private final Command.Builder builder; - - @SuppressWarnings("deprecation") - Command.Builder create() { - return builder.literal("import") - .permission("worlds.command.world.import") - .required("image", StringParser.greedyStringParser(), - SuggestionProvider.blocking((context, input) -> plugin.imageProvider().findImages().stream() - .map(WorldImage::name) - .filter(name -> Bukkit.getWorld(name) == null) - .map(Suggestion::suggestion) - .toList())) - .handler(this::execute); - } - - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - - var imageName = context.get("image"); - var image = plugin.imageProvider().findImageFiles().stream() - .filter(file -> { - var worldImage = plugin.imageProvider().of(file); - return worldImage != null && worldImage.name().equals(imageName); - }) - .findFirst() - .map(plugin.imageProvider()::of) - .orElse(null); - - if (image == null) { - plugin.bundle().sendMessage(sender, "image.exists.not", Placeholder.parsed("image", imageName)); - return; - } - - var world = Bukkit.getWorld(image.name()); - var placeholder = Placeholder.parsed("world", world != null ? world.getName() : image.name()); - if (world != null) { - plugin.bundle().sendMessage(sender, "world.known", placeholder); - return; - } - - var result = plugin.imageProvider().load(image); - var message = result != null ? "world.import.success" : "world.import.failed"; - plugin.bundle().sendMessage(sender, message, placeholder); - if (result == null || !(sender instanceof Player player)) return; - player.teleportAsync(result.getWorld().getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND); + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("import") + .requires(source -> source.getSender().hasPermission("worlds.command.import")) + .then(Commands.argument("world", StringArgumentType.string()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index e2932a89..99fc38e9 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -1,73 +1,24 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.World; -import org.bukkit.WorldType; -import org.bukkit.entity.Player; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.exception.InvalidSyntaxException; -import org.jetbrains.annotations.Nullable; - -import java.util.List; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldInfoCommand { - private final Worlds plugin; - private final Command.Builder builder; - - Command.Builder create() { - return builder.literal("info") - .permission("worlds.command.world.info") - .optional("world", WorldParser.worldParser()) - .handler(this::execute); - } - - @SuppressWarnings("deprecation") - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var world = context.optional("world").orElse(sender instanceof Player self ? self.getWorld() : null); - if (world == null) throw new InvalidSyntaxException("world info [world]", sender, List.of()); - var volume = plugin.imageProvider().getOrDefault(world); - plugin.bundle().sendMessage(sender, "world.info.name", - Placeholder.parsed("world", world.getName())); - plugin.bundle().sendMessage(sender, "world.info.players", - Placeholder.parsed("players", String.valueOf(world.getPlayers().size()))); - plugin.bundle().sendMessage(sender, "world.info.type", - Placeholder.parsed("type", getName(notnull(world.getWorldType(), WorldType.NORMAL)))); - plugin.bundle().sendMessage(sender, "world.info.environment", - Placeholder.parsed("environment", getName(world.getEnvironment()))); - plugin.bundle().sendMessage(sender, "world.info.generator", - Placeholder.parsed("generator", String.valueOf(notnull( - volume.getWorldImage().generator(), "Vanilla")))); - plugin.bundle().sendMessage(sender, "world.info.seed", - Placeholder.parsed("seed", String.valueOf(world.getSeed()))); - } - - private static String getName(World.Environment environment) { - return switch (environment) { - case THE_END -> "The End"; - case NETHER -> "Nether"; - case NORMAL -> "Normal"; - case CUSTOM -> "Custom"; - }; - } - - private String getName(WorldType type) { - return switch (type) { - case LARGE_BIOMES -> "Large Biomes"; - case AMPLIFIED -> "Amplified"; - case NORMAL -> "Normal"; - case FLAT -> "Flat"; - }; - } - - private V notnull(@Nullable V value, V defaultValue) { - return value != null ? value : defaultValue; + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("info") + .requires(source -> source.getSender().hasPermission("worlds.command.info")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java index 471e0850..3ed1956a 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java @@ -1,41 +1,22 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.JoinConfiguration; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.Bukkit; -import org.bukkit.generator.WorldInfo; -import org.incendo.cloud.Command; -import org.incendo.cloud.context.CommandContext; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldListCommand { - private final Worlds plugin; - private final Command.Builder builder; + private final WorldsPlugin plugin; - Command.Builder create() { - return builder.literal("list") - .permission("worlds.command.world.list") - .handler(this::execute); - } - - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var worlds = Bukkit.getWorlds().stream().map(WorldInfo::getName).toList(); - var joined = Component.join(JoinConfiguration.commas(true), worlds.stream() - .map(world -> Component.text(world) - .hoverEvent(HoverEvent.showText(plugin.bundle().component(sender, - "world.list.hover", Placeholder.parsed("world", world)))) - .clickEvent(ClickEvent.runCommand("/world teleport " + world))) - .toList()); - plugin.bundle().sendMessage(sender, "world.list", - Placeholder.parsed("amount", String.valueOf(worlds.size())), - Placeholder.component("worlds", joined)); + ArgumentBuilder create() { + return Commands.literal("list") + .requires(source -> source.getSender().hasPermission("worlds.command.list")) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java index 8fc5da88..c15e6b1f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java @@ -1,62 +1,22 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.location.LocationParser; -import org.incendo.cloud.component.DefaultValue; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.exception.InvalidCommandSenderException; -import org.incendo.cloud.parser.standard.FloatParser; - -import java.util.List; - -import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldSetSpawnCommand { - private final Worlds plugin; - private final Command.Builder builder; - - Command.Builder create() { - return builder.literal("setspawn") - .permission("worlds.command.world.setspawn") - // .senderType(Player.class) - .optional("position", LocationParser.locationParser(), - DefaultValue.dynamic(context -> context.sender().getLocation())) - .optional("angle", FloatParser.floatParser(-360, 360), - DefaultValue.dynamic(context -> { - var executor = context.sender().getExecutor(); - return executor != null ? executor.getYaw() : 0; - })) - .handler(this::execute); - } - - private void execute(CommandContext context) { - if (!(context.sender().getSender() instanceof Player player)) - throw new InvalidCommandSenderException(context.sender(), Player.class, List.of(), context.command()); - - var location = context.get("position"); - float angle = context.get("angle"); - - var success = player.getWorld().setSpawnLocation( - location.getBlockX(), - location.getBlockY(), - location.getBlockZ(), - angle - ); - if (success) player.teleportAsync(player.getWorld().getSpawnLocation(), COMMAND); - - var message = success ? "world.spawn.set.success" : "world.spawn.set.failed"; - plugin.bundle().sendMessage(player, message, - Placeholder.parsed("x", String.valueOf(location.getBlockX())), - Placeholder.parsed("y", String.valueOf(location.getBlockY())), - Placeholder.parsed("z", String.valueOf(location.getBlockZ())), - Placeholder.parsed("angle", String.valueOf(angle))); + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("setspawn") + .requires(source -> source.getSender().hasPermission("worlds.command.setspawn")) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java index ebc3fb71..ccae362f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java @@ -1,42 +1,24 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.PlayerParser; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.exception.InvalidSyntaxException; - -import java.util.List; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldTeleportCommand { - private final Worlds plugin; - private final Command.Builder builder; - - Command.Builder create() { - return builder.literal("teleport", "tp") - .permission("worlds.command.world.teleport") - .required("world", WorldParser.worldParser()) - .optional("player", PlayerParser.playerParser()) - .handler(this::execute); - } + private final WorldsPlugin plugin; - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var world = context.get("world"); - var player = context.optional("player").orElse(sender instanceof Player self ? self : null); - if (player == null) throw new InvalidSyntaxException("world teleport [world] [player]", sender, List.of()); - player.teleportAsync(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND); - var message = player.equals(sender) ? "world.teleport.player.self" : "world.teleport.player.other"; - plugin.bundle().sendMessage(sender, message, Placeholder.parsed("world", world.getName()), - Placeholder.parsed("player", player.getName())); + ArgumentBuilder create() { + return Commands.literal("teleport") + .requires(source -> source.getSender().hasPermission("worlds.command.teleport")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); } } From e9904a9719322ada17eafba72ef5fc588f3370dd Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:05:00 +0200 Subject: [PATCH 014/182] Add commands for world unload, load, spawn, and clone --- .../worlds/command/WorldCloneCommand.java | 24 +++++++++++++++++++ .../worlds/command/WorldLoadCommand.java | 24 +++++++++++++++++++ .../worlds/command/WorldSpawnCommand.java | 22 +++++++++++++++++ .../worlds/command/WorldUnloadCommand.java | 24 +++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java new file mode 100644 index 00000000..3a4004e2 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java @@ -0,0 +1,24 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldCloneCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("clone") + .requires(source -> source.getSender().hasPermission("worlds.command.clone")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java new file mode 100644 index 00000000..f8db99b0 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java @@ -0,0 +1,24 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldLoadCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("load") + .requires(source -> source.getSender().hasPermission("worlds.command.load")) + .then(Commands.argument("world", StringArgumentType.string()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java new file mode 100644 index 00000000..d13b7336 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java @@ -0,0 +1,22 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldSpawnCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("spawn") + .requires(source -> source.getSender().hasPermission("worlds.command.spawn")) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java new file mode 100644 index 00000000..c38fbe44 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -0,0 +1,24 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldUnloadCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("unload") + .requires(source -> source.getSender().hasPermission("worlds.command.unload")) + .then(Commands.argument("world", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + })); + } +} From 1416ada7d1af2eb3d69e1e74d1877020a4b868fa Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:06:43 +0200 Subject: [PATCH 015/182] Add package-info.java with @NotNull defaults across packages --- .../net/thenextlvl/worlds/command/package-info.java | 10 ++++++++++ .../java/net/thenextlvl/worlds/image/package-info.java | 10 ++++++++++ .../java/net/thenextlvl/worlds/link/package-info.java | 10 ++++++++++ .../net/thenextlvl/worlds/listener/package-info.java | 10 ++++++++++ .../java/net/thenextlvl/worlds/model/package-info.java | 10 ++++++++++ 5 files changed, 50 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/package-info.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/listener/package-info.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/model/package-info.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/command/package-info.java new file mode 100644 index 00000000..c1080462 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.command; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java new file mode 100644 index 00000000..399d5585 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.image; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java new file mode 100644 index 00000000..39ff5b2f --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.link; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/package-info.java new file mode 100644 index 00000000..e4c41ffc --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.listener; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/model/package-info.java new file mode 100644 index 00000000..03e75f4d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.model; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From d469331152d34e411f4f8857d0ec1f9bf0cabdbb Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:07:34 +0200 Subject: [PATCH 016/182] Rename and update package structure for organization --- .../worlds/command => old}/CustomSyntaxFormatter.java | 2 +- .../net/thenextlvl/worlds/{util => model}/PortalCooldown.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename {plugin/src/main/java/net/thenextlvl/worlds/command => old}/CustomSyntaxFormatter.java (96%) rename plugin/src/main/java/net/thenextlvl/worlds/{util => model}/PortalCooldown.java (95%) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/CustomSyntaxFormatter.java b/old/CustomSyntaxFormatter.java similarity index 96% rename from plugin/src/main/java/net/thenextlvl/worlds/command/CustomSyntaxFormatter.java rename to old/CustomSyntaxFormatter.java index 10401363..5da50670 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/CustomSyntaxFormatter.java +++ b/old/CustomSyntaxFormatter.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.command; +package net.thenextlvl.worlds.command.old; import core.annotation.MethodsReturnNotNullByDefault; import core.annotation.ParametersAreNotNullByDefault; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/util/PortalCooldown.java b/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java similarity index 95% rename from plugin/src/main/java/net/thenextlvl/worlds/util/PortalCooldown.java rename to plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java index d93fbf7d..cd12aded 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/util/PortalCooldown.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.util; +package net.thenextlvl.worlds.model; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import org.bukkit.entity.Entity; From 5e7750b382d2eb67b5a4dbb1016760bf739dd9b1 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:08:28 +0200 Subject: [PATCH 017/182] Refactor CraftImage by removing redundant methods and dependencies --- .../thenextlvl/worlds/image/CraftImage.java | 87 ++++--------------- 1 file changed, 18 insertions(+), 69 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java b/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java index 6d9410b3..89fcb0f8 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java @@ -1,106 +1,55 @@ package net.thenextlvl.worlds.image; -import core.file.FileIO; -import core.io.IO; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.Worlds; +import net.thenextlvl.worlds.WorldsPlugin; import org.bukkit.Bukkit; import org.bukkit.World; import java.io.File; -import java.io.IOException; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -class CraftImage implements Image { - private final FileIO file; - private final Worlds plugin; +class CraftImage { + private final WorldsPlugin plugin; private final World world; - CraftImage(Worlds plugin, World world, WorldImage image) { - this(CraftImageProvider.loadFile(IO.of(Bukkit.getWorldContainer(), image.name() + ".image"), image), plugin, world); - } - - CraftImage(Worlds plugin, World world) { - this(plugin, world, plugin.imageProvider().of(world)); - } - - @Override - public CraftImage save() { - file.save(); - return this; - } - - @Override - public WorldImage getWorldImage() { - return getFile().getRoot(); - } - - @Override public boolean canUnload() { - return !Bukkit.isTickingWorlds() && world.getPlayers().isEmpty(); + return /*!Bukkit.isTickingWorlds() && */world.getPlayers().isEmpty(); } - @Override - public boolean canDelete() { - return getWorld().getKey().toString().equals("minecraft:overworld"); + public boolean isDeletable() { + return !getWorld().getKey().toString().equals("minecraft:overworld"); } - @Override public boolean unload() { return canUnload() && Bukkit.unloadWorld(world, world.isAutoSave()); } - @Override - public DeleteResult delete(boolean keepImage, boolean keepWorld, boolean schedule) { - return schedule ? scheduleDeletion(keepImage, keepWorld) : deleteNow(keepImage, keepWorld); + public DeleteResult delete(boolean schedule) { + return schedule ? scheduleDeletion() : deleteNow(); } - @Override - public DeleteResult deleteNow(boolean keepImage, boolean keepWorld) { - if (canDelete()) return DeleteResult.WORLD_DELETE_ILLEGAL; + public DeleteResult deleteNow() { + if (!isDeletable()) return DeleteResult.EXEMPTED; var fallback = Bukkit.getWorlds().getFirst().getSpawnLocation(); getWorld().getPlayers().forEach(player -> player.teleport(fallback)); - try { - if (!keepImage && file.getIO().exists() && !file.delete()) - return DeleteResult.IMAGE_DELETE_FAILED; - } catch (IOException e) { - return DeleteResult.IMAGE_DELETE_FAILED; - } - - if (keepImage && keepWorld) - return Bukkit.unloadWorld(world, world.isAutoSave()) - ? DeleteResult.WORLD_UNLOADED - : DeleteResult.WORLD_UNLOAD_FAILED; - - if (!Bukkit.unloadWorld(world, world.isAutoSave() && keepWorld)) - return DeleteResult.WORLD_UNLOAD_FAILED; + if (!unload()) return DeleteResult.UNLOAD_FAILED; - if (!keepWorld && !delete(world.getWorldFolder())) - return DeleteResult.WORLD_DELETE_FAILED; - - return DeleteResult.WORLD_DELETED; + return delete(world.getWorldFolder()) + ? DeleteResult.SUCCESS + : DeleteResult.FAILED; } - @Override - public DeleteResult scheduleDeletion(boolean keepImage, boolean keepWorld) { - if (keepWorld && (keepImage || !getFile().getIO().exists())) - return DeleteResult.WORLD_DELETE_NOTHING; - + public DeleteResult scheduleDeletion() { Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (!keepWorld) delete(getWorld().getWorldFolder()); - if (!keepImage) try { - getFile().delete(); - } catch (IOException e) { - plugin.getComponentLogger().error("Failed to delete world {}", getWorld().getName(), e); - } + if (delete(getWorld().getWorldFolder())) return; + plugin.getComponentLogger().error("Failed to delete world {}", getWorld().getName()); })); - - return DeleteResult.WORLD_DELETE_SCHEDULED; + return DeleteResult.SCHEDULED; } private boolean delete(File file) { From f81ae3272bb4827dc7b79cf914a4c3edbb2683c0 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:08:35 +0200 Subject: [PATCH 018/182] Rename Worlds to WorldsPlugin in CraftLinkRegistry.java --- .../java/net/thenextlvl/worlds/link/CraftLinkRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java b/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java index 3c823dd6..1420b238 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java @@ -7,7 +7,7 @@ import core.io.IO; import core.paper.adapters.world.WorldAdapter; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.Worlds; +import net.thenextlvl.worlds.WorldsPlugin; import org.bukkit.World; import java.util.HashSet; @@ -17,7 +17,7 @@ @RequiredArgsConstructor public class CraftLinkRegistry implements LinkRegistry { private final Set links = new HashSet<>(); - private final Worlds plugin; + private final WorldsPlugin plugin; @Override public Stream getLinks() { From c8792ff7f0d782ca02455dd3f70cf5e604e96ecf Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:09:05 +0200 Subject: [PATCH 019/182] Update plugin references to WorldsPlugin and organize imports --- .../java/net/thenextlvl/worlds/listener/PortalListener.java | 6 +++--- .../java/net/thenextlvl/worlds/listener/WorldListener.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index f9be0399..1f07912b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -2,9 +2,9 @@ import io.papermc.paper.event.entity.EntityPortalReadyEvent; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.Worlds; +import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.link.Link; -import net.thenextlvl.worlds.util.PortalCooldown; +import net.thenextlvl.worlds.model.PortalCooldown; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.PortalType; @@ -23,7 +23,7 @@ @RequiredArgsConstructor public class PortalListener implements Listener { private final PortalCooldown cooldown = new PortalCooldown(); - private final Worlds plugin; + private final WorldsPlugin plugin; @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityPortal(EntityPortalReadyEvent event) { diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java index 2cc33c78..37f8ae26 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java @@ -1,7 +1,7 @@ package net.thenextlvl.worlds.listener; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.Worlds; +import net.thenextlvl.worlds.WorldsPlugin; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -9,7 +9,7 @@ @RequiredArgsConstructor public class WorldListener implements Listener { - private final Worlds plugin; + private final WorldsPlugin plugin; @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldUnload(WorldUnloadEvent event) { From cc97b937802ac8d5c256d8db8333205b6a249cb4 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:09:15 +0200 Subject: [PATCH 020/182] Add LevelExtras record to model package --- .../main/java/net/thenextlvl/worlds/model/LevelExtras.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java new file mode 100644 index 00000000..b5ed9494 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java @@ -0,0 +1,6 @@ +package net.thenextlvl.worlds.model; + +public record LevelExtras( + +) { +} From 06659f02c787c57c28e5d834e42d13886e6c1f66 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 00:10:41 +0200 Subject: [PATCH 021/182] [ci skip] Bump version from 1.2.5 to 2.0.0 in build.gradle.kts --- api/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 9adfdf83..a0881321 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -11,7 +11,7 @@ java { } group = "net.thenextlvl.worlds" -version = "1.2.5" +version = "2.0.0" repositories { mavenCentral() From 26656116271f6c961eb67dd7535c4c5f52f962c7 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 10:11:28 +0200 Subject: [PATCH 022/182] Refactor WorldLinkCommand to us Brigadier --- .../worlds/command/WorldLinkCommand.java | 162 ++---------------- 1 file changed, 17 insertions(+), 145 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java index ba55c017..638e2a86 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java @@ -1,154 +1,26 @@ + package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.worlds.Worlds; -import net.thenextlvl.worlds.link.Link; -import org.bukkit.PortalType; -import org.bukkit.World; -import org.incendo.cloud.Command; -import org.incendo.cloud.bukkit.parser.WorldParser; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.description.Description; -import org.incendo.cloud.exception.InvalidSyntaxException; -import org.incendo.cloud.parser.standard.EnumParser; -import org.incendo.cloud.parser.standard.StringParser; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.suggestion.SuggestionProvider; +import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") -abstract class WorldLinkCommand { - protected final Worlds plugin; - protected final Command.Builder builder; - - protected final Command.Builder linkCommand() { - return builder.literal("link") - .commandDescription(Description.description("link portals between dimensions")); - } - - abstract Command.Builder create(); - - static class Create extends WorldLinkCommand { - public Create(Worlds plugin, Command.Builder builder) { - super(plugin, builder); - } - - @Override - Command.Builder create() { - return linkCommand().literal("create") - .permission("worlds.command.link.create") - .required("source", WorldParser.worldParser()) - .required("destination", WorldParser.worldParser()) - .optional("portal-type", EnumParser.enumParser(PortalType.class)) - .handler(this::execute); - } - - private void execute(CommandContext context) { - handleCreate(context); - } - - private void handleCreate(CommandContext context) { - var source = context.get("source"); - var destination = context.get("destination"); - var portalType = context.optional("portal-type") - .orElse(getPortalType(source.getEnvironment(), destination.getEnvironment())); - - var sender = context.sender().getSender(); - - if (portalType == null) throw new InvalidSyntaxException( - "world link create [source] [destination] [portal-type]", - sender, java.util.List.of() - ); - - var link = new Link(portalType, source, destination); - if (plugin.linkRegistry().register(link)) { - plugin.bundle().sendMessage(sender, "link.created", - Placeholder.parsed("type", link.portalType().name().toLowerCase()), - Placeholder.parsed("source", link.source().getName()), - Placeholder.parsed("destination", link.destination().getName())); - } else plugin.bundle().sendMessage(sender, "link.exists", - Placeholder.parsed("type", link.portalType().name().toLowerCase()), - Placeholder.parsed("source", link.source().getName()), - Placeholder.parsed("destination", link.destination().getName())); - } - - private PortalType getPortalType(World.Environment source, World.Environment destination) { - return switch (source) { - case NORMAL -> switch (destination) { - case NETHER -> PortalType.NETHER; - case THE_END -> PortalType.ENDER; - default -> null; - }; - case NETHER -> switch (destination) { - case THE_END -> PortalType.ENDER; - case NORMAL -> PortalType.NETHER; - default -> null; - }; - case THE_END -> switch (destination) { - case NORMAL -> PortalType.ENDER; - case NETHER -> PortalType.NETHER; - default -> null; - }; - default -> null; - }; - } - } - - static class Delete extends WorldLinkCommand { - public Delete(Worlds plugin, Command.Builder builder) { - super(plugin, builder); - } - - @Override - Command.Builder create() { - return linkCommand().literal("delete") - .permission("worlds.command.link.delete") - .required("link", StringParser.greedyStringParser(), - SuggestionProvider.blocking((context, input) -> plugin.linkRegistry().getLinks() - .map(Link::toString) - .map(Suggestion::suggestion) - .toList())) - .handler(this::execute); - } - - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var linkName = context.get("link"); - var link = plugin.linkRegistry().getLinks() - .filter(link1 -> link1.toString().equals(linkName)) - .findFirst() - .orElse(null); - if (link != null && plugin.linkRegistry().unregister(link)) { - plugin.bundle().sendMessage(sender, "link.deleted", - Placeholder.parsed("type", link.portalType().name().toLowerCase()), - Placeholder.parsed("source", link.source().getName()), - Placeholder.parsed("destination", link.destination().getName())); - } else plugin.bundle().sendMessage(sender, "link.exists.not", - Placeholder.parsed("link", linkName)); - } - } - - static class List extends WorldLinkCommand { - public List(Worlds plugin, Command.Builder builder) { - super(plugin, builder); - } - - @Override - Command.Builder create() { - return linkCommand().literal("list") - .permission("worlds.command.link.list") - .handler(this::execute); - } - - private void execute(CommandContext context) { - var sender = context.sender().getSender(); - var links = plugin.linkRegistry().getLinks().map(Link::toString).toList(); - if (links.isEmpty()) plugin.bundle().sendMessage(sender, "link.list.empty"); - else plugin.bundle().sendMessage(sender, "link.list", - Placeholder.parsed("links", String.join(", ", links)), - Placeholder.parsed("amount", String.valueOf(links.size()))); - } +class WorldLinkCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("link") + .requires(source -> source.getSender().hasPermission("worlds.command.link")) + .then(Commands.argument("source", ArgumentTypes.world()) + .then(Commands.argument("destination", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }))); } } From 6dad784189e83231e02356e1216992d8cd31fe7d Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 12:29:10 +0200 Subject: [PATCH 023/182] Add JitPack repository and update paper dependency to 1.4.1 --- plugin/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index fc085afd..e0f03101 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -23,6 +23,7 @@ version = project(":api").version repositories { mavenCentral() + maven("https://jitpack.io") maven("https://repo.thenextlvl.net/releases") maven("https://repo.thenextlvl.net/snapshots") maven("https://repo.papermc.io/repository/maven-public/") @@ -40,7 +41,7 @@ dependencies { implementation("net.thenextlvl.core:nbt:1.4.2") implementation("net.thenextlvl.core:files:1.0.5") implementation("net.thenextlvl.core:i18n:1.0.18") - implementation("net.thenextlvl.core:paper:1.3.5") + implementation("net.thenextlvl.core:paper:1.4.1") implementation("net.thenextlvl.core:adapters:1.0.9") annotationProcessor("org.projectlombok:lombok:1.18.32") From daab3daf2f7ca830de8756c4ecd1bd6b3e612abd Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 12:29:36 +0200 Subject: [PATCH 024/182] Refactor world save messages with language keys. --- plugin/src/main/resources/worlds.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index aeff2c64..1e22379a 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -1,6 +1,7 @@ prefix=Worlds » -world.save.success= Saved the world -world.save.failed= Failed to save the world +command.world.save.saving= +command.world.save.success= +command.world.save.failed= world.create.success= Successfully created the world world.create.failed= Failed to create the world world.import.success= Successfully imported the world From 231cda2501f88a61072642c05004916b2a3f0c8b Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 12:29:45 +0200 Subject: [PATCH 025/182] Update getLinks method to return Set instead of Stream --- .../java/net/thenextlvl/worlds/link/CraftLinkRegistry.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java b/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java index 1420b238..ef436fca 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java @@ -12,7 +12,6 @@ import java.util.HashSet; import java.util.Set; -import java.util.stream.Stream; @RequiredArgsConstructor public class CraftLinkRegistry implements LinkRegistry { @@ -20,8 +19,8 @@ public class CraftLinkRegistry implements LinkRegistry { private final WorldsPlugin plugin; @Override - public Stream getLinks() { - return links.stream(); + public Set getLinks() { + return Set.copyOf(links); } @Override From a681419f50127f20336852afe40d9b516bf3383a Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 12:29:54 +0200 Subject: [PATCH 026/182] Refactor entity portal event handling in PortalListener. --- .../java/net/thenextlvl/worlds/listener/PortalListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index 1f07912b..49bbdf43 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -27,8 +27,8 @@ public class PortalListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityPortal(EntityPortalReadyEvent event) { - plugin.linkRegistry().getLinks() - .filter(link -> event.getPortalType().equals(link.portalType())) + plugin.linkRegistry().getLinks().stream() + // .filter(link -> event.getPortalType().equals(link.portalType())) .filter(link -> event.getEntity().getWorld().equals(link.source())) .findFirst() .map(Link::destination) From 68f0bcf3f4ea19877b8dd33108704ef59bf8ca4c Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 14:00:34 +0200 Subject: [PATCH 027/182] Add NamespacedKey and autoSave fields to LevelExtras --- .../main/java/net/thenextlvl/worlds/model/LevelExtras.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java index b5ed9494..2c9f8018 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java @@ -1,6 +1,10 @@ package net.thenextlvl.worlds.model; -public record LevelExtras( +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; +public record LevelExtras( + @Nullable NamespacedKey key, + boolean autoSave ) { } From c2030f985b9427f5f2d5c904bc0936f786db5b8c Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 14:37:52 +0200 Subject: [PATCH 028/182] Add version checker for Worlds plugin --- .../net/thenextlvl/worlds/WorldsPlugin.java | 2 + .../worlds/version/PluginVersionChecker.java | 41 +++++++++++++++++++ .../worlds/version/package-info.java | 10 +++++ 3 files changed, 53 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/version/PluginVersionChecker.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/version/package-info.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 7a859d56..df9ffd45 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -51,6 +51,8 @@ public class WorldsPlugin extends JavaPlugin { public void onLoad() { Bukkit.getServicesManager().register(LinkRegistry.class, linkRegistry(), this, ServicePriority.Highest); if (presetsFolder().list() == null) saveDefaultPresets(); + + versionChecker().checkVersion(); } @Override diff --git a/plugin/src/main/java/net/thenextlvl/worlds/version/PluginVersionChecker.java b/plugin/src/main/java/net/thenextlvl/worlds/version/PluginVersionChecker.java new file mode 100644 index 00000000..8b302e5a --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/version/PluginVersionChecker.java @@ -0,0 +1,41 @@ +package net.thenextlvl.worlds.version; + +import core.paper.version.PaperHangarVersionChecker; +import core.version.SemanticVersion; +import lombok.Getter; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +@Getter +@SuppressWarnings("UnstableApiUsage") +public class PluginVersionChecker extends PaperHangarVersionChecker { + private final SemanticVersion versionRunning; + private final Plugin plugin; + + public PluginVersionChecker(Plugin plugin) { + super("Worlds"); + this.plugin = plugin; + this.versionRunning = Objects.requireNonNull(parseVersion(plugin.getPluginMeta().getVersion())); + } + + @Override + public @Nullable SemanticVersion parseVersion(String version) { + return SemanticVersion.parse(version); + } + + public void checkVersion() { + retrieveLatestSupportedVersion(latest -> latest.ifPresentOrElse(version -> { + if (version.equals(getVersionRunning())) { + plugin.getComponentLogger().info("You are running the latest version of Worlds"); + } else if (version.compareTo(getVersionRunning()) > 0) { + plugin.getComponentLogger().warn("An update for Worlds is available"); + plugin.getComponentLogger().warn("You are running version {}, the latest supported version is {}", getVersionRunning(), version); + plugin.getComponentLogger().warn("Update at https://modrinth.com/plugin/worlds-1 or https://hangar.papermc.io/TheNextLvl/Worlds"); + } else { + plugin.getComponentLogger().warn("You are running a snapshot version of Worlds"); + } + }, () -> plugin.getComponentLogger().error("Version check failed"))); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/version/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/version/package-info.java new file mode 100644 index 00000000..dc9aa34d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/version/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.version; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From 3caf9d1cbb330f0f752b88a7d2ca14a29897cfa8 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:35:45 +0200 Subject: [PATCH 029/182] [ci skip] Remove unused command permission and flag combination messages --- plugin/src/main/resources/worlds.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 1e22379a..f31d667e 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -41,8 +41,6 @@ link.list= Links (): < image.exists.not= An image called does not exist environment.custom= Cannot generate world using environment custom player.unknown= The player is not online -command.permission= You have no rights () command.sender= You cannot use this command command.argument= Invalid command argument -command.flag.combination= You can't combine the flag with command.confirmation= '>Confirm your '>action, this cannot be undone! \ No newline at end of file From a637209070364a23f108580b38578c9a85515d56 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:40:28 +0200 Subject: [PATCH 030/182] Delete CraftImage --- .../thenextlvl/worlds/image/CraftImage.java | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java b/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java deleted file mode 100644 index 89fcb0f8..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/CraftImage.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.thenextlvl.worlds.image; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.WorldsPlugin; -import org.bukkit.Bukkit; -import org.bukkit.World; - -import java.io.File; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -class CraftImage { - private final WorldsPlugin plugin; - private final World world; - - public boolean canUnload() { - return /*!Bukkit.isTickingWorlds() && */world.getPlayers().isEmpty(); - } - - public boolean isDeletable() { - return !getWorld().getKey().toString().equals("minecraft:overworld"); - } - - public boolean unload() { - return canUnload() && Bukkit.unloadWorld(world, world.isAutoSave()); - } - - public DeleteResult delete(boolean schedule) { - return schedule ? scheduleDeletion() : deleteNow(); - } - - public DeleteResult deleteNow() { - if (!isDeletable()) return DeleteResult.EXEMPTED; - - var fallback = Bukkit.getWorlds().getFirst().getSpawnLocation(); - getWorld().getPlayers().forEach(player -> player.teleport(fallback)); - - if (!unload()) return DeleteResult.UNLOAD_FAILED; - - return delete(world.getWorldFolder()) - ? DeleteResult.SUCCESS - : DeleteResult.FAILED; - } - - public DeleteResult scheduleDeletion() { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (delete(getWorld().getWorldFolder())) return; - plugin.getComponentLogger().error("Failed to delete world {}", getWorld().getName()); - })); - return DeleteResult.SCHEDULED; - } - - private boolean delete(File file) { - if (file.isFile()) return file.delete(); - var files = file.listFiles(); - if (files == null) return false; - for (var file1 : files) delete(file1); - return file.delete(); - } -} From 2e0f65239f3ed29e1e5791e215a9e62db9cd08cc Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:40:37 +0200 Subject: [PATCH 031/182] Add CommandFlagsArgument to support command flag parsing --- .../argument/CommandFlagsArgument.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java new file mode 100644 index 00000000..09629333 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java @@ -0,0 +1,34 @@ +package net.thenextlvl.worlds.command.argument; + +import com.mojang.brigadier.arguments.StringArgumentType; +import core.paper.command.WrappedArgumentType; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public class CommandFlagsArgument extends WrappedArgumentType { + public CommandFlagsArgument(Set flags) { + super(StringArgumentType.greedyString(), (reader, type) -> { + var split = type.split(" "); + if (Arrays.stream(split).anyMatch(s -> !flags.contains(s))) + throw new IllegalArgumentException("unrecognized flag"); + return new Flags(split); + }, (context, builder) -> { + var index = builder.getRemaining().lastIndexOf(' ') + 1; + var substring = builder.getRemaining().substring(index); + flags.stream() + .filter(flag -> !builder.getRemaining().contains(flag)) + .filter(flag -> flag.startsWith(substring)) + .forEach(s -> builder.suggest(builder.getRemaining() + s.substring(substring.length()))); + return builder.buildFuture(); + }); + } + + public static class Flags extends HashSet { + private Flags(@NotNull String... flags) { + super(Set.of(flags)); + } + } +} From 960d1756679dd17c84c5c0067f066078ef5665e8 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:40:42 +0200 Subject: [PATCH 032/182] Add WorldSuggestionProvider for command suggestions --- .../suggestion/WorldSuggestionProvider.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java new file mode 100644 index 00000000..03434121 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java @@ -0,0 +1,27 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.key.Key; +import org.bukkit.Keyed; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +public class WorldSuggestionProvider implements SuggestionProvider { + private final Plugin plugin; + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + plugin.getServer().getWorlds().stream() + .map(Keyed::key) + .map(Key::asString) + .filter(s -> s.contains(builder.getRemaining())) + .forEach(builder::suggest); + return builder.buildFuture(); + } +} From 27616593ecbcbe2d4f28086a152abd5a40fe0dce Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:40:48 +0200 Subject: [PATCH 033/182] Add world suggestion provider to clone command --- .../java/net/thenextlvl/worlds/command/WorldCloneCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java index 3a4004e2..5c597278 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java @@ -7,6 +7,7 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,6 +18,7 @@ class WorldCloneCommand { return Commands.literal("clone") .requires(source -> source.getSender().hasPermission("worlds.command.clone")) .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) .executes(context -> { return Command.SINGLE_SUCCESS; })); From ed54bb98e6569ef3289e563d8cde8403738e12c8 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:01 +0200 Subject: [PATCH 034/182] Add new world management commands --- .../java/net/thenextlvl/worlds/command/WorldCommand.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java index 4436833b..3fbe4ae2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java @@ -18,8 +18,13 @@ public void register() { .then(new WorldExportCommand(plugin).create()) .then(new WorldImportCommand(plugin).create()) .then(new WorldInfoCommand(plugin).create()) + .then(new WorldLinkCommand(plugin).create()) .then(new WorldListCommand(plugin).create()) .then(new WorldLoadCommand(plugin).create()) + .then(new WorldSaveAllCommand(plugin).create()) + .then(new WorldSaveCommand(plugin).create()) + .then(new WorldSaveOffCommand(plugin).create()) + .then(new WorldSaveOnCommand(plugin).create()) .then(new WorldSetSpawnCommand(plugin).create()) .then(new WorldSpawnCommand(plugin).create()) .then(new WorldTeleportCommand(plugin).create()) From 7ebb129a040a51635f8deb52a61786baf43322c9 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:09 +0200 Subject: [PATCH 035/182] Remove unused WorldExportCommand from WorldCommand.java --- .../main/java/net/thenextlvl/worlds/command/WorldCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java index 3fbe4ae2..4ef6085b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java @@ -15,7 +15,6 @@ public void register() { .then(new WorldCloneCommand(plugin).create()) .then(new WorldCreateCommand(plugin).create()) .then(new WorldDeleteCommand(plugin).create()) - .then(new WorldExportCommand(plugin).create()) .then(new WorldImportCommand(plugin).create()) .then(new WorldInfoCommand(plugin).create()) .then(new WorldLinkCommand(plugin).create()) From 9b9df068ee9c289dbd2f2dd623cb72e949186694 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:16 +0200 Subject: [PATCH 036/182] Update WorldCreateCommand to use ArgumentTypes.key for "create" --- .../net/thenextlvl/worlds/command/WorldCreateCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index 7ed3125e..32756582 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -1,10 +1,10 @@ package net.thenextlvl.worlds.command; import com.mojang.brigadier.Command; -import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; @@ -16,7 +16,7 @@ class WorldCreateCommand { ArgumentBuilder create() { return Commands.literal("create") .requires(source -> source.getSender().hasPermission("worlds.command.create")) - .then(Commands.argument("name", StringArgumentType.string()) + .then(Commands.argument("key", ArgumentTypes.key()) .executes(context -> { return Command.SINGLE_SUCCESS; })); From 6daccbde757d6ed45a9705b6db12f4db800436c9 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:24 +0200 Subject: [PATCH 037/182] Add confirmation and scheduling options to world deletion command --- .../worlds/command/WorldDeleteCommand.java | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java index ac4ad936..f30fadbb 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java @@ -2,11 +2,20 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.argument.CommandFlagsArgument; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import net.thenextlvl.worlds.image.DeleteResult; +import org.bukkit.World; + +import java.io.File; +import java.util.Set; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,8 +26,73 @@ class WorldDeleteCommand { return Commands.literal("delete") .requires(source -> source.getSender().hasPermission("worlds.command.delete")) .then(Commands.argument("world", ArgumentTypes.world()) - .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("flags", new CommandFlagsArgument( + Set.of("--confirm", "--schedule") + )).executes(this::delete)) + .executes(this::confirmationNeeded)); + } + + private int confirmationNeeded(CommandContext context) { + var sender = context.getSource().getSender(); + plugin.bundle().sendMessage(sender, "command.confirmation", + Placeholder.parsed("action", "/" + context.getInput()), + Placeholder.parsed("confirmation", "/" + context.getInput() + " --confirm")); + return Command.SINGLE_SUCCESS; + } + + private int delete(CommandContext context) { + var flags = context.getArgument("flags", CommandFlagsArgument.Flags.class); + if (!flags.contains("--confirm")) return confirmationNeeded(context); + var world = context.getArgument("world", World.class); + var result = delete(world, flags.contains("--schedule")); + plugin.bundle().sendMessage(context.getSource().getSender(), result.getMessage(), + Placeholder.parsed("world", world.key().asString())); + return Command.SINGLE_SUCCESS; + } + + private DeleteResult delete(World world, boolean schedule) { + return schedule ? scheduleDeletion(world) : deleteNow(world); + } + + private boolean canUnload(World world) { + return /*!Bukkit.isTickingWorlds() && */world.getPlayers().isEmpty(); + } + + private boolean isDeletable(World world) { + return !world.getKey().toString().equals("minecraft:overworld"); + } + + private boolean unload(World world) { + return canUnload(world) && plugin.getServer().unloadWorld(world, false); + } + + private DeleteResult deleteNow(World world) { + if (!isDeletable(world)) return DeleteResult.EXEMPTED; + + var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); + world.getPlayers().forEach(player -> player.teleport(fallback)); + + if (!unload(world)) return DeleteResult.UNLOAD_FAILED; + + return delete(world.getWorldFolder()) + ? DeleteResult.SUCCESS + : DeleteResult.FAILED; + } + + private DeleteResult scheduleDeletion(World world) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (delete(world.getWorldFolder())) return; + plugin.getComponentLogger().error("Failed to delete world {}", world.getName()); + })); + return DeleteResult.SCHEDULED; + } + + private boolean delete(File file) { + if (file.isFile()) return file.delete(); + var files = file.listFiles(); + if (files == null) return false; + for (var file1 : files) delete(file1); + return file.delete(); } } From 4ac5a8c1375ad0adca200a8689a446ece66517ed Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:28 +0200 Subject: [PATCH 038/182] Add world suggestion provider to WorldInfoCommand --- .../java/net/thenextlvl/worlds/command/WorldInfoCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index 99fc38e9..9d3893cf 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -7,6 +7,7 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,6 +18,7 @@ class WorldInfoCommand { return Commands.literal("info") .requires(source -> source.getSender().hasPermission("worlds.command.info")) .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) .executes(context -> { return Command.SINGLE_SUCCESS; })); From 15bc1a6eb19679c290192d9ee292e0d2e1c0e49a Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:35 +0200 Subject: [PATCH 039/182] Add world suggestion provider to 'link' command --- .../java/net/thenextlvl/worlds/command/WorldLinkCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java index 638e2a86..3bd2cdd0 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java @@ -8,6 +8,7 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -18,6 +19,7 @@ class WorldLinkCommand { return Commands.literal("link") .requires(source -> source.getSender().hasPermission("worlds.command.link")) .then(Commands.argument("source", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) .then(Commands.argument("destination", ArgumentTypes.world()) .executes(context -> { return Command.SINGLE_SUCCESS; From c7578076a047e27a14f5ac87152e9bc0fee27fa3 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:44 +0200 Subject: [PATCH 040/182] Add WorldSaveAll command to save worlds via command --- .../worlds/command/WorldSaveAllCommand.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java new file mode 100644 index 00000000..d855554c --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java @@ -0,0 +1,32 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.craftbukkit.CraftServer; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldSaveAllCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("save-all") + .requires(source -> source.getSender().hasPermission("worlds.command.save-all")) + .then(Commands.literal("flush").executes(context -> saveAll(context.getSource(), true))) + .executes(context -> saveAll(context.getSource(), false)); + } + + private int saveAll(CommandSourceStack source, boolean flush) { + plugin.bundle().sendMessage(source.getSender(), "command.world.save.saving"); + var server = ((CraftServer) plugin.getServer()).getServer(); + var saved = server.saveEverything(!(source.getSender() instanceof ConsoleCommandSender), flush, true); + var message = saved ? "command.world.save.success" : "command.world.save.failed"; + plugin.bundle().sendMessage(source.getSender(), message); + return saved ? Command.SINGLE_SUCCESS : 0; + } +} From 236d1545cbd3782063b0459925a255a44ae4b630 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:41:55 +0200 Subject: [PATCH 041/182] Add WorldSaveCommand for saving single worlds. --- .../worlds/command/WorldSaveCommand.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java new file mode 100644 index 00000000..c82a891d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java @@ -0,0 +1,38 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; +import org.bukkit.entity.Player; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldSaveCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("save") + .requires(source -> source.getSender().hasPermission("worlds.command.save")) + .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .executes(context -> { + var world = context.getArgument("world", World.class); + world.save(); + // todo: add message + return Command.SINGLE_SUCCESS; + })) + .executes(context -> { + if (!(context.getSource().getSender() instanceof Player player)) { + plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); + } else player.getWorld().save(); + // todo: add message + return Command.SINGLE_SUCCESS; + }); + } +} From 3a9382c79590e1ca4b02a2729b4f1acbebf7f7eb Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:42:14 +0200 Subject: [PATCH 042/182] Add WorldSaveOffCommand command to disable saving --- ...xportCommand.java => WorldSaveOffCommand.java} | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) rename plugin/src/main/java/net/thenextlvl/worlds/command/{WorldExportCommand.java => WorldSaveOffCommand.java} (59%) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java similarity index 59% rename from plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java rename to plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java index 2b0e34cb..ecb9db51 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldExportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java @@ -4,21 +4,20 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") -class WorldExportCommand { +class WorldSaveOffCommand { private final WorldsPlugin plugin; ArgumentBuilder create() { - return Commands.literal("export") - .requires(source -> source.getSender().hasPermission("worlds.command.export")) - .then(Commands.argument("world", ArgumentTypes.world()) - .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + return Commands.literal("save-off") + .requires(source -> source.getSender().hasPermission("worlds.command.save-off")) + .executes(context -> { + // todo: save-off + return Command.SINGLE_SUCCESS; + }); } } From 6bbed42880444cece1b188d549dd04fe256abaf0 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:42:30 +0200 Subject: [PATCH 043/182] Add WorldSaveOnCommand to enable world saving --- .../worlds/command/WorldSaveOnCommand.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java new file mode 100644 index 00000000..50f833ed --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java @@ -0,0 +1,23 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldSaveOnCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("save-on") + .requires(source -> source.getSender().hasPermission("worlds.command.save-on")) + .executes(context -> { + // todo: save-on + return Command.SINGLE_SUCCESS; + }); + } +} From 5297167afb4d5420f900cc275a0c21dc71ffebc5 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:42:57 +0200 Subject: [PATCH 044/182] Implement setspawn logic --- .../worlds/command/WorldSetSpawnCommand.java | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java index c15e6b1f..5c073315 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSetSpawnCommand.java @@ -1,11 +1,17 @@ package net.thenextlvl.worlds.command; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.World; +import org.bukkit.command.CommandSender; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -15,8 +21,44 @@ class WorldSetSpawnCommand { ArgumentBuilder create() { return Commands.literal("setspawn") .requires(source -> source.getSender().hasPermission("worlds.command.setspawn")) + .then(Commands.argument("position", ArgumentTypes.blockPosition()) + .then(Commands.argument("angle", FloatArgumentType.floatArg(-180, 180)) + .executes(context -> { + var angle = context.getArgument("angle", float.class); + var resolver = context.getArgument("position", BlockPositionResolver.class); + var position = resolver.resolve(context.getSource()); + return setSpawn(context.getSource().getSender(), + context.getSource().getLocation().getWorld(), + position.blockX(), position.blockY(), position.blockZ(), angle + ); + })) + .executes(context -> { + var resolver = context.getArgument("position", BlockPositionResolver.class); + var position = resolver.resolve(context.getSource()); + return setSpawn(context.getSource().getSender(), + context.getSource().getLocation().getWorld(), + position.blockX(), position.blockY(), position.blockZ(), 0 + ); + })) .executes(context -> { - return Command.SINGLE_SUCCESS; + var location = context.getSource().getLocation(); + return setSpawn(context.getSource().getSender(), + location.getWorld(), + location.blockX(), + location.blockY(), + location.blockZ(), 0 + ); }); } + + private int setSpawn(CommandSender sender, World world, int x, int y, int z, float angle) { + var success = world.setSpawnLocation(x, y, z, angle); + var message = success ? "world.spawn.set.success" : "world.spawn.set.failed"; + plugin.bundle().sendMessage(sender, message, + Placeholder.parsed("x", String.valueOf(x)), + Placeholder.parsed("y", String.valueOf(y)), + Placeholder.parsed("z", String.valueOf(z)), + Placeholder.parsed("angle", String.valueOf(angle))); + return success ? Command.SINGLE_SUCCESS : 0; + } } From 4d21c21b6abeeaaea6caf0adc0c9a13200058f49 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:43:37 +0200 Subject: [PATCH 045/182] Restrict 'spawn' command to players and implement teleport. --- .../net/thenextlvl/worlds/command/WorldSpawnCommand.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java index d13b7336..a1f5466d 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java @@ -6,6 +6,9 @@ import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.entity.Player; + +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -14,8 +17,12 @@ class WorldSpawnCommand { ArgumentBuilder create() { return Commands.literal("spawn") - .requires(source -> source.getSender().hasPermission("worlds.command.spawn")) + .requires(source -> source.getSender().hasPermission("worlds.command.spawn") + && source.getSender() instanceof Player) .executes(context -> { + var player = (Player) context.getSource().getSender(); + player.teleportAsync(player.getWorld().getSpawnLocation(), COMMAND); + // todo: add message return Command.SINGLE_SUCCESS; }); } From 5274ef68e4223208ca4cd5587257488cfd53b099 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:54:04 +0200 Subject: [PATCH 046/182] Add entity teleportation support to WorldTeleportCommand --- .../worlds/command/WorldTeleportCommand.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java index ccae362f..f23b44df 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java @@ -5,8 +5,15 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,7 +24,27 @@ class WorldTeleportCommand { return Commands.literal("teleport") .requires(source -> source.getSender().hasPermission("worlds.command.teleport")) .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("entities", ArgumentTypes.entities()) + .executes(context -> { + var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); + var world = context.getArgument("world", World.class); + var resolved = entities.resolve(context.getSource()); + // todo: add messages + resolved.forEach(entity -> { + entity.teleportAsync(world.getSpawnLocation(), COMMAND); + }); + return Command.SINGLE_SUCCESS; + })) .executes(context -> { + if (!(context.getSource().getSender() instanceof Player player)) { + plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); + return 0; + } + var world = context.getArgument("world", World.class); + player.teleportAsync(world.getSpawnLocation(), COMMAND); + plugin.bundle().sendMessage(player, "world.teleport.player.self", + Placeholder.parsed("world", world.key().asString())); return Command.SINGLE_SUCCESS; })); } From 5911d8642a1579356f6b8d11d043a371b45bc712 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:54:10 +0200 Subject: [PATCH 047/182] Add world name suggestions to unload command --- .../java/net/thenextlvl/worlds/command/WorldUnloadCommand.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java index c38fbe44..21b6a5fc 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -7,6 +7,7 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,6 +18,7 @@ class WorldUnloadCommand { return Commands.literal("unload") .requires(source -> source.getSender().hasPermission("worlds.command.unload")) .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) .executes(context -> { return Command.SINGLE_SUCCESS; })); From b1b58a5abc562172ab6ec6e440fcca8c9e500b4e Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:54:25 +0200 Subject: [PATCH 048/182] Add ServerListener to handle server load events --- .../worlds/listener/ServerListener.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java new file mode 100644 index 00000000..86ae3b28 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java @@ -0,0 +1,27 @@ +package net.thenextlvl.worlds.listener; + +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerLoadEvent; + +@RequiredArgsConstructor +public class ServerListener implements Listener { + private final WorldsPlugin plugin; + + @EventHandler(priority = EventPriority.MONITOR) + public void onServerLoad(ServerLoadEvent event) { + if (!event.getType().equals(ServerLoadEvent.LoadType.STARTUP)) return; + plugin.levelView().listOverworldLevels() + .filter(plugin.levelView()::canLoad) + .forEach(plugin.levelView()::loadOverworldLevel); + plugin.levelView().listNetherLevels() + .filter(plugin.levelView()::canLoad) + .forEach(plugin.levelView()::loadNetherLevel); + plugin.levelView().listEndLevels() + .filter(plugin.levelView()::canLoad) + .forEach(plugin.levelView()::loadEndLevel); + } +} From 47897f69c00a7564a0fcd9685859864767112796 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:54:35 +0200 Subject: [PATCH 049/182] Switch to handling WorldSaveEvent in WorldListener --- .../java/net/thenextlvl/worlds/listener/WorldListener.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java index 37f8ae26..22aff577 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java @@ -5,14 +5,14 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.event.world.WorldSaveEvent; @RequiredArgsConstructor public class WorldListener implements Listener { private final WorldsPlugin plugin; @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onWorldUnload(WorldUnloadEvent event) { - plugin.linkRegistry().unregisterAll(event.getWorld()); + public void onWorldSave(WorldSaveEvent event) { + plugin.persistWorld(event.getWorld()); } } From c0b90f880fbc0acdf4d4ad377ea29cfcee6da80a Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:54:42 +0200 Subject: [PATCH 050/182] Add 'enabled' field to LevelExtras class --- .../src/main/java/net/thenextlvl/worlds/model/LevelExtras.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java index 2c9f8018..88901b60 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java @@ -5,6 +5,7 @@ public record LevelExtras( @Nullable NamespacedKey key, - boolean autoSave + boolean autoSave, + boolean enabled ) { } From 28aa1a09bef0c033720cac8b0e1fb1a48be04d3c Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:55:30 +0200 Subject: [PATCH 051/182] Add `level.dat_old`fallback - Implement world auto-save and enable checks - Enhance world creation with additional settings --- .../net/thenextlvl/worlds/view/LevelView.java | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 41539120..04601f7e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -1,6 +1,7 @@ package net.thenextlvl.worlds.view; import core.io.IO; +import core.io.PathIO; import core.nbt.file.NBTFile; import core.nbt.tag.CompoundTag; import core.nbt.tag.StringTag; @@ -12,7 +13,10 @@ import net.thenextlvl.worlds.preset.Layer; import net.thenextlvl.worlds.preset.Preset; import net.thenextlvl.worlds.preset.Structure; -import org.bukkit.*; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -30,7 +34,8 @@ public Stream listLevels() { return Optional.ofNullable(plugin.getServer().getWorldContainer() .listFiles(File::isDirectory)).stream() .flatMap(files -> Arrays.stream(files).filter(level -> - new File(level, "level.dat").isFile())); + new File(level, "level.dat").isFile() || + new File(level, "level.dat_old").isFile())); } public Stream listOverworldLevels() { @@ -40,7 +45,9 @@ public Stream listOverworldLevels() { } public boolean canLoad(File level) { - return Bukkit.getWorld(level.getName()) == null; + return plugin.getServer().getWorlds().stream() + .map(World::getWorldFolder) + .noneMatch(level::equals); } public Stream listNetherLevels() { @@ -64,9 +71,17 @@ public Stream listEndLevels() { } private @Nullable World loadLevel(File level, World.Environment environment) { - var root = new NBTFile<>(IO.of(level, "level.dat"), new CompoundTag()).getRoot(); + var root = new NBTFile<>(Optional.of( + IO.of(level, "level.dat") + ).filter(PathIO::exists).orElseGet(() -> + IO.of(level, "level.dat_old") + ), new CompoundTag()).getRoot(); var data = root.getAsCompound("Data"); + var extras = readExtras(data); + + if (extras.filter(LevelExtras::enabled).isEmpty()) return null; + var settings = data.getAsCompound("WorldGenSettings"); var dimensions = settings.getAsCompound("dimensions"); @@ -100,10 +115,12 @@ public Stream listEndLevels() { .orElseThrow(() -> new NoSuchElementException("generate_features")) .getAsBoolean(); - var extras = readExtras(root); + var key = extras.map(LevelExtras::key).orElseGet(() -> { + var namespace = level.getName().toLowerCase().replace(" ", "_"); + return new NamespacedKey("worlds", namespace); + }); - var namespace = level.getName().toLowerCase().replace(" ", "_"); - var creator = new WorldCreator(level.getName(), new NamespacedKey("worlds", namespace)) + var creator = new WorldCreator(level.getName(), key) .environment(environment) .generateStructures(structures) .hardcore(hardcore) @@ -114,11 +131,31 @@ public Stream listEndLevels() { Preset.serialize(generatorSettings).toString() ); - return creator.createWorld(); + + var world = creator.createWorld(); + if (world == null) return null; + + extras.map(LevelExtras::autoSave).ifPresent(world::setAutoSave); + + return world; } - private LevelExtras readExtras(CompoundTag root) { - return null; + private Optional readExtras(CompoundTag data) { + return data.optional("BukkitValues") + .map(Tag::getAsCompound) + .map(values -> { + var key = values.optional("worlds:world_key") + .map(Tag::getAsString) + .map(NamespacedKey::fromString) + .orElse(null); + var autoSave = values.optional("worlds:auto_save") + .map(Tag::getAsBoolean) + .orElse(true); + var enabled = values.optional("worlds:enabled") + .map(Tag::getAsBoolean) + .orElse(true); + return new LevelExtras(key, autoSave, enabled); + }); } private Preset readSettings(CompoundTag generator) { From 551f83473055bb15d502933b862d2c0f84f716a2 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:55:44 +0200 Subject: [PATCH 052/182] Refactor world persistence and service registration logic --- .../net/thenextlvl/worlds/WorldsPlugin.java | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index df9ffd45..76ba8ead 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -9,28 +9,29 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.thenextlvl.worlds.command.WorldCommand; -import net.thenextlvl.worlds.link.CraftLinkRegistry; -import net.thenextlvl.worlds.link.LinkRegistry; import net.thenextlvl.worlds.listener.PortalListener; +import net.thenextlvl.worlds.listener.ServerListener; import net.thenextlvl.worlds.listener.WorldListener; import net.thenextlvl.worlds.preset.Presets; +import net.thenextlvl.worlds.version.PluginVersionChecker; import net.thenextlvl.worlds.view.LevelView; import org.bstats.bukkit.Metrics; -import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.World; import org.bukkit.entity.Player; -import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; import java.util.Locale; +import static org.bukkit.persistence.PersistentDataType.BOOLEAN; +import static org.bukkit.persistence.PersistentDataType.STRING; + @Getter @Accessors(fluent = true) @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault public class WorldsPlugin extends JavaPlugin { - private final CraftLinkRegistry linkRegistry = new CraftLinkRegistry(this); - private final LevelView levelView = new LevelView(this); private final File presetsFolder = new File(getDataFolder(), "presets"); @@ -45,11 +46,13 @@ public class WorldsPlugin extends JavaPlugin { Placeholder.component("prefix", bundle.component(Locale.US, "prefix")) )).build()); + private final PluginVersionChecker versionChecker = new PluginVersionChecker(this); private final Metrics metrics = new Metrics(this, 19652); @Override public void onLoad() { - Bukkit.getServicesManager().register(LinkRegistry.class, linkRegistry(), this, ServicePriority.Highest); + registerServices(); + if (presetsFolder().list() == null) saveDefaultPresets(); versionChecker().checkVersion(); @@ -57,43 +60,43 @@ public void onLoad() { @Override public void onEnable() { - levelView().listOverworldLevels() - .filter(levelView()::canLoad) - .forEach(levelView()::loadOverworldLevel); - levelView().listNetherLevels() - .filter(levelView()::canLoad) - .forEach(levelView()::loadNetherLevel); - levelView().listEndLevels() - .filter(levelView()::canLoad) - .forEach(levelView()::loadEndLevel); - - linkRegistry().loadLinks(); registerListeners(); registerCommands(); } @Override public void onDisable() { - Bukkit.getWorlds().stream() - .map(imageProvider()::get) - .filter(Objects::nonNull) - .forEach(image -> { - var deletionType = image.getWorldImage().deletionType(); - if (deletionType != null) { - image.getWorld().getPlayers().forEach(player -> player.kick(Bukkit.shutdownMessage())); - image.deleteNow(deletionType.keepImage(), false); - } else if (!image.getWorldImage().autoSave()) { - image.getWorld().getPlayers().forEach(player -> player.kick(Bukkit.shutdownMessage())); - image.unload(); - } - }); - linkRegistry().saveLinks(); metrics().shutdown(); + persistWorlds(); + unloadWorlds(); + } + + private void unloadWorlds() { + getServer().getWorlds().stream().filter(world -> !world.isAutoSave()).forEach(world -> { + world.getPlayers().forEach(player -> player.kick(getServer().shutdownMessage())); + getServer().unloadWorld(world, false); + }); + } + + private void persistWorlds() { + getServer().getWorlds().forEach(this::persistWorld); + } + + public void persistWorld(World world) { + var container = world.getPersistentDataContainer(); + container.set(new NamespacedKey("worlds", "world_key"), STRING, world.getKey().asString()); + container.set(new NamespacedKey("worlds", "auto_save"), BOOLEAN, world.isAutoSave()); + container.set(new NamespacedKey("worlds", "enabled"), BOOLEAN, true); + } + + private void registerServices() { + // getServer().getServicesManager().register(LinkRegistry.class, linkRegistry(), this, ServicePriority.Highest); } private void registerListeners() { - Bukkit.getPluginManager().registerEvents(new PortalListener(this), this); - Bukkit.getPluginManager().registerEvents(new WorldListener(this), this); + getServer().getPluginManager().registerEvents(new PortalListener(this), this); + getServer().getPluginManager().registerEvents(new ServerListener(this), this); + getServer().getPluginManager().registerEvents(new WorldListener(this), this); } private void registerCommands() { From 3ba689da588206d56acd01fe5ce4c3e440c28240 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:55:54 +0200 Subject: [PATCH 053/182] Change plugin load order to STARTUP in build.gradle.kts --- plugin/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index e0f03101..937cb5e0 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -59,7 +59,7 @@ paper { main = "net.thenextlvl.worlds.WorldsPlugin" apiVersion = "1.20" description = "Create, delete and manage your worlds" - load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD + load = BukkitPluginDescription.PluginLoadOrder.STARTUP website = "https://thenextlvl.net" authors = listOf("NonSwag") From 6304b7f3f24b41572332684c3403978370ea8198 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:56:03 +0200 Subject: [PATCH 054/182] Remove unused PortalType field from Link record --- api/src/main/java/net/thenextlvl/worlds/link/Link.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/link/Link.java b/api/src/main/java/net/thenextlvl/worlds/link/Link.java index 4bc992f8..793e1a1a 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/Link.java +++ b/api/src/main/java/net/thenextlvl/worlds/link/Link.java @@ -1,16 +1,14 @@ package net.thenextlvl.worlds.link; import com.google.gson.annotations.SerializedName; -import org.bukkit.PortalType; import org.bukkit.World; public record Link( - @SerializedName("portal") PortalType portalType, @SerializedName("source") World source, @SerializedName("destination") World destination ) { @Override public String toString() { - return portalType.name().toLowerCase() + ": " + source.getName() + " -> " + destination.getName(); + return source.key().asMinimalString() + " -> " + destination.key().asMinimalString(); } } From fe504412f3a30f5c589cc1ca7029b5a4004b12b3 Mon Sep 17 00:00:00 2001 From: david Date: Sat, 3 Aug 2024 22:56:09 +0200 Subject: [PATCH 055/182] Replace Stream with Set in LinkRegistry interface. --- .../main/java/net/thenextlvl/worlds/link/LinkRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java b/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java index ccfe7ae7..37d8d16e 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java +++ b/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java @@ -2,10 +2,10 @@ import org.bukkit.World; -import java.util.stream.Stream; +import java.util.Set; public interface LinkRegistry { - Stream getLinks(); + Set getLinks(); boolean isRegistered(Link link); From 2b2b521a2124eb09bed884cc8b3156c68124e29f Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 09:32:16 +0200 Subject: [PATCH 056/182] Disallow unloading overworld in properties files. --- plugin/src/main/resources/worlds.properties | 3 ++- plugin/src/main/resources/worlds_german.properties | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index f31d667e..ee71e586 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -24,10 +24,11 @@ world.info.type= Type: world.info.environment= Environment: world.info.generator= Generator: world.info.seed= Seed: '>'> -world.delete.disallowed= The world can only be scheduled for deletion +world.delete.disallowed= The overworld can only be scheduled for deletion world.delete.success= Successfully deleted the world world.unload.failed= Failed to unload the world world.unload.success= Successfully unloaded the world +world.unload.disallowed= The overworld cannot be unloaded world.delete.nothing= There is nothing to delete world.delete.failed= Failed to delete the world world.delete.scheduled= The world will be deleted on the next restart diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 0cdd53fc..8adbb92e 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -23,10 +23,11 @@ world.info.environment= Umfeld: world.info.generator= Generator: world.info.seed= Startwert: '>'> world.name.absent= Du musst eine Welt angeben -world.delete.disallowed= Die Welt kann nur zum Löschen eingeplant werden +world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden world.delete.success= Die Welt wurde erfolgreich gelöscht world.unload.failed= Die Welt konnte nicht entladen werden world.unload.success= Die Welt wurde erfolgreich entladen +world.unload.disallowed= Die Oberwelt kann nicht entladen werden world.delete.failed= Die Welt konnte nicht gelöscht werden world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht image.delete.failed= Das Abbild konnte nicht gelöscht werden From 0eeaeb253fe04fd4beb7f2b4f677b69220ad3fd6 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 09:32:27 +0200 Subject: [PATCH 057/182] Rename and refactor DeleteResult; add new unload statuses --- .../thenextlvl/worlds/image/DeleteResult.java | 17 ----------------- .../worlds/model/WorldActionResult.java | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java diff --git a/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java b/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java deleted file mode 100644 index a76ffac1..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/DeleteResult.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.thenextlvl.worlds.image; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public -enum DeleteResult { - EXEMPTED("world.delete.disallowed"), - SCHEDULED("world.delete.scheduled"), - FAILED("world.delete.failed"), - SUCCESS("world.delete.success"), - UNLOAD_FAILED("world.unload.failed"); - - private final String message; -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java b/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java new file mode 100644 index 00000000..ccf98c8c --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java @@ -0,0 +1,19 @@ +package net.thenextlvl.worlds.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public +enum WorldActionResult { + DELETE_EXEMPTED("world.delete.disallowed"), + DELETE_SCHEDULED("world.delete.scheduled"), + DELETE_FAILED("world.delete.failed"), + DELETE_SUCCESS("world.delete.success"), + UNLOAD_EXEMPTED("world.unload.disallowed"), + UNLOAD_SUCCESS("world.unload.success"), + UNLOAD_FAILED("world.unload.failed"); + + private final String message; +} From 0ccab7bfa25cd1d91aeb0892437e7117fa788a5f Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 10:30:09 +0200 Subject: [PATCH 058/182] Refactor delete method return type to WorldActionResult --- .../worlds/command/WorldDeleteCommand.java | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java index f30fadbb..4bf6d8f3 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java @@ -11,7 +11,7 @@ import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.argument.CommandFlagsArgument; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.image.DeleteResult; +import net.thenextlvl.worlds.model.WorldActionResult; import org.bukkit.World; import java.io.File; @@ -51,41 +51,31 @@ private int delete(CommandContext context) { return Command.SINGLE_SUCCESS; } - private DeleteResult delete(World world, boolean schedule) { + private WorldActionResult delete(World world, boolean schedule) { return schedule ? scheduleDeletion(world) : deleteNow(world); } - private boolean canUnload(World world) { - return /*!Bukkit.isTickingWorlds() && */world.getPlayers().isEmpty(); - } - - private boolean isDeletable(World world) { - return !world.getKey().toString().equals("minecraft:overworld"); - } - - private boolean unload(World world) { - return canUnload(world) && plugin.getServer().unloadWorld(world, false); - } - - private DeleteResult deleteNow(World world) { - if (!isDeletable(world)) return DeleteResult.EXEMPTED; + private WorldActionResult deleteNow(World world) { + if (world.getKey().toString().equals("minecraft:overworld")) + return WorldActionResult.DELETE_EXEMPTED; var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); world.getPlayers().forEach(player -> player.teleport(fallback)); - if (!unload(world)) return DeleteResult.UNLOAD_FAILED; + if (!plugin.getServer().unloadWorld(world, false)) + return WorldActionResult.UNLOAD_FAILED; return delete(world.getWorldFolder()) - ? DeleteResult.SUCCESS - : DeleteResult.FAILED; + ? WorldActionResult.DELETE_SUCCESS + : WorldActionResult.DELETE_FAILED; } - private DeleteResult scheduleDeletion(World world) { + private WorldActionResult scheduleDeletion(World world) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (delete(world.getWorldFolder())) return; plugin.getComponentLogger().error("Failed to delete world {}", world.getName()); })); - return DeleteResult.SCHEDULED; + return WorldActionResult.DELETE_SCHEDULED; } private boolean delete(File file) { From 048f4b6dfd3ba4e7312f319bac7b3c519f24de48 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 10:30:17 +0200 Subject: [PATCH 059/182] Add fallback world option to WorldUnloadCommand --- .../worlds/command/WorldUnloadCommand.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java index 21b6a5fc..88d571d0 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -6,8 +6,12 @@ import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import net.thenextlvl.worlds.model.WorldActionResult; +import org.bukkit.World; +import org.jetbrains.annotations.Nullable; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -19,8 +23,35 @@ class WorldUnloadCommand { .requires(source -> source.getSender().hasPermission("worlds.command.unload")) .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("fallback", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .executes(context -> { + var world = context.getArgument("world", World.class); + var fallback = context.getArgument("fallback", World.class); + var message = unload(world, fallback).getMessage(); + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("world", world.key().asString())); + return Command.SINGLE_SUCCESS; + })) .executes(context -> { + var world = context.getArgument("world", World.class); + var message = unload(world, null).getMessage(); + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("world", world.key().asString())); return Command.SINGLE_SUCCESS; })); } + + private WorldActionResult unload(World world, @Nullable World fallback) { + if (world.getKey().toString().equals("minecraft:overworld")) + return WorldActionResult.UNLOAD_EXEMPTED; + + var fallbackSpawn = fallback != null ? fallback.getSpawnLocation() + : plugin.getServer().getWorlds().getFirst().getSpawnLocation(); + world.getPlayers().forEach(player -> player.teleport(fallbackSpawn)); + + return plugin.getServer().unloadWorld(world, world.isAutoSave()) + ? WorldActionResult.UNLOAD_SUCCESS + : WorldActionResult.UNLOAD_FAILED; + } } From f5532b7ff9357f18d2ed1132ef399f87a073cef5 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 10:47:27 +0200 Subject: [PATCH 060/182] Remove WorldActionResult class and update related logic --- .../worlds/command/WorldDeleteCommand.java | 19 ++++++++----------- .../worlds/command/WorldUnloadCommand.java | 14 +++++++------- .../worlds/model/WorldActionResult.java | 19 ------------------- 3 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java index 4bf6d8f3..cdaa74e4 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java @@ -11,7 +11,6 @@ import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.argument.CommandFlagsArgument; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.model.WorldActionResult; import org.bukkit.World; import java.io.File; @@ -46,36 +45,34 @@ private int delete(CommandContext context) { if (!flags.contains("--confirm")) return confirmationNeeded(context); var world = context.getArgument("world", World.class); var result = delete(world, flags.contains("--schedule")); - plugin.bundle().sendMessage(context.getSource().getSender(), result.getMessage(), + plugin.bundle().sendMessage(context.getSource().getSender(), result, Placeholder.parsed("world", world.key().asString())); return Command.SINGLE_SUCCESS; } - private WorldActionResult delete(World world, boolean schedule) { + private String delete(World world, boolean schedule) { return schedule ? scheduleDeletion(world) : deleteNow(world); } - private WorldActionResult deleteNow(World world) { + private String deleteNow(World world) { if (world.getKey().toString().equals("minecraft:overworld")) - return WorldActionResult.DELETE_EXEMPTED; + return "world.delete.disallowed"; var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); world.getPlayers().forEach(player -> player.teleport(fallback)); if (!plugin.getServer().unloadWorld(world, false)) - return WorldActionResult.UNLOAD_FAILED; + return "world.unload.failed"; - return delete(world.getWorldFolder()) - ? WorldActionResult.DELETE_SUCCESS - : WorldActionResult.DELETE_FAILED; + return delete(world.getWorldFolder()) ? "world.delete.success" : "world.delete.failed"; } - private WorldActionResult scheduleDeletion(World world) { + private String scheduleDeletion(World world) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (delete(world.getWorldFolder())) return; plugin.getComponentLogger().error("Failed to delete world {}", world.getName()); })); - return WorldActionResult.DELETE_SCHEDULED; + return "world.delete.scheduled"; } private boolean delete(File file) { diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java index 88d571d0..fe6cafae 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -9,7 +9,6 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.model.WorldActionResult; import org.bukkit.World; import org.jetbrains.annotations.Nullable; @@ -28,30 +27,31 @@ class WorldUnloadCommand { .executes(context -> { var world = context.getArgument("world", World.class); var fallback = context.getArgument("fallback", World.class); - var message = unload(world, fallback).getMessage(); + var message = unload(world, fallback); plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world.key().asString())); return Command.SINGLE_SUCCESS; })) .executes(context -> { var world = context.getArgument("world", World.class); - var message = unload(world, null).getMessage(); + var message = unload(world, null); plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world.key().asString())); return Command.SINGLE_SUCCESS; })); } - private WorldActionResult unload(World world, @Nullable World fallback) { + private String unload(World world, @Nullable World fallback) { + if (world.equals(fallback)) return "world.unload.fallback"; if (world.getKey().toString().equals("minecraft:overworld")) - return WorldActionResult.UNLOAD_EXEMPTED; + return "world.unload.disallowed"; var fallbackSpawn = fallback != null ? fallback.getSpawnLocation() : plugin.getServer().getWorlds().getFirst().getSpawnLocation(); world.getPlayers().forEach(player -> player.teleport(fallbackSpawn)); return plugin.getServer().unloadWorld(world, world.isAutoSave()) - ? WorldActionResult.UNLOAD_SUCCESS - : WorldActionResult.UNLOAD_FAILED; + ? "world.unload.success" + : "world.unload.failed"; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java b/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java deleted file mode 100644 index ccf98c8c..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/model/WorldActionResult.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.thenextlvl.worlds.model; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public -enum WorldActionResult { - DELETE_EXEMPTED("world.delete.disallowed"), - DELETE_SCHEDULED("world.delete.scheduled"), - DELETE_FAILED("world.delete.failed"), - DELETE_SUCCESS("world.delete.success"), - UNLOAD_EXEMPTED("world.unload.disallowed"), - UNLOAD_SUCCESS("world.unload.success"), - UNLOAD_FAILED("world.unload.failed"); - - private final String message; -} From 7826a867796c1e07e88e674f4a1c1891444e4acd Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 10:49:05 +0200 Subject: [PATCH 061/182] Add message for unload fallback error in properties files --- plugin/src/main/resources/worlds.properties | 1 + plugin/src/main/resources/worlds_german.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index ee71e586..50ae30dd 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -29,6 +29,7 @@ world.delete.success= Successfully deleted the world Failed to unload the world world.unload.success= Successfully unloaded the world world.unload.disallowed= The overworld cannot be unloaded +world.unload.fallback= The fallback and target world cannot match world.delete.nothing= There is nothing to delete world.delete.failed= Failed to delete the world world.delete.scheduled= The world will be deleted on the next restart diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 8adbb92e..8a0bd4ef 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -28,6 +28,7 @@ world.delete.success= Die Welt wurde erfolg world.unload.failed= Die Welt konnte nicht entladen werden world.unload.success= Die Welt wurde erfolgreich entladen world.unload.disallowed= Die Oberwelt kann nicht entladen werden +world.unload.fallback= Die zu löschende und Rückfallwelt können nicht die gleiche sein world.delete.failed= Die Welt konnte nicht gelöscht werden world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht image.delete.failed= Das Abbild konnte nicht gelöscht werden From 8283232604e15fddb353946ecfac529716ad5f99 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 11:04:44 +0200 Subject: [PATCH 062/182] Implement world listing with interactive hover and click actions --- .../worlds/command/WorldListCommand.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java index 3ed1956a..ca1bfd1d 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldListCommand.java @@ -2,10 +2,18 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.Keyed; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -15,8 +23,26 @@ class WorldListCommand { ArgumentBuilder create() { return Commands.literal("list") .requires(source -> source.getSender().hasPermission("worlds.command.list")) - .executes(context -> { - return Command.SINGLE_SUCCESS; - }); + .executes(this::list); + } + + private int list(CommandContext context) { + var sender = context.getSource().getSender(); + var worlds = plugin.getServer().getWorlds().stream() + .map(Keyed::key) + .map(Key::asString) + .toList(); + + var joined = Component.join(JoinConfiguration.commas(true), worlds.stream() + .map(world -> Component.text(world) + .hoverEvent(HoverEvent.showText(plugin.bundle().component(sender, + "world.list.hover", Placeholder.parsed("world", world)))) + .clickEvent(ClickEvent.runCommand("/world teleport " + world))) + .toList()); + plugin.bundle().sendMessage(sender, "world.list", + Placeholder.parsed("amount", String.valueOf(worlds.size())), + Placeholder.component("worlds", joined)); + + return Command.SINGLE_SUCCESS; } } From 514795b712581a034ed1f5e43772aa842bd62223 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:39:32 +0200 Subject: [PATCH 063/182] Add detailed world info command to display world data --- .../worlds/command/WorldInfoCommand.java | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index 9d3893cf..15124c9b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -2,12 +2,20 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import core.nbt.tag.CompoundTag; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Optional; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -20,7 +28,49 @@ class WorldInfoCommand { .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + var world = context.getArgument("world", World.class); + return list(context.getSource().getSender(), world); + })) + .executes(context -> { + if (!(context.getSource().getSender() instanceof Player player)) { + plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); + return 0; + } else return list(context.getSource().getSender(), player.getWorld()); + }); + } + + private int list(CommandSender sender, World world) { + var root = plugin.levelView().getLevelDataFile(world.getWorldFolder()).getRoot(); + var data = root.optional("Data"); + var settings = data.flatMap(tag -> tag.optional("WorldGenSettings")); + var dimensions = settings.flatMap(tag -> tag.optional("dimensions")); + var dimension = dimensions.flatMap(tag -> tag.optional( + plugin.levelView().getDimension(tag, world.getEnvironment()))); + var generator = dimension.flatMap(tag -> tag.optional("generator")); + + var environment = dimensions.map(tag -> plugin.levelView().getDimension(tag, world.getEnvironment())); + var type = generator.flatMap(plugin.levelView()::getGeneratorType); + + plugin.bundle().sendMessage(sender, "world.info.name", + Placeholder.parsed("world", world.key().asString())); + plugin.bundle().sendMessage(sender, "world.info.players", + Placeholder.parsed("players", String.valueOf(world.getPlayers().size()))); + plugin.bundle().sendMessage(sender, "world.info.type", + Placeholder.parsed("type", type.orElse("unknown"))); + plugin.bundle().sendMessage(sender, "world.info.dimension", + Placeholder.parsed("dimension", environment.orElse("unknown"))); + getGenerator(world).ifPresent(gen -> plugin.bundle().sendMessage(sender, + "world.info.generator", Placeholder.parsed("generator", gen))); + plugin.bundle().sendMessage(sender, "world.info.seed", + Placeholder.parsed("seed", String.valueOf(world.getSeed()))); + return Command.SINGLE_SUCCESS; + } + + private Optional getGenerator(World world) { + if (world.getGenerator() == null) return Optional.empty(); + var loader = world.getGenerator().getClass().getClassLoader(); + if (!(loader instanceof ConfiguredPluginClassLoader pluginLoader)) return Optional.empty(); + if (pluginLoader.getPlugin() == null) return Optional.empty(); + return Optional.of(pluginLoader.getPlugin().getName()); } } From 137f3267fcafa383d6fa296001911c2c529fdb9a Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:39:37 +0200 Subject: [PATCH 064/182] Update 'environment' to 'dimension' in world info properties --- plugin/src/main/resources/worlds.properties | 2 +- plugin/src/main/resources/worlds_german.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 50ae30dd..3c9a1002 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -21,7 +21,7 @@ world.spawn.set.failed= Failed to change world spawn world.info.name= Name: world.info.players= Players: world.info.type= Type: -world.info.environment= Environment: +world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Seed: '>'> world.delete.disallowed= The overworld can only be scheduled for deletion diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 8a0bd4ef..313a351e 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -19,7 +19,7 @@ world.spawn.set.failed= Der spawn konnte nicht neu gesetzt werden world.info.name= Name: world.info.players= Spieler: world.info.type= Typ: -world.info.environment= Umfeld: +world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Startwert: '>'> world.name.absent= Du musst eine Welt angeben From 60d530e3f5afc63d0f3e6d0ec0ec036da4ecd94b Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:39:44 +0200 Subject: [PATCH 065/182] Remove unused 'of' method from Generator class --- .../java/net/thenextlvl/worlds/image/Generator.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java b/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java index d93516c6..e1e6514c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java @@ -1,22 +1,10 @@ package net.thenextlvl.worlds.image; -import org.bukkit.World; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.PluginClassLoader; import org.jetbrains.annotations.Nullable; public record Generator(String plugin, @Nullable String id) { - @Nullable - @SuppressWarnings("UnstableApiUsage") - public static Generator of(World world) { - if (world.getGenerator() == null) return null; - var loader = world.getGenerator().getClass().getClassLoader(); - if (!(loader instanceof PluginClassLoader pluginLoader)) return null; - if (pluginLoader.getPlugin() == null) return null; - return new Generator(pluginLoader.getPlugin().getName(), null); - } - public static boolean hasChunkGenerator(Class clazz) { try { return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); From ac3244f440506f7a373cdfbd21ee5ccfe9adbda4 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:39:54 +0200 Subject: [PATCH 066/182] Refactor LevelView: streamline level loading and add utility methods --- .../net/thenextlvl/worlds/view/LevelView.java | 145 ++++++++++-------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 04601f7e..56d63084 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -3,9 +3,7 @@ import core.io.IO; import core.io.PathIO; import core.nbt.file.NBTFile; -import core.nbt.tag.CompoundTag; -import core.nbt.tag.StringTag; -import core.nbt.tag.Tag; +import core.nbt.tag.*; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.model.LevelExtras; @@ -71,47 +69,28 @@ public Stream listEndLevels() { } private @Nullable World loadLevel(File level, World.Environment environment) { - var root = new NBTFile<>(Optional.of( - IO.of(level, "level.dat") - ).filter(PathIO::exists).orElseGet(() -> - IO.of(level, "level.dat_old") - ), new CompoundTag()).getRoot(); - - var data = root.getAsCompound("Data"); - var extras = readExtras(data); + var data = getLevelDataFile(level).getRoot().optional("Data"); + var extras = data.flatMap(this::getExtras); if (extras.filter(LevelExtras::enabled).isEmpty()) return null; - var settings = data.getAsCompound("WorldGenSettings"); - var dimensions = settings.getAsCompound("dimensions"); + var settings = data.flatMap(tag -> tag.optional("WorldGenSettings")); + var dimensions = settings.flatMap(tag -> tag.optional("dimensions")); + var dimension = dimensions.flatMap(tag -> tag.optional(getDimension(tag, environment))); + var generator = dimension.flatMap(tag -> tag.optional("generator")); - var dimension = dimensions.optional(switch (environment) { - case NORMAL -> "minecraft:overworld"; - case NETHER -> "minecraft:the_nether"; - case THE_END -> "minecraft:the_end"; - case CUSTOM -> throw new UnsupportedOperationException("Custom dimensions are not yet supported"); - }).orElseThrow().getAsCompound(); + var type = generator.flatMap(this::getWorldType); - var generator = dimension.getAsCompound("generator"); - - var type = generator.optional("type") - .map(Tag::getAsString) - .map(string -> switch (string) { - case "minecraft:noise" -> WorldType.NORMAL; - case "minecraft:flat" -> WorldType.FLAT; - case "minecraft:debug" -> throw new IllegalArgumentException("Debug worlds are not yet supported"); - default -> throw new IllegalArgumentException("Unexpected generator type: " + string); - }).orElseThrow(() -> new NoSuchElementException("type")); + var generatorSettings = type.filter(worldType -> worldType.equals(WorldType.FLAT)) + .flatMap(worldType -> generator.flatMap(this::getSettings)); - var generatorSettings = type.equals(WorldType.FLAT) ? readSettings(generator) : null; - - var hardcore = data.optional("hardcore") + var hardcore = data.flatMap(tag -> tag.optional("hardcore")) .orElseThrow(() -> new NoSuchElementException("hardcore")) .getAsBoolean(); - var seed = settings.optional("seed") + var seed = settings.flatMap(tag -> tag.optional("seed")) .orElseThrow(() -> new NoSuchElementException("seed")) .getAsInt(); - var structures = settings.optional("generate_features") + var structures = settings.flatMap(tag -> tag.optional("generate_features")) .orElseThrow(() -> new NoSuchElementException("generate_features")) .getAsBoolean(); @@ -125,11 +104,9 @@ public Stream listEndLevels() { .generateStructures(structures) .hardcore(hardcore) .seed(seed) - .type(type); + .type(type.orElse(WorldType.NORMAL)); - if (generatorSettings != null) creator.generatorSettings( - Preset.serialize(generatorSettings).toString() - ); + generatorSettings.ifPresent(preset -> creator.generator(preset.serialize().toString())); var world = creator.createWorld(); @@ -140,7 +117,7 @@ public Stream listEndLevels() { return world; } - private Optional readExtras(CompoundTag data) { + public Optional getExtras(CompoundTag data) { return data.optional("BukkitValues") .map(Tag::getAsCompound) .map(values -> { @@ -158,39 +135,71 @@ private Optional readExtras(CompoundTag data) { }); } - private Preset readSettings(CompoundTag generator) { - var settings = generator.getAsCompound("settings"); + public Optional getSettings(CompoundTag generator) { + var settings = generator.optional("settings"); - var biome = settings.optional("biome") - .orElseThrow(() -> new NoSuchElementException("biome")) - .getAsString(); - var features = settings.optional("features") - .orElseThrow(() -> new NoSuchElementException("features")) - .getAsBoolean(); - var lakes = settings.optional("lakes") - .orElseThrow(() -> new NoSuchElementException("lakes")) - .getAsBoolean(); + if (settings.isEmpty()) return Optional.empty(); + + var preset = new Preset(); + + settings.flatMap(tag -> tag.optional("biome")) + .map(Tag::getAsString) + .map(Biome::literal) + .ifPresent(preset::biome); + + settings.flatMap(tag -> tag.optional("features")) + .map(Tag::getAsBoolean) + .ifPresent(preset::features); + + settings.flatMap(tag -> tag.optional("lakes")) + .map(Tag::getAsBoolean) + .ifPresent(preset::lakes); - var layers = settings.optional("layers") - .orElseThrow(() -> new NoSuchElementException("layers")) - .getAsList().stream() - .map(layer -> { + settings.flatMap(tag -> tag.optional("layers")) + .map(tag -> tag.getAsList().stream().map(layer -> { var block = layer.optional("block").orElseThrow().getAsString(); var height = layer.optional("height").orElseThrow().getAsInt(); return new Layer(block, height); - }).collect(Collectors.toSet()); - - var structures = settings.optional("structure_overrides") - .orElseThrow(() -> new NoSuchElementException("structure_overrides")) - .getAsList().stream() - .map(structure -> new Structure(structure.getAsString())) - .collect(Collectors.toSet()); - - return new Preset() - .biome(Biome.literal(biome)) - .features(features) - .lakes(lakes) - .layers(layers) - .structures(structures); + }).collect(Collectors.toSet())) + .ifPresent(preset::layers); + + settings.flatMap(tag -> tag.optional("structure_overrides")) + .map(tag -> tag.getAsList().stream() + .map(structure -> new Structure(structure.getAsString())) + .collect(Collectors.toSet())) + .ifPresent(preset::structures); + + return Optional.of(preset); + } + + public NBTFile getLevelDataFile(File level) { + return new NBTFile<>(Optional.of( + IO.of(level, "level.dat") + ).filter(PathIO::exists).orElseGet(() -> + IO.of(level, "level.dat_old") + ), new CompoundTag()); + } + + public String getDimension(CompoundTag dimensions, World.Environment environment) { + return switch (environment) { + case NORMAL -> "minecraft:overworld"; + case NETHER -> "minecraft:the_nether"; + case THE_END -> "minecraft:the_end"; + case CUSTOM -> dimensions.keySet().stream().filter(s -> !s.startsWith("minecraft")).findAny() + .orElseThrow(() -> new UnsupportedOperationException("Could not find custom dimension")); + }; + } + + public Optional getWorldType(CompoundTag generator) { + return getGeneratorType(generator).map(string -> switch (string) { + case "minecraft:noise" -> WorldType.NORMAL; + case "minecraft:flat" -> WorldType.FLAT; + case "minecraft:debug" -> throw new IllegalArgumentException("Debug worlds are not yet supported"); + default -> throw new IllegalArgumentException("Unexpected generator type: " + string); + }); + } + + public Optional getGeneratorType(CompoundTag generator) { + return generator.optional("type").map(Tag::getAsString); } } From 2ec8eb5ba89d6b0876e6cd6f74bc1203f16441da Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:40:27 +0200 Subject: [PATCH 067/182] Remove CustomSyntaxFormatter.java from the codebase --- old/CustomSyntaxFormatter.java | 44 ---------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 old/CustomSyntaxFormatter.java diff --git a/old/CustomSyntaxFormatter.java b/old/CustomSyntaxFormatter.java deleted file mode 100644 index 5da50670..00000000 --- a/old/CustomSyntaxFormatter.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.thenextlvl.worlds.command.old; - -import core.annotation.MethodsReturnNotNullByDefault; -import core.annotation.ParametersAreNotNullByDefault; -import org.incendo.cloud.CommandManager; -import org.incendo.cloud.syntax.StandardCommandSyntaxFormatter; - -@MethodsReturnNotNullByDefault -@ParametersAreNotNullByDefault -public class CustomSyntaxFormatter extends StandardCommandSyntaxFormatter { - public CustomSyntaxFormatter(CommandManager manager) { - super(manager); - } - - @Override - protected FormattingInstance createInstance() { - return new FormattingInstance() { - @Override - public String optionalPrefix() { - return "("; - } - - @Override - public String optionalSuffix() { - return ")"; - } - - @Override - public String requiredPrefix() { - return "["; - } - - @Override - public String requiredSuffix() { - return "]"; - } - - @Override - public void appendPipe() { - appendName(" | "); - } - }; - } -} From 5ba8a94f298c229847267358d4468c63aa4152d8 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 13:41:01 +0200 Subject: [PATCH 068/182] Add instance method for Preset JSON serialization --- .../net/thenextlvl/worlds/preset/Preset.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java index 0f4cd30f..5ed38791 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java @@ -66,6 +66,15 @@ public boolean saveToFile(File file, boolean force) { return true; } + /** + * Serialize this preset into a json object. + * + * @return the serialized preset as a JsonObject + */ + public JsonObject serialize() { + return gson.toJsonTree(this).getAsJsonObject(); + } + private static final Gson gson = new GsonBuilder() .registerTypeAdapter(Structure.class, new StructureTypeAdapter()) .registerTypeAdapter(Material.class, MaterialAdapter.NotNull.INSTANCE) @@ -73,16 +82,6 @@ public boolean saveToFile(File file, boolean force) { .setPrettyPrinting() .create(); - /** - * Serialize a preset into a json object - * - * @param preset the preset to serialize - * @return the serialized preset - */ - public static JsonObject serialize(Preset preset) { - return gson.toJsonTree(preset).getAsJsonObject(); - } - /** * Deserialize a json object into a preset * From d6e904eea0beae45e9c1e38ae879eba440d564b6 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 14:25:43 +0200 Subject: [PATCH 069/182] Simplify level loading mechanism in ServerListener --- .../net/thenextlvl/worlds/listener/ServerListener.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java index 86ae3b28..e84e36b1 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java @@ -14,14 +14,8 @@ public class ServerListener implements Listener { @EventHandler(priority = EventPriority.MONITOR) public void onServerLoad(ServerLoadEvent event) { if (!event.getType().equals(ServerLoadEvent.LoadType.STARTUP)) return; - plugin.levelView().listOverworldLevels() + plugin.levelView().listLevels() .filter(plugin.levelView()::canLoad) - .forEach(plugin.levelView()::loadOverworldLevel); - plugin.levelView().listNetherLevels() - .filter(plugin.levelView()::canLoad) - .forEach(plugin.levelView()::loadNetherLevel); - plugin.levelView().listEndLevels() - .filter(plugin.levelView()::canLoad) - .forEach(plugin.levelView()::loadEndLevel); + .forEach(plugin.levelView()::loadLevel); } } From 4405af797e339d50e8028bc5c40bc7618703573c Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 14:25:55 +0200 Subject: [PATCH 070/182] Refactor unloadWorlds to use dropWhile instead of filter --- plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 76ba8ead..76831795 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -72,7 +72,7 @@ public void onDisable() { } private void unloadWorlds() { - getServer().getWorlds().stream().filter(world -> !world.isAutoSave()).forEach(world -> { + getServer().getWorlds().stream().dropWhile(World::isAutoSave).forEach(world -> { world.getPlayers().forEach(player -> player.kick(getServer().shutdownMessage())); getServer().unloadWorld(world, false); }); From a44c9dd3d290a01de189a4bf2391c4bad4031705 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 14:26:08 +0200 Subject: [PATCH 071/182] use dropWhile instead of filter --- .../worlds/command/argument/CommandFlagsArgument.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java index 09629333..37a179b5 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java @@ -19,7 +19,7 @@ public CommandFlagsArgument(Set flags) { var index = builder.getRemaining().lastIndexOf(' ') + 1; var substring = builder.getRemaining().substring(index); flags.stream() - .filter(flag -> !builder.getRemaining().contains(flag)) + .dropWhile(builder.getRemaining()::contains) .filter(flag -> flag.startsWith(substring)) .forEach(s -> builder.suggest(builder.getRemaining() + s.substring(substring.length()))); return builder.buildFuture(); From b1010b6a76d61cff0c8a202f4906e519b03b2f8c Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 16:01:18 +0200 Subject: [PATCH 072/182] Refactor level handling to unify dimension checks and loading conditions --- .../net/thenextlvl/worlds/view/LevelView.java | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 56d63084..f7a757d8 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -36,10 +36,12 @@ public Stream listLevels() { new File(level, "level.dat_old").isFile())); } - public Stream listOverworldLevels() { - return listLevels() - .filter(level -> !new File(level, "DIM1").exists()) - .filter(level -> !new File(level, "DIM-1").exists()); + public boolean hasNetherDimension(File level) { + return new File(level, "DIM-1").isDirectory(); + } + + public boolean hasEndDimension(File level) { + return new File(level, "DIM1").isDirectory(); } public boolean canLoad(File level) { @@ -48,31 +50,25 @@ public boolean canLoad(File level) { .noneMatch(level::equals); } - public Stream listNetherLevels() { - return listLevels().filter(level -> new File(level, "DIM-1").exists()); - } - - public Stream listEndLevels() { - return listLevels().filter(level -> new File(level, "DIM1").exists()); - } - - public @Nullable World loadOverworldLevel(File level) { - return loadLevel(level, World.Environment.NORMAL); + public World.Environment getEnvironment(File level) { + if (hasEndDimension(level)) return World.Environment.THE_END; + if (hasNetherDimension(level)) return World.Environment.NETHER; + return World.Environment.NORMAL; } - public @Nullable World loadNetherLevel(File level) { - return loadLevel(level, World.Environment.NETHER); + public @Nullable World loadLevel(File level) { + return loadLevel(level, false); } - public @Nullable World loadEndLevel(File level) { - return loadLevel(level, World.Environment.THE_END); + public @Nullable World loadLevel(File level, boolean force) { + return loadLevel(level, getEnvironment(level), force); } - private @Nullable World loadLevel(File level, World.Environment environment) { + public @Nullable World loadLevel(File level, World.Environment environment, boolean force) { var data = getLevelDataFile(level).getRoot().optional("Data"); var extras = data.flatMap(this::getExtras); - if (extras.filter(LevelExtras::enabled).isEmpty()) return null; + if (!force && extras.filter(LevelExtras::enabled).isEmpty()) return null; var settings = data.flatMap(tag -> tag.optional("WorldGenSettings")); var dimensions = settings.flatMap(tag -> tag.optional("dimensions")); @@ -185,7 +181,7 @@ public String getDimension(CompoundTag dimensions, World.Environment environment case NORMAL -> "minecraft:overworld"; case NETHER -> "minecraft:the_nether"; case THE_END -> "minecraft:the_end"; - case CUSTOM -> dimensions.keySet().stream().filter(s -> !s.startsWith("minecraft")).findAny() + case CUSTOM -> dimensions.keySet().stream().dropWhile(s -> s.startsWith("minecraft")).findAny() .orElseThrow(() -> new UnsupportedOperationException("Could not find custom dimension")); }; } From 7eab76a303378721d89b4eef42df9872e83c0934 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 16:01:33 +0200 Subject: [PATCH 073/182] Add LevelSuggestionProvider for dynamic command suggestions --- .../suggestion/LevelSuggestionProvider.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/LevelSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/LevelSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/LevelSuggestionProvider.java new file mode 100644 index 00000000..26f6d404 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/LevelSuggestionProvider.java @@ -0,0 +1,28 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +import java.io.File; +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +public class LevelSuggestionProvider implements SuggestionProvider { + private final WorldsPlugin plugin; + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + plugin.levelView().listLevels() + .filter(plugin.levelView()::canLoad) + .map(File::getName) + .map(StringArgumentType::escapeIfRequired) + .filter(s -> s.contains(builder.getRemaining())) + .forEach(builder::suggest); + return builder.buildFuture(); + } +} From 883e4439b732adbd041b0c59068080c8b74d42e3 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 4 Aug 2024 21:05:47 +0200 Subject: [PATCH 074/182] Temporarily disable portal linking logic in PortalListener.java --- .../java/net/thenextlvl/worlds/listener/PortalListener.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index 49bbdf43..39f57087 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -3,7 +3,6 @@ import io.papermc.paper.event.entity.EntityPortalReadyEvent; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.link.Link; import net.thenextlvl.worlds.model.PortalCooldown; import org.bukkit.Location; import org.bukkit.Material; @@ -27,12 +26,14 @@ public class PortalListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityPortal(EntityPortalReadyEvent event) { + /* plugin.linkRegistry().getLinks().stream() - // .filter(link -> event.getPortalType().equals(link.portalType())) + .filter(link -> event.getPortalType().equals(link.portalType())) .filter(link -> event.getEntity().getWorld().equals(link.source())) .findFirst() .map(Link::destination) .ifPresent(event::setTargetWorld); + */ } @SuppressWarnings("UnstableApiUsage") From 18b7e5bb7b6217c42ec1a5d09297dc998ec49094 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:36:13 +0200 Subject: [PATCH 075/182] map debug to normal --- plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index f7a757d8..065e5e9f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -188,9 +188,8 @@ public String getDimension(CompoundTag dimensions, World.Environment environment public Optional getWorldType(CompoundTag generator) { return getGeneratorType(generator).map(string -> switch (string) { - case "minecraft:noise" -> WorldType.NORMAL; + case "minecraft:noise", "minecraft:debug" -> WorldType.NORMAL; case "minecraft:flat" -> WorldType.FLAT; - case "minecraft:debug" -> throw new IllegalArgumentException("Debug worlds are not yet supported"); default -> throw new IllegalArgumentException("Unexpected generator type: " + string); }); } From c82948a5c87ed7738af3626c810a44aec228d877 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:36:22 +0200 Subject: [PATCH 076/182] add isLevel method --- .../main/java/net/thenextlvl/worlds/view/LevelView.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 065e5e9f..17bec99f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -31,9 +31,11 @@ public class LevelView { public Stream listLevels() { return Optional.ofNullable(plugin.getServer().getWorldContainer() .listFiles(File::isDirectory)).stream() - .flatMap(files -> Arrays.stream(files).filter(level -> - new File(level, "level.dat").isFile() || - new File(level, "level.dat_old").isFile())); + .flatMap(files -> Arrays.stream(files).filter(this::isLevel)); + } + + public boolean isLevel(File file) { + return file.isDirectory() && (new File(file, "level.dat").isFile() || new File(file, "level.dat_old").isFile()); } public boolean hasNetherDimension(File level) { From 51a06f6dd6789fb597df06a7b1e862053e33dfae Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:36:50 +0200 Subject: [PATCH 077/182] replace `force` option with predicate --- .../net/thenextlvl/worlds/view/LevelView.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 17bec99f..0beecde2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.NoSuchElementException; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -59,18 +60,22 @@ public World.Environment getEnvironment(File level) { } public @Nullable World loadLevel(File level) { - return loadLevel(level, false); + return loadLevel(level, getEnvironment(level)); } - public @Nullable World loadLevel(File level, boolean force) { - return loadLevel(level, getEnvironment(level), force); + public @Nullable World loadLevel(File level, World.Environment environment) { + return loadLevel(level, environment, extras -> extras.map(LevelExtras::enabled).isPresent()); } - public @Nullable World loadLevel(File level, World.Environment environment, boolean force) { + public @Nullable World loadLevel(File level, Predicate> predicate) { + return loadLevel(level, getEnvironment(level), predicate); + } + + public @Nullable World loadLevel(File level, World.Environment environment, Predicate> predicate) { var data = getLevelDataFile(level).getRoot().optional("Data"); var extras = data.flatMap(this::getExtras); - if (!force && extras.filter(LevelExtras::enabled).isEmpty()) return null; + if (!predicate.test(extras)) return null; var settings = data.flatMap(tag -> tag.optional("WorldGenSettings")); var dimensions = settings.flatMap(tag -> tag.optional("dimensions")); From 07c7332fc70fe82e861752eefe4de2dea19ab2a4 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:37:31 +0200 Subject: [PATCH 078/182] return normal for vanilla generated levels --- .../main/java/net/thenextlvl/worlds/view/LevelView.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 0beecde2..f0fc2802 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -54,8 +54,11 @@ public boolean canLoad(File level) { } public World.Environment getEnvironment(File level) { - if (hasEndDimension(level)) return World.Environment.THE_END; - if (hasNetherDimension(level)) return World.Environment.NETHER; + var end = hasEndDimension(level); + var nether = hasNetherDimension(level); + if (end && nether) return World.Environment.NORMAL; + if (end) return World.Environment.THE_END; + if (nether) return World.Environment.NETHER; return World.Environment.NORMAL; } From 2cd94305677fc957ce2797bc928b196fc1e6c0c0 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:37:56 +0200 Subject: [PATCH 079/182] fix tag cast --- plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index f0fc2802..df5a97ab 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -93,7 +93,7 @@ public World.Environment getEnvironment(File level) { var hardcore = data.flatMap(tag -> tag.optional("hardcore")) .orElseThrow(() -> new NoSuchElementException("hardcore")) .getAsBoolean(); - var seed = settings.flatMap(tag -> tag.optional("seed")) + var seed = settings.flatMap(tag -> tag.optional("seed")) .orElseThrow(() -> new NoSuchElementException("seed")) .getAsInt(); var structures = settings.flatMap(tag -> tag.optional("generate_features")) From bddb10647f9cbe739778152b9feeb9f125b8312f Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:38:14 +0200 Subject: [PATCH 080/182] remove `(` and `)` from key --- .../src/main/java/net/thenextlvl/worlds/view/LevelView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index df5a97ab..e8a9071d 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -101,7 +101,9 @@ public World.Environment getEnvironment(File level) { .getAsBoolean(); var key = extras.map(LevelExtras::key).orElseGet(() -> { - var namespace = level.getName().toLowerCase().replace(" ", "_"); + var namespace = level.getName().toLowerCase() + .replace("(", "").replace(")", "") + .replace(" ", "_"); return new NamespacedKey("worlds", namespace); }); From 7bef4f6f7990e8a5efae9b1c3b3e6c33b0f41eaf Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:38:22 +0200 Subject: [PATCH 081/182] ignore overworld --- .../main/java/net/thenextlvl/worlds/listener/WorldListener.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java index 22aff577..cae462b1 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java @@ -13,6 +13,7 @@ public class WorldListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldSave(WorldSaveEvent event) { + if (event.getWorld().key().asString().equals("minecraft:overworld")) return; plugin.persistWorld(event.getWorld()); } } From a4232406ae15ae95e4e922b019ee49ea30ca2d12 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:38:34 +0200 Subject: [PATCH 082/182] add messages --- plugin/src/main/resources/worlds.properties | 5 +++++ plugin/src/main/resources/worlds_german.properties | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 3c9a1002..e5e9d620 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -16,6 +16,8 @@ world.preset.invalid= The world preset is not a valid json string world.preset.flat= Presets are only applicable on flat maps world.clone.success= Successfully cloned world world.clone.failed= Failed to clone world +world.load.success= Successfully loaded the world +world.load.failed= Failed to load the world world.spawn.set.success= Set world spawn at , , [] world.spawn.set.failed= Failed to change world spawn world.info.name= Name: @@ -24,6 +26,9 @@ world.info.type= Type: world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Seed: '>'> +world.regenerate.disallowed= The overworld can only be scheduled for regeneration +world.regenerate.success= Successfully regenerated the world +world.regenerate.failed= Failed to regenerate the world world.delete.disallowed= The overworld can only be scheduled for deletion world.delete.success= Successfully deleted the world world.unload.failed= Failed to unload the world diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 313a351e..c28a3afa 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -14,6 +14,8 @@ world.preset.invalid= Die Welten Voreinstellung ist kein gültiger world.preset.flat= Voreinstellungen sind nur auf flache Welten anwendbar world.clone.success= Die Welt wurde erfolgreich geklont world.clone.failed= Die Welt konnte nicht geklont werden +world.load.success= Die Welt wurde erfolgreich geladen +world.load.failed= Die Welt konnte nicht geladen werden world.spawn.set.success= Der spawn ist jetzt bei , , [] world.spawn.set.failed= Der spawn konnte nicht neu gesetzt werden world.info.name= Name: @@ -22,7 +24,9 @@ world.info.type= Typ: world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Startwert: '>'> -world.name.absent= Du musst eine Welt angeben +world.regenerate.disallowed= The overworld can only be scheduled for regeneration +world.regenerate.success= Successfully regenerated the world +world.regenerate.failed= Failed to regenerate the world world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden world.delete.success= Die Welt wurde erfolgreich gelöscht world.unload.failed= Die Welt konnte nicht entladen werden From 9f6efe132af30a6d7c2321347f93e5da3e5aeb89 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:38:45 +0200 Subject: [PATCH 083/182] add world regenerate command --- .../command/WorldRegenerateCommand.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java new file mode 100644 index 00000000..70fbb72d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java @@ -0,0 +1,105 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.argument.CommandFlagsArgument; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; + +import java.io.File; +import java.util.Set; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +class WorldRegenerateCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("regenerate") + .requires(source -> source.getSender().hasPermission("worlds.command.regenerate")) + .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("flags", new CommandFlagsArgument( + Set.of("--confirm", "--schedule") + )).executes(this::regenerate)) + .executes(this::confirmationNeeded)); + } + + private int confirmationNeeded(CommandContext context) { + var sender = context.getSource().getSender(); + plugin.bundle().sendMessage(sender, "command.confirmation", + Placeholder.parsed("action", "/" + context.getInput()), + Placeholder.parsed("confirmation", "/" + context.getInput() + " --confirm")); + return Command.SINGLE_SUCCESS; + } + + private int regenerate(CommandContext context) { + var flags = context.getArgument("flags", CommandFlagsArgument.Flags.class); + if (!flags.contains("--confirm")) return confirmationNeeded(context); + var world = context.getArgument("world", World.class); + var result = regenerate(world, flags.contains("--schedule")); + plugin.bundle().sendMessage(context.getSource().getSender(), result, + Placeholder.parsed("world", world.key().asString())); + return Command.SINGLE_SUCCESS; + } + + private String regenerate(World world, boolean schedule) { + return schedule ? scheduleRegeneration(world) : regenerateNow(world); + } + + private String regenerateNow(World world) { + if (world.getKey().toString().equals("minecraft:overworld")) + return "world.regenerate.disallowed"; + + var environment = world.getEnvironment(); + var worldFolder = world.getWorldFolder(); + var players = world.getPlayers(); + + var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); + players.forEach(player -> player.teleport(fallback)); + + if (!plugin.getServer().unloadWorld(world, false)) + return "world.unload.failed"; + + regenerate(worldFolder); + + var regenerated = plugin.levelView().loadLevel(worldFolder, environment); + if (regenerated != null) players.forEach(player -> + player.teleportAsync(regenerated.getSpawnLocation())); + return regenerated != null ? "world.regenerate.success" : "world.regenerate.failed"; + } + + private String scheduleRegeneration(World world) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> + regenerate(world.getWorldFolder()))); + return "world.delete.scheduled"; + } + + private void regenerate(File level) { + delete(new File(level, "DIM-1")); + delete(new File(level, "DIM1")); + delete(new File(level, "advancements")); + delete(new File(level, "data")); + delete(new File(level, "entities")); + delete(new File(level, "playerdata")); + delete(new File(level, "poi")); + delete(new File(level, "region")); + delete(new File(level, "stats")); + } + + @SuppressWarnings("UnusedReturnValue") + private boolean delete(File file) { + if (file.isFile()) return file.delete(); + var files = file.listFiles(); + if (files == null) return false; + for (var file1 : files) delete(file1); + return file.delete(); + } +} From 70a2cba1a8c7ec769f2d73636d502269e36b9cb1 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:38:58 +0200 Subject: [PATCH 084/182] implement world load logic --- .../worlds/command/WorldLoadCommand.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java index f8db99b0..54c4b727 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java @@ -3,10 +3,15 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.LevelSuggestionProvider; + +import java.io.File; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,8 +22,18 @@ class WorldLoadCommand { return Commands.literal("load") .requires(source -> source.getSender().hasPermission("worlds.command.load")) .then(Commands.argument("world", StringArgumentType.string()) - .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + .suggests(new LevelSuggestionProvider<>(plugin)) + .executes(this::load)); + } + + private int load(CommandContext context) { + var name = context.getArgument("world", String.class); + var level = new File(plugin.getServer().getWorldContainer(), name); + var world = plugin.levelView().isLevel(level) ? plugin.levelView().loadLevel(level, + optional -> optional.map(extras -> !extras.enabled()).isPresent()) : null; + var message = world != null ? "world.load.success" : "world.load.failed"; + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("world", world != null ? world.key().asString() : name)); + return world != null ? Command.SINGLE_SUCCESS : 0; } } From 52b11d53a4a6f3af1c458db71b315ad35d3bdc75 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:39:04 +0200 Subject: [PATCH 085/182] implement world import logic --- .../worlds/command/WorldImportCommand.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index c7222530..4f4bef33 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -3,10 +3,21 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.DimensionSuggestionProvider; +import net.thenextlvl.worlds.command.suggestion.LevelSuggestionProvider; +import org.bukkit.World; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Optional; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,8 +28,30 @@ class WorldImportCommand { return Commands.literal("import") .requires(source -> source.getSender().hasPermission("worlds.command.import")) .then(Commands.argument("world", StringArgumentType.string()) - .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + .suggests(new LevelSuggestionProvider<>(plugin)) + .then(Commands.argument("dimension", ArgumentTypes.key()) + .suggests(new DimensionSuggestionProvider<>()) + .executes(context -> { + var dimension = context.getArgument("dimension", Key.class); + return execute(context, switch (dimension.asString()) { + case "minecraft:overworld" -> World.Environment.NORMAL; + case "minecraft:the_end" -> World.Environment.THE_END; + case "minecraft:the_nether" -> World.Environment.NETHER; + default -> World.Environment.CUSTOM; + }); + })) + .executes(context -> execute(context, null))); + } + + private int execute(CommandContext context, @Nullable World.Environment environment) { + var name = context.getArgument("world", String.class); + var level = new File(plugin.getServer().getWorldContainer(), name); + var world = plugin.levelView().isLevel(level) ? environment != null + ? plugin.levelView().loadLevel(level, environment, Optional::isEmpty) + : plugin.levelView().loadLevel(level, Optional::isEmpty) : null; + var message = world != null ? "world.import.success" : "world.import.failed"; + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("world", world != null ? world.key().asString() : name)); + return world != null ? Command.SINGLE_SUCCESS : 0; } } From 467bfcf7664ff6c13751ee0976474f130123b249 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:39:14 +0200 Subject: [PATCH 086/182] register world regenerate command --- .../main/java/net/thenextlvl/worlds/command/WorldCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java index 4ef6085b..2cd25049 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java @@ -20,6 +20,7 @@ public void register() { .then(new WorldLinkCommand(plugin).create()) .then(new WorldListCommand(plugin).create()) .then(new WorldLoadCommand(plugin).create()) + .then(new WorldRegenerateCommand(plugin).create()) .then(new WorldSaveAllCommand(plugin).create()) .then(new WorldSaveCommand(plugin).create()) .then(new WorldSaveOffCommand(plugin).create()) From 3336a0163a55be5aef2fbe8a704e7eb4c797d33b Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 20:39:25 +0200 Subject: [PATCH 087/182] add dimension suggestion provider --- .../DimensionSuggestionProvider.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java new file mode 100644 index 00000000..8a8a8a79 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java @@ -0,0 +1,18 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import java.util.concurrent.CompletableFuture; + +public class DimensionSuggestionProvider implements SuggestionProvider { + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + builder.suggest("minecraft:overworld"); + builder.suggest("minecraft:the_end"); + builder.suggest("minecraft:the_nether"); + return builder.buildFuture(); + } +} From 39291a93bb2dfd7c0aa880a2e32dd0b273118890 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 21:01:07 +0200 Subject: [PATCH 088/182] add dimension argument --- .../command/argument/DimensionArgument.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java new file mode 100644 index 00000000..f97e7196 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java @@ -0,0 +1,21 @@ +package net.thenextlvl.worlds.command.argument; + +import core.paper.command.WrappedArgumentType; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import net.kyori.adventure.key.Key; +import net.thenextlvl.worlds.command.suggestion.DimensionSuggestionProvider; +import org.bukkit.World; + +@SuppressWarnings("UnstableApiUsage") +public class DimensionArgument extends WrappedArgumentType { + public DimensionArgument() { + super(ArgumentTypes.key(), (reader, type) -> { + return switch (type.asString()) { + case "minecraft:overworld" -> World.Environment.NORMAL; + case "minecraft:the_end" -> World.Environment.THE_END; + case "minecraft:the_nether" -> World.Environment.NETHER; + default -> World.Environment.CUSTOM; + }; + }, new DimensionSuggestionProvider()); + } +} From 9da7c2af7ffec5c509ce956aaa290b81bd536072 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 21:01:19 +0200 Subject: [PATCH 089/182] refactor dimension suggestion provider --- .../command/suggestion/DimensionSuggestionProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java index 8a8a8a79..ca4ea9e6 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java @@ -1,15 +1,16 @@ package net.thenextlvl.worlds.command.suggestion; import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import core.paper.command.SuggestionProvider; import java.util.concurrent.CompletableFuture; -public class DimensionSuggestionProvider implements SuggestionProvider { +public class DimensionSuggestionProvider implements SuggestionProvider { + @Override - public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { + public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { builder.suggest("minecraft:overworld"); builder.suggest("minecraft:the_end"); builder.suggest("minecraft:the_nether"); From f705540e842951dc1ca2f907f061c1f0cd8ffc70 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 21:01:25 +0200 Subject: [PATCH 090/182] add package info --- .../worlds/command/argument/package-info.java | 10 ++++++++++ .../worlds/command/suggestion/package-info.java | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/package-info.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/package-info.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/package-info.java new file mode 100644 index 00000000..2902104b --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.command.argument; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/package-info.java new file mode 100644 index 00000000..88600988 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.worlds.command.suggestion; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From 7eedf3e86c94d035745858f5629cc9b9a521925c Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 21:01:38 +0200 Subject: [PATCH 091/182] refactor world import command --- .../worlds/command/WorldImportCommand.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 4f4bef33..3b3e49c7 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -6,14 +6,13 @@ import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; -import net.kyori.adventure.key.Key; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.command.suggestion.DimensionSuggestionProvider; +import net.thenextlvl.worlds.command.argument.DimensionArgument; import net.thenextlvl.worlds.command.suggestion.LevelSuggestionProvider; import org.bukkit.World; +import org.bukkit.entity.Entity; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -29,16 +28,10 @@ class WorldImportCommand { .requires(source -> source.getSender().hasPermission("worlds.command.import")) .then(Commands.argument("world", StringArgumentType.string()) .suggests(new LevelSuggestionProvider<>(plugin)) - .then(Commands.argument("dimension", ArgumentTypes.key()) - .suggests(new DimensionSuggestionProvider<>()) + .then(Commands.argument("dimension", new DimensionArgument()) .executes(context -> { - var dimension = context.getArgument("dimension", Key.class); - return execute(context, switch (dimension.asString()) { - case "minecraft:overworld" -> World.Environment.NORMAL; - case "minecraft:the_end" -> World.Environment.THE_END; - case "minecraft:the_nether" -> World.Environment.NETHER; - default -> World.Environment.CUSTOM; - }); + var environment = context.getArgument("dimension", World.Environment.class); + return execute(context, environment); })) .executes(context -> execute(context, null))); } @@ -52,6 +45,8 @@ private int execute(CommandContext context, @Nullable World. var message = world != null ? "world.import.success" : "world.import.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : name)); + if (world != null && context.getSource().getSender() instanceof Entity entity) + entity.teleportAsync(world.getSpawnLocation()); return world != null ? Command.SINGLE_SUCCESS : 0; } } From 32187200e0313f9aec733db051fb47a4e71d75c8 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 5 Aug 2024 21:01:51 +0200 Subject: [PATCH 092/182] teleport executor to newly loaded world --- .../java/net/thenextlvl/worlds/command/WorldLoadCommand.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java index 54c4b727..c2f27c4a 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java @@ -10,6 +10,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.LevelSuggestionProvider; +import org.bukkit.entity.Entity; import java.io.File; @@ -34,6 +35,8 @@ private int load(CommandContext context) { var message = world != null ? "world.load.success" : "world.load.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : name)); + if (world != null && context.getSource().getSender() instanceof Entity entity) + entity.teleportAsync(world.getSpawnLocation()); return world != null ? Command.SINGLE_SUCCESS : 0; } } From 55c13dc126d154569a141dc3035006820d0f3dc7 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:43:17 +0200 Subject: [PATCH 093/182] Restrict world command to users with specific permissions. --- .../main/java/net/thenextlvl/worlds/command/WorldCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java index 2cd25049..9b3c78b5 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCommand.java @@ -12,6 +12,7 @@ public class WorldCommand { public void register() { var command = Commands.literal("world") + .requires(source -> source.getSender().hasPermission("worlds.command")) .then(new WorldCloneCommand(plugin).create()) .then(new WorldCreateCommand(plugin).create()) .then(new WorldDeleteCommand(plugin).create()) From 1a868272471167ec501c1e212878824fb6dd91eb Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:45:16 +0200 Subject: [PATCH 094/182] Refactor WorldLinkCommand and add create, list, remove functionality. --- .../worlds/command/WorldLinkCommand.java | 12 +++------ .../command/WorldLinkCreateCommand.java | 27 +++++++++++++++++++ .../worlds/command/WorldLinkListCommand.java | 18 +++++++++++++ .../command/WorldLinkRemoveCommand.java | 27 +++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java index 3bd2cdd0..ce2f20b5 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java @@ -1,14 +1,11 @@ package net.thenextlvl.worlds.command; -import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -18,11 +15,8 @@ class WorldLinkCommand { ArgumentBuilder create() { return Commands.literal("link") .requires(source -> source.getSender().hasPermission("worlds.command.link")) - .then(Commands.argument("source", ArgumentTypes.world()) - .suggests(new WorldSuggestionProvider<>(plugin)) - .then(Commands.argument("destination", ArgumentTypes.world()) - .executes(context -> { - return Command.SINGLE_SUCCESS; - }))); + .then(new WorldLinkCreateCommand(plugin).create()) + .then(new WorldLinkListCommand(plugin).create()) + .then(new WorldLinkRemoveCommand(plugin).create()); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java new file mode 100644 index 00000000..d8b69d40 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java @@ -0,0 +1,27 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +public class WorldLinkCreateCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("link") + .requires(source -> source.getSender().hasPermission("worlds.command.link.create")) + .then(Commands.argument("source", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("destination", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }))); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java new file mode 100644 index 00000000..47fe706d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java @@ -0,0 +1,18 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +public class WorldLinkListCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("link") + .requires(source -> source.getSender().hasPermission("worlds.command.link.list")); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java new file mode 100644 index 00000000..558e71a8 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -0,0 +1,27 @@ +package net.thenextlvl.worlds.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +public class WorldLinkRemoveCommand { + private final WorldsPlugin plugin; + + ArgumentBuilder create() { + return Commands.literal("link") + .requires(source -> source.getSender().hasPermission("worlds.command.link.remove")) + .then(Commands.argument("source", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.argument("destination", ArgumentTypes.world()) + .executes(context -> { + return Command.SINGLE_SUCCESS; + }))); + } +} From f7c84b154cb5575bbd0472887b3171b0973815d3 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:45:28 +0200 Subject: [PATCH 095/182] Update shadow plugin version to 8.1.8 --- plugin/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 937cb5e0..47acc6fd 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("io.papermc.hangar-publish-plugin") version "0.1.2" id("io.papermc.paperweight.userdev") version "1.7.1" id("net.minecrell.plugin-yml.paper") version "0.6.0" - id("io.github.goooler.shadow") version "8.1.7" + id("io.github.goooler.shadow") version "8.1.8" id("com.modrinth.minotaur") version "2.+" } From 54a223ce3d530883347de6d1fa9a50171a11464b Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:45:38 +0200 Subject: [PATCH 096/182] Remove unnecessary blank line from build script. --- plugin/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 47acc6fd..9c86b896 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -47,7 +47,6 @@ dependencies { annotationProcessor("org.projectlombok:lombok:1.18.32") } - tasks.shadowJar { relocate("org.bstats", "net.thenextlvl.worlds.bstats") archiveBaseName.set("worlds") From 0f3627f0734e78e7f244aaa521498289d1c3b04b Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:45:58 +0200 Subject: [PATCH 097/182] Update permissions structure for world commands --- plugin/build.gradle.kts | 84 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 9c86b896..51fc98c6 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -63,16 +63,24 @@ paper { authors = listOf("NonSwag") permissions { - register("worlds.commands.world") { + register("worlds.commands.admin") { this.children = listOf( - "worlds.command.world.create", - "worlds.command.world.delete", - "worlds.command.world.export", - "worlds.command.world.import", - "worlds.command.world.info", - "worlds.command.world.list", - "worlds.command.world.setspawn", - "worlds.command.world.teleport" + "worlds.command.clone", + "worlds.command.create", + "worlds.command.delete", + "worlds.command.import", + "worlds.command.info", + "worlds.command.link", + "worlds.command.list", + "worlds.command.load", + "worlds.command.save", + "worlds.command.save-all", + "worlds.command.save-off", + "worlds.command.save-on", + "worlds.command.setspawn", + "worlds.command.spawn", + "worlds.command.teleport", + "worlds.command.unload", ) } register("worlds.commands.link") { @@ -82,6 +90,64 @@ paper { "worlds.command.link.list" ) } + register("worlds.command.link.create") { + this.children = listOf("worlds.command.link") + } + register("worlds.command.link.delete") { + this.children = listOf("worlds.command.link") + } + register("worlds.command.link.list") { + this.children = listOf("worlds.command.link") + } + + register("worlds.command.link") { + this.children = listOf("worlds.command") + } + register("worlds.command.clone") { + this.children = listOf("worlds.command") + } + register("worlds.command.create") { + this.children = listOf("worlds.command") + } + register("worlds.command.delete") { + this.children = listOf("worlds.command") + } + register("worlds.command.import") { + this.children = listOf("worlds.command") + } + register("worlds.command.info") { + this.children = listOf("worlds.command") + } + register("worlds.command.list") { + this.children = listOf("worlds.command") + } + register("worlds.command.load") { + this.children = listOf("worlds.command") + } + register("worlds.command.save") { + this.children = listOf("worlds.command") + } + register("worlds.command.save-all") { + this.children = listOf("worlds.command") + } + register("worlds.command.save-off") { + this.children = listOf("worlds.command") + } + register("worlds.command.save-on") { + this.children = listOf("worlds.command") + } + register("worlds.command.setspawn") { + this.children = listOf("worlds.command") + } + register("worlds.command.spawn") { + this.children = listOf("worlds.command") + } + register("worlds.command.teleport") { + this.children = listOf("worlds.command") + } + register("worlds.command.unload") { + this.children = listOf("worlds.command") + } } } From 2dba0e815a1ad32fc0f449fd69e0755e97cf5e67 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 6 Aug 2024 21:46:05 +0200 Subject: [PATCH 098/182] Remove commented out minimize() invocation in build.gradle.kts --- plugin/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 51fc98c6..d2db4a40 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -50,7 +50,6 @@ dependencies { tasks.shadowJar { relocate("org.bstats", "net.thenextlvl.worlds.bstats") archiveBaseName.set("worlds") - // minimize() // breaks cloud } paper { From c4e4fd50f91bae9fd048a75e952595e6270f3df8 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 15:53:25 +0200 Subject: [PATCH 099/182] Remove unused methods and conditional check in persistWorld --- .../main/java/net/thenextlvl/worlds/WorldsPlugin.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 76831795..cfae7dd7 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -51,10 +51,7 @@ public class WorldsPlugin extends JavaPlugin { @Override public void onLoad() { - registerServices(); - if (presetsFolder().list() == null) saveDefaultPresets(); - versionChecker().checkVersion(); } @@ -83,16 +80,12 @@ private void persistWorlds() { } public void persistWorld(World world) { + if (world.key().asString().equals("minecraft:overworld")) return; var container = world.getPersistentDataContainer(); container.set(new NamespacedKey("worlds", "world_key"), STRING, world.getKey().asString()); - container.set(new NamespacedKey("worlds", "auto_save"), BOOLEAN, world.isAutoSave()); container.set(new NamespacedKey("worlds", "enabled"), BOOLEAN, true); } - private void registerServices() { - // getServer().getServicesManager().register(LinkRegistry.class, linkRegistry(), this, ServicePriority.Highest); - } - private void registerListeners() { getServer().getPluginManager().registerEvents(new PortalListener(this), this); getServer().getPluginManager().registerEvents(new ServerListener(this), this); From 70d58b12e780945d7dce5b9208499cdeca2ed821 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 15:53:56 +0200 Subject: [PATCH 100/182] Remove autoSave functionality from LevelExtras and LevelView --- .../net/thenextlvl/worlds/model/LevelExtras.java | 1 - .../java/net/thenextlvl/worlds/view/LevelView.java | 13 ++----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java index 88901b60..9ef8b89c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java @@ -5,7 +5,6 @@ public record LevelExtras( @Nullable NamespacedKey key, - boolean autoSave, boolean enabled ) { } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index e8a9071d..ac043d97 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -116,13 +116,7 @@ public World.Environment getEnvironment(File level) { generatorSettings.ifPresent(preset -> creator.generator(preset.serialize().toString())); - - var world = creator.createWorld(); - if (world == null) return null; - - extras.map(LevelExtras::autoSave).ifPresent(world::setAutoSave); - - return world; + return creator.createWorld(); } public Optional getExtras(CompoundTag data) { @@ -133,13 +127,10 @@ public Optional getExtras(CompoundTag data) { .map(Tag::getAsString) .map(NamespacedKey::fromString) .orElse(null); - var autoSave = values.optional("worlds:auto_save") - .map(Tag::getAsBoolean) - .orElse(true); var enabled = values.optional("worlds:enabled") .map(Tag::getAsBoolean) .orElse(true); - return new LevelExtras(key, autoSave, enabled); + return new LevelExtras(key, enabled); }); } From 19aecd48ce451e939760d9a6d071aa3b489b7429 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 15:54:08 +0200 Subject: [PATCH 101/182] Remove redundant overworld check in onWorldSave event --- .../main/java/net/thenextlvl/worlds/listener/WorldListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java index cae462b1..22aff577 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java @@ -13,7 +13,6 @@ public class WorldListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldSave(WorldSaveEvent event) { - if (event.getWorld().key().asString().equals("minecraft:overworld")) return; plugin.persistWorld(event.getWorld()); } } From 63ba2f0190fbc4b144dceb797650d3fb50d9ce98 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 15:54:23 +0200 Subject: [PATCH 102/182] Remove Link feature and associated classes. --- .../java/net/thenextlvl/worlds/link/Link.java | 14 ---- .../thenextlvl/worlds/link/LinkRegistry.java | 17 ----- .../thenextlvl/worlds/link/package-info.java | 10 --- .../worlds/link/CraftLinkRegistry.java | 66 ------------------- .../thenextlvl/worlds/link/package-info.java | 10 --- 5 files changed, 117 deletions(-) delete mode 100644 api/src/main/java/net/thenextlvl/worlds/link/Link.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/link/package-info.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java diff --git a/api/src/main/java/net/thenextlvl/worlds/link/Link.java b/api/src/main/java/net/thenextlvl/worlds/link/Link.java deleted file mode 100644 index 793e1a1a..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/link/Link.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.thenextlvl.worlds.link; - -import com.google.gson.annotations.SerializedName; -import org.bukkit.World; - -public record Link( - @SerializedName("source") World source, - @SerializedName("destination") World destination -) { - @Override - public String toString() { - return source.key().asMinimalString() + " -> " + destination.key().asMinimalString(); - } -} diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java b/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java deleted file mode 100644 index 37d8d16e..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/link/LinkRegistry.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.thenextlvl.worlds.link; - -import org.bukkit.World; - -import java.util.Set; - -public interface LinkRegistry { - Set getLinks(); - - boolean isRegistered(Link link); - - boolean register(Link link); - - boolean unregister(Link link); - - boolean unregisterAll(World world); -} diff --git a/api/src/main/java/net/thenextlvl/worlds/link/package-info.java b/api/src/main/java/net/thenextlvl/worlds/link/package-info.java deleted file mode 100644 index dc8bea85..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/link/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -@TypesAreNotNullByDefault -@FieldsAreNotNullByDefault -@ParametersAreNotNullByDefault -@MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.link; - -import core.annotation.FieldsAreNotNullByDefault; -import core.annotation.MethodsReturnNotNullByDefault; -import core.annotation.ParametersAreNotNullByDefault; -import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java b/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java deleted file mode 100644 index ef436fca..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/link/CraftLinkRegistry.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.thenextlvl.worlds.link; - -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import core.file.FileIO; -import core.file.format.GsonFile; -import core.io.IO; -import core.paper.adapters.world.WorldAdapter; -import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.WorldsPlugin; -import org.bukkit.World; - -import java.util.HashSet; -import java.util.Set; - -@RequiredArgsConstructor -public class CraftLinkRegistry implements LinkRegistry { - private final Set links = new HashSet<>(); - private final WorldsPlugin plugin; - - @Override - public Set getLinks() { - return Set.copyOf(links); - } - - @Override - public boolean isRegistered(Link link) { - return links.contains(link); - } - - @Override - public boolean register(Link link) { - return links.add(link); - } - - @Override - public boolean unregister(Link link) { - return links.remove(link); - } - - @Override - public boolean unregisterAll(World world) { - return links.removeIf(link -> link.source().equals(world) || link.destination().equals(world)); - } - - public void saveLinks() { - var file = loadFile(); - file.setRoot(links); - file.save(); - } - - public void loadLinks() { - var file = loadFile(); - links.addAll(file.getRoot()); - } - - private FileIO> loadFile() { - return new GsonFile<>( - IO.of(plugin.getDataFolder(), "links.json"), - new HashSet<>(), new TypeToken<>() { - }, new GsonBuilder() - .registerTypeHierarchyAdapter(World.class, WorldAdapter.Key.INSTANCE) - .setPrettyPrinting() - .create()); - } -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java deleted file mode 100644 index 39ff5b2f..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/link/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -@TypesAreNotNullByDefault -@FieldsAreNotNullByDefault -@MethodsReturnNotNullByDefault -@ParametersAreNotNullByDefault -package net.thenextlvl.worlds.link; - -import core.annotation.FieldsAreNotNullByDefault; -import core.annotation.MethodsReturnNotNullByDefault; -import core.annotation.ParametersAreNotNullByDefault; -import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From d06a13b1a2b18227b693eb99a7ae78c3036cffc1 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 15:54:31 +0200 Subject: [PATCH 103/182] Refactor WorldTeleportCommand to support custom positions --- .../worlds/command/WorldTeleportCommand.java | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java index f23b44df..3957c0a3 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java @@ -2,9 +2,12 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver; import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -26,26 +29,41 @@ class WorldTeleportCommand { .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) .then(Commands.argument("entities", ArgumentTypes.entities()) - .executes(context -> { - var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); - var world = context.getArgument("world", World.class); - var resolved = entities.resolve(context.getSource()); - // todo: add messages - resolved.forEach(entity -> { - entity.teleportAsync(world.getSpawnLocation(), COMMAND); - }); - return Command.SINGLE_SUCCESS; - })) - .executes(context -> { - if (!(context.getSource().getSender() instanceof Player player)) { - plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); - return 0; - } - var world = context.getArgument("world", World.class); - player.teleportAsync(world.getSpawnLocation(), COMMAND); - plugin.bundle().sendMessage(player, "world.teleport.player.self", - Placeholder.parsed("world", world.key().asString())); - return Command.SINGLE_SUCCESS; - })); + .then(Commands.argument("position", ArgumentTypes.finePosition(true)) + .executes(this::teleportEntityPosition)) + .executes(this::teleportEntity)) + .executes(this::teleport)); + } + + private int teleportEntityPosition(CommandContext context) throws CommandSyntaxException { + var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); + var position = context.getArgument("position", FinePositionResolver.class); + var world = context.getArgument("world", World.class); + var location = position.resolve(context.getSource()).toLocation(world); + var resolved = entities.resolve(context.getSource()); + // todo: add messages + resolved.forEach(entity -> entity.teleportAsync(location, COMMAND)); + return Command.SINGLE_SUCCESS; + } + + private int teleportEntity(CommandContext context) throws CommandSyntaxException { + var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); + var world = context.getArgument("world", World.class); + var resolved = entities.resolve(context.getSource()); + // todo: add messages + resolved.forEach(entity -> entity.teleportAsync(world.getSpawnLocation(), COMMAND)); + return Command.SINGLE_SUCCESS; + } + + private int teleport(CommandContext context) { + if (!(context.getSource().getSender() instanceof Player player)) { + plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); + return 0; + } + var world = context.getArgument("world", World.class); + player.teleportAsync(world.getSpawnLocation(), COMMAND); + plugin.bundle().sendMessage(player, "world.teleport.player.self", + Placeholder.parsed("world", world.key().asString())); + return Command.SINGLE_SUCCESS; } } From 7fb84b6ebac1931607c66eb237939c0ec7425c19 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 16:41:34 +0200 Subject: [PATCH 104/182] Add "world.save" message to world property files --- plugin/src/main/resources/worlds.properties | 2 ++ plugin/src/main/resources/worlds_german.properties | 1 + 2 files changed, 3 insertions(+) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index e5e9d620..e6841a94 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -1,4 +1,6 @@ prefix=Worlds » +world.save= Saving the world (this may take a moment!) +world.save.success= Saved the world command.world.save.saving= command.world.save.success= command.world.save.failed= diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index c28a3afa..1e247e99 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -1,3 +1,4 @@ +world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) world.save.success= Die Welt wurde gespeichert world.save.failed= Die Welt konnte nicht gespeichert werden world.create.success= Die Welt wurde erfolgreich erstellt From 46ce7267da123c30c647fae7201ecd0f426276e3 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 16:41:46 +0200 Subject: [PATCH 105/182] Add save and flush functionality to WorldSaveCommand --- .../worlds/command/WorldSaveCommand.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java index c82a891d..f1c72ca5 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java @@ -6,10 +6,12 @@ import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import org.bukkit.World; -import org.bukkit.entity.Player; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftWorld; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -21,18 +23,35 @@ class WorldSaveCommand { .requires(source -> source.getSender().hasPermission("worlds.command.save")) .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) + .then(Commands.literal("flush") + .executes(context -> { + var world = context.getArgument("world", World.class); + save(context.getSource().getSender(), world, true); + return Command.SINGLE_SUCCESS; + })) .executes(context -> { var world = context.getArgument("world", World.class); - world.save(); - // todo: add message + save(context.getSource().getSender(), world, false); return Command.SINGLE_SUCCESS; })) - .executes(context -> { - if (!(context.getSource().getSender() instanceof Player player)) { - plugin.bundle().sendMessage(context.getSource().getSender(), "command.sender"); - } else player.getWorld().save(); - // todo: add message - return Command.SINGLE_SUCCESS; - }); + .then(Commands.literal("flush") + .executes(context -> save(context.getSource().getSender(), + context.getSource().getLocation().getWorld(), true))) + .executes(context -> save(context.getSource().getSender(), + context.getSource().getLocation().getWorld(), false)); + } + + private int save(CommandSender sender, World world, boolean flush) { + var placeholder = Placeholder.parsed("world", world.key().asString()); + plugin.bundle().sendMessage(sender, "world.save", placeholder); + + var level = ((CraftWorld) world).getHandle(); + var oldSave = level.noSave; + level.noSave = false; + level.save(null, flush, false); + level.noSave = oldSave; + + plugin.bundle().sendMessage(sender, "world.save.success", placeholder); + return Command.SINGLE_SUCCESS; } } From 5d1559cc82196d22baa152bb8e216d0308d771c6 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 16:42:25 +0200 Subject: [PATCH 106/182] Refactor command save calls to return execution result --- .../net/thenextlvl/worlds/command/WorldSaveCommand.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java index f1c72ca5..25ab9ee7 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java @@ -26,13 +26,11 @@ class WorldSaveCommand { .then(Commands.literal("flush") .executes(context -> { var world = context.getArgument("world", World.class); - save(context.getSource().getSender(), world, true); - return Command.SINGLE_SUCCESS; + return save(context.getSource().getSender(), world, true); })) .executes(context -> { var world = context.getArgument("world", World.class); - save(context.getSource().getSender(), world, false); - return Command.SINGLE_SUCCESS; + return save(context.getSource().getSender(), world, false); })) .then(Commands.literal("flush") .executes(context -> save(context.getSource().getSender(), From 211d7346f8077b4d7735fbf053691192c8b897eb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 19:09:09 +0200 Subject: [PATCH 107/182] Add auto-save toggle commands and related messages --- .../worlds/command/WorldSaveCommand.java | 24 ++++++------------- .../worlds/command/WorldSaveOffCommand.java | 21 ++++++++++++---- .../worlds/command/WorldSaveOnCommand.java | 21 ++++++++++++---- plugin/src/main/resources/worlds.properties | 4 ++++ .../main/resources/worlds_german.properties | 4 ++++ 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java index 25ab9ee7..032e437a 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; @@ -10,7 +11,6 @@ import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import org.bukkit.World; -import org.bukkit.command.CommandSender; import org.bukkit.craftbukkit.CraftWorld; @RequiredArgsConstructor @@ -24,24 +24,14 @@ class WorldSaveCommand { .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) .then(Commands.literal("flush") - .executes(context -> { - var world = context.getArgument("world", World.class); - return save(context.getSource().getSender(), world, true); - })) - .executes(context -> { - var world = context.getArgument("world", World.class); - return save(context.getSource().getSender(), world, false); - })) - .then(Commands.literal("flush") - .executes(context -> save(context.getSource().getSender(), - context.getSource().getLocation().getWorld(), true))) - .executes(context -> save(context.getSource().getSender(), - context.getSource().getLocation().getWorld(), false)); + .executes(context -> save(context, true))) + .executes(context -> save(context, false))); } - private int save(CommandSender sender, World world, boolean flush) { + private int save(CommandContext context, boolean flush) { + var world = context.getArgument("world", World.class); var placeholder = Placeholder.parsed("world", world.key().asString()); - plugin.bundle().sendMessage(sender, "world.save", placeholder); + plugin.bundle().sendMessage(context.getSource().getSender(), "world.save", placeholder); var level = ((CraftWorld) world).getHandle(); var oldSave = level.noSave; @@ -49,7 +39,7 @@ private int save(CommandSender sender, World world, boolean flush) { level.save(null, flush, false); level.noSave = oldSave; - plugin.bundle().sendMessage(sender, "world.save.success", placeholder); + plugin.bundle().sendMessage(context.getSource().getSender(), "world.save.success", placeholder); return Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java index ecb9db51..95827f1b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java @@ -4,8 +4,12 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; +import org.bukkit.command.CommandSender; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -15,9 +19,18 @@ class WorldSaveOffCommand { ArgumentBuilder create() { return Commands.literal("save-off") .requires(source -> source.getSender().hasPermission("worlds.command.save-off")) - .executes(context -> { - // todo: save-off - return Command.SINGLE_SUCCESS; - }); + .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .executes(context -> saveOff(context.getSource().getSender(), + context.getArgument("world", World.class)))) + .executes(context -> saveOff(context.getSource().getSender(), + context.getSource().getLocation().getWorld())); + } + + private int saveOff(CommandSender sender, World world) { + var message = world.isAutoSave() ? "command.world.save.off" : "command.world.save.already-off"; + world.setAutoSave(false); + plugin.bundle().sendMessage(sender, message); + return Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java index 50f833ed..f2834760 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java @@ -4,8 +4,12 @@ import com.mojang.brigadier.builder.ArgumentBuilder; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; +import org.bukkit.command.CommandSender; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -15,9 +19,18 @@ class WorldSaveOnCommand { ArgumentBuilder create() { return Commands.literal("save-on") .requires(source -> source.getSender().hasPermission("worlds.command.save-on")) - .executes(context -> { - // todo: save-on - return Command.SINGLE_SUCCESS; - }); + .then(Commands.argument("world", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin)) + .executes(context -> saveOn(context.getSource().getSender(), + context.getArgument("world", World.class)))) + .executes(context -> saveOn(context.getSource().getSender(), + context.getSource().getLocation().getWorld())); + } + + private int saveOn(CommandSender sender, World world) { + var message = world.isAutoSave() ? "command.world.save.already-on" : "command.world.save.on"; + world.setAutoSave(true); + plugin.bundle().sendMessage(sender, message); + return Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index e6841a94..eac72a88 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -4,6 +4,10 @@ world.save.success= Saved the world command.world.save.saving= command.world.save.success= command.world.save.failed= +command.world.save.already-off= Saving is already turned off +command.world.save.off= Automatic saving is now disabled +command.world.save.already-on= Saving is already turned on +command.world.save.on= Automatic saving is now enabled world.create.success= Successfully created the world world.create.failed= Failed to create the world world.import.success= Successfully imported the world diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 1e247e99..2e089291 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -1,6 +1,10 @@ world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) world.save.success= Die Welt wurde gespeichert world.save.failed= Die Welt konnte nicht gespeichert werden +command.world.save.already-off= Automatisches Speichern ist bereits deaktiviert +command.world.save.off= Automatisches Speichern ist jetzt deaktiviert +command.world.save.already-on= Automatisches Speichern ist bereits aktiviert +command.world.save.on= Automatisches Speichern ist jetzt aktiviert world.create.success= Die Welt wurde erfolgreich erstellt world.create.failed= Die Welt konnte nicht erstellt werden world.import.success= Die Welt wurde erfolgreich importiert From cc5185d19a6994b8d7b1388cf600f24ae16cdde4 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 19:09:19 +0200 Subject: [PATCH 108/182] Update teleport commands for multiple entities handling --- .../worlds/command/WorldTeleportCommand.java | 42 ++++++++++++------- plugin/src/main/resources/worlds.properties | 6 ++- .../main/resources/worlds_german.properties | 6 ++- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java index 3957c0a3..a78a46e9 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java @@ -13,9 +13,14 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import java.util.List; + import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; @RequiredArgsConstructor @@ -30,29 +35,25 @@ class WorldTeleportCommand { .suggests(new WorldSuggestionProvider<>(plugin)) .then(Commands.argument("entities", ArgumentTypes.entities()) .then(Commands.argument("position", ArgumentTypes.finePosition(true)) - .executes(this::teleportEntityPosition)) - .executes(this::teleportEntity)) + .executes(this::teleportEntitiesPosition)) + .executes(this::teleportEntities)) .executes(this::teleport)); } - private int teleportEntityPosition(CommandContext context) throws CommandSyntaxException { + private int teleportEntitiesPosition(CommandContext context) throws CommandSyntaxException { var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); var position = context.getArgument("position", FinePositionResolver.class); var world = context.getArgument("world", World.class); var location = position.resolve(context.getSource()).toLocation(world); var resolved = entities.resolve(context.getSource()); - // todo: add messages - resolved.forEach(entity -> entity.teleportAsync(location, COMMAND)); - return Command.SINGLE_SUCCESS; + return teleport(context.getSource().getSender(), resolved, location); } - private int teleportEntity(CommandContext context) throws CommandSyntaxException { + private int teleportEntities(CommandContext context) throws CommandSyntaxException { var entities = context.getArgument("entities", EntitySelectorArgumentResolver.class); var world = context.getArgument("world", World.class); var resolved = entities.resolve(context.getSource()); - // todo: add messages - resolved.forEach(entity -> entity.teleportAsync(world.getSpawnLocation(), COMMAND)); - return Command.SINGLE_SUCCESS; + return teleport(context.getSource().getSender(), resolved, world.getSpawnLocation()); } private int teleport(CommandContext context) { @@ -61,9 +62,22 @@ private int teleport(CommandContext context) { return 0; } var world = context.getArgument("world", World.class); - player.teleportAsync(world.getSpawnLocation(), COMMAND); - plugin.bundle().sendMessage(player, "world.teleport.player.self", - Placeholder.parsed("world", world.key().asString())); - return Command.SINGLE_SUCCESS; + return teleport(player, List.of(player), world.getSpawnLocation()); + } + + private int teleport(CommandSender sender, List entities, Location location) { + var message = entities.size() == 1 ? "world.teleport.other" + : entities.isEmpty() ? "world.teleport.none" : "world.teleport.others"; + entities.forEach(entity -> { + entity.teleportAsync(location, COMMAND); + plugin.bundle().sendMessage(entity, "world.teleport.self", + Placeholder.parsed("world", location.getWorld().key().asString())); + }); + if (entities.size() == 1 && entities.getFirst().equals(sender)) return Command.SINGLE_SUCCESS; + plugin.bundle().sendMessage(sender, message, + Placeholder.parsed("entities", String.valueOf(entities.size())), + Placeholder.parsed("entity", entities.getFirst().getName()), + Placeholder.parsed("world", location.getWorld().key().asString())); + return entities.isEmpty() ? 0 : Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index eac72a88..ad9f5984 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -14,8 +14,10 @@ world.import.success= Successfully imported the world Failed to import the world world.list= Worlds (): world.list.hover=Click to teleport to -world.teleport.player.self= You got teleported to -world.teleport.player.other= Teleported to +world.teleport.none= No entity was found +world.teleport.self= You got teleported to +world.teleport.other= Teleported to +world.teleport.others= Teleported entities to world.known= A world called does already exist world.unknown= A world called does not exist world.preset.invalid= The world preset is not a valid json string diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 2e089291..0620ed4d 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -11,8 +11,10 @@ world.import.success= Die Welt wurde erfolg world.import.failed= Die Welt konnte nicht importiert werden world.list= Welten (): world.list.hover=Klicke um dich zu zu teleportieren -world.teleport.player.self= Du wurdest zu teleportiert -world.teleport.player.other= Du hast zu teleportiert +world.teleport.none= Es wurde kein Objekt gefunden +world.teleport.self= Du wurdest zu teleportiert +world.teleport.other= wurde zu teleportiert +world.teleport.others= Objekte wurden zu teleportiert world.known= Eine Welt mit dem namen existiert bereits world.unknown= Eine Welt mit dem namen existiert nicht world.preset.invalid= Die Welten Voreinstellung ist kein gültiger json Text From 8e40f4123d79a1ba2f968e36f904fb7aef49e085 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 19:18:59 +0200 Subject: [PATCH 109/182] Update teleport messages and optimize command placeholders --- .../thenextlvl/worlds/command/WorldTeleportCommand.java | 7 ++++--- plugin/src/main/resources/worlds.properties | 2 +- plugin/src/main/resources/worlds_german.properties | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java index a78a46e9..98634333 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldTeleportCommand.java @@ -10,6 +10,7 @@ import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver; import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; @@ -75,9 +76,9 @@ private int teleport(CommandSender sender, List entities, Location locat }); if (entities.size() == 1 && entities.getFirst().equals(sender)) return Command.SINGLE_SUCCESS; plugin.bundle().sendMessage(sender, message, - Placeholder.parsed("entities", String.valueOf(entities.size())), - Placeholder.parsed("entity", entities.getFirst().getName()), - Placeholder.parsed("world", location.getWorld().key().asString())); + Placeholder.component("entity", entities.isEmpty() ? Component.empty() : entities.getFirst().name()), + Placeholder.parsed("world", location.getWorld().key().asString()), + Placeholder.parsed("entities", String.valueOf(entities.size()))); return entities.isEmpty() ? 0 : Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index ad9f5984..732cce7c 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -14,7 +14,7 @@ world.import.success= Successfully imported the world Failed to import the world world.list= Worlds (): world.list.hover=Click to teleport to -world.teleport.none= No entity was found +world.teleport.none= No entity was found world.teleport.self= You got teleported to world.teleport.other= Teleported to world.teleport.others= Teleported entities to diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 0620ed4d..48a40bb6 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -11,7 +11,7 @@ world.import.success= Die Welt wurde erfolg world.import.failed= Die Welt konnte nicht importiert werden world.list= Welten (): world.list.hover=Klicke um dich zu zu teleportieren -world.teleport.none= Es wurde kein Objekt gefunden +world.teleport.none= Es wurde kein Objekt gefunden world.teleport.self= Du wurdest zu teleportiert world.teleport.other= wurde zu teleportiert world.teleport.others= Objekte wurden zu teleportiert From bc451ebd3bf4d3503bb51e495c61c10bb96405e8 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 21:00:54 +0200 Subject: [PATCH 110/182] Add world cloning functionality with partial and full options --- .../worlds/command/WorldCloneCommand.java | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java index 5c597278..f6517bb2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java @@ -2,12 +2,22 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.entity.Player; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -19,8 +29,59 @@ class WorldCloneCommand { .requires(source -> source.getSender().hasPermission("worlds.command.clone")) .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) - .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + .then(Commands.argument("key", ArgumentTypes.namespacedKey()) + .then(Commands.literal("template") + .executes(context -> clone(context, false))) + .executes(context -> clone(context, true)))); + } + + private int clone(CommandContext context, boolean full) { + var world = context.getArgument("world", World.class); + var name = context.getArgument("key", NamespacedKey.class); + if (plugin.getServer().getWorld(name) != null) return 0; + var creator = new WorldCreator(name).copy(world); + if (full) copy(world, new File(plugin.getServer().getWorldContainer(), creator.name())); + var copy = creator.createWorld(); + if (copy != null && context.getSource().getSender() instanceof Player player) + player.teleportAsync(copy.getSpawnLocation()); + return copy != null ? Command.SINGLE_SUCCESS : 0; + } + + private void copy(World world, File destination) { + var files = world.getWorldFolder().listFiles(this::shouldCopy); + if (files == null) return; + for (File file : files) copy(file, new File(destination, file.getName())); + } + + private boolean shouldCopy(File file, String name) { + if (name.equals("advancements") && file.isDirectory()) return false; + if (name.equals("datapacks") && file.isDirectory()) return false; + if (name.equals("playerdata") && file.isDirectory()) return false; + if (name.equals("session.lock")) return false; + if (name.equals("stats") && file.isDirectory()) return false; + return !name.equals("uid.dat"); + } + + private void copy(File source, File destination) { + if (source.isDirectory()) copyDirectory(source, destination); + else copyFile(source, destination); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private void copyDirectory(File source, File destination) { + if (!destination.exists()) destination.mkdirs(); + var list = source.listFiles(); + if (list == null) return; + for (var file : list) copy(file, new File(destination, file.getName())); + } + + private void copyFile(File source, File destination) { + try (var in = new FileInputStream(source); + var out = new FileOutputStream(destination)) { + int length; + var buf = new byte[1024]; + while ((length = in.read(buf)) > 0) out.write(buf, 0, length); + } catch (IOException ignored) { + } } } From 4ad605fe867b2484a312c5e8d0eacb9cdb3ca3bb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 21:09:19 +0200 Subject: [PATCH 111/182] Update stream operations from dropWhile to filter --- plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java | 2 +- .../worlds/command/argument/CommandFlagsArgument.java | 2 +- plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index cfae7dd7..c56d63d3 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -69,7 +69,7 @@ public void onDisable() { } private void unloadWorlds() { - getServer().getWorlds().stream().dropWhile(World::isAutoSave).forEach(world -> { + getServer().getWorlds().stream().filter(world -> !world.isAutoSave()).forEach(world -> { world.getPlayers().forEach(player -> player.kick(getServer().shutdownMessage())); getServer().unloadWorld(world, false); }); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java index 37a179b5..09629333 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/CommandFlagsArgument.java @@ -19,7 +19,7 @@ public CommandFlagsArgument(Set flags) { var index = builder.getRemaining().lastIndexOf(' ') + 1; var substring = builder.getRemaining().substring(index); flags.stream() - .dropWhile(builder.getRemaining()::contains) + .filter(flag -> !builder.getRemaining().contains(flag)) .filter(flag -> flag.startsWith(substring)) .forEach(s -> builder.suggest(builder.getRemaining() + s.substring(substring.length()))); return builder.buildFuture(); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index ac043d97..3f2a927d 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -184,7 +184,7 @@ public String getDimension(CompoundTag dimensions, World.Environment environment case NORMAL -> "minecraft:overworld"; case NETHER -> "minecraft:the_nether"; case THE_END -> "minecraft:the_end"; - case CUSTOM -> dimensions.keySet().stream().dropWhile(s -> s.startsWith("minecraft")).findAny() + case CUSTOM -> dimensions.keySet().stream().filter(s -> !s.startsWith("minecraft")).findAny() .orElseThrow(() -> new UnsupportedOperationException("Could not find custom dimension")); }; } From 49dc4f6cdfc5871bf108a8e236ba6cc1d27c554d Mon Sep 17 00:00:00 2001 From: david Date: Wed, 7 Aug 2024 21:21:05 +0200 Subject: [PATCH 112/182] Refactor clone method to improve clarity and message handling. --- .../worlds/command/WorldCloneCommand.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java index f6517bb2..ad6918a8 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java @@ -7,12 +7,14 @@ import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileInputStream; @@ -37,14 +39,25 @@ class WorldCloneCommand { private int clone(CommandContext context, boolean full) { var world = context.getArgument("world", World.class); - var name = context.getArgument("key", NamespacedKey.class); - if (plugin.getServer().getWorld(name) != null) return 0; - var creator = new WorldCreator(name).copy(world); - if (full) copy(world, new File(plugin.getServer().getWorldContainer(), creator.name())); - var copy = creator.createWorld(); - if (copy != null && context.getSource().getSender() instanceof Player player) - player.teleportAsync(copy.getSpawnLocation()); - return copy != null ? Command.SINGLE_SUCCESS : 0; + var key = context.getArgument("key", NamespacedKey.class); + var clone = clone(world, key, full); + + var placeholder = Placeholder.parsed("world", world.key().asString()); + var message = clone != null ? "world.clone.success" : "world.clone.failed"; + + if (clone != null && context.getSource().getSender() instanceof Player player) + player.teleportAsync(clone.getSpawnLocation()); + + plugin.bundle().sendMessage(context.getSource().getSender(), message, placeholder); + return clone != null ? Command.SINGLE_SUCCESS : 0; + } + + private @Nullable World clone(World world, NamespacedKey key, boolean full) { + if (plugin.getServer().getWorld(key) != null) return null; + if (plugin.getServer().getWorld(key.getKey()) != null) return null; + if (new File(plugin.getServer().getWorldContainer(), key.getKey()).isDirectory()) return null; + if (full) copy(world, new File(plugin.getServer().getWorldContainer(), key.getKey())); + return new WorldCreator(key.getKey(), key).copy(world).createWorld(); } private void copy(World world, File destination) { From cac2440d6ca463d4e24f67fa87e0b90887af697b Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 18:29:22 +0200 Subject: [PATCH 113/182] Remove Generator class and package-info from image directories --- .../thenextlvl/worlds/image/Generator.java | 45 ------------------- .../thenextlvl/worlds/image/package-info.java | 10 ----- .../thenextlvl/worlds/image/Generator.java | 28 ------------ .../thenextlvl/worlds/image/package-info.java | 10 ----- 4 files changed, 93 deletions(-) delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/Generator.java delete mode 100644 api/src/main/java/net/thenextlvl/worlds/image/package-info.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java diff --git a/api/src/main/java/net/thenextlvl/worlds/image/Generator.java b/api/src/main/java/net/thenextlvl/worlds/image/Generator.java deleted file mode 100644 index e9020573..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/Generator.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.thenextlvl.worlds.image; - -import org.bukkit.World; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.PluginClassLoader; -import org.jetbrains.annotations.Nullable; - -public record Generator(String plugin, @Nullable String id) { - - @Nullable - public static Generator of(World world) { - var plugin = getGeneratorPlugin(world); - return plugin != null ? new Generator(plugin.getName(), null) : null; - } - - @Nullable - @SuppressWarnings("UnstableApiUsage") - public static Plugin getGeneratorPlugin(World world) { - if (world.getGenerator() == null) return null; - var loader = world.getGenerator().getClass().getClassLoader(); - if (!(loader instanceof PluginClassLoader pluginLoader)) return null; - return pluginLoader.getPlugin(); - } - - public static boolean hasChunkGenerator(Class clazz) { - try { - return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - return false; - } - } - - public static boolean hasBiomeProvider(Class clazz) { - try { - return clazz.getMethod("getDefaultBiomeProvider", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - return false; - } - } - - @Override - public String toString() { - return id() != null ? plugin() + ":" + id() : plugin(); - } -} diff --git a/api/src/main/java/net/thenextlvl/worlds/image/package-info.java b/api/src/main/java/net/thenextlvl/worlds/image/package-info.java deleted file mode 100644 index 06f93ad7..00000000 --- a/api/src/main/java/net/thenextlvl/worlds/image/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -@TypesAreNotNullByDefault -@FieldsAreNotNullByDefault -@ParametersAreNotNullByDefault -@MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.image; - -import core.annotation.FieldsAreNotNullByDefault; -import core.annotation.MethodsReturnNotNullByDefault; -import core.annotation.ParametersAreNotNullByDefault; -import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java b/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java deleted file mode 100644 index e1e6514c..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/Generator.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.thenextlvl.worlds.image; - -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.Nullable; - -public record Generator(String plugin, @Nullable String id) { - - public static boolean hasChunkGenerator(Class clazz) { - try { - return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - return false; - } - } - - public static boolean hasBiomeProvider(Class clazz) { - try { - return clazz.getMethod("getDefaultBiomeProvider", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - return false; - } - } - - @Override - public String toString() { - return id() != null ? plugin() + ":" + id() : plugin(); - } -} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java b/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java deleted file mode 100644 index 399d5585..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/image/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -@TypesAreNotNullByDefault -@FieldsAreNotNullByDefault -@MethodsReturnNotNullByDefault -@ParametersAreNotNullByDefault -package net.thenextlvl.worlds.image; - -import core.annotation.FieldsAreNotNullByDefault; -import core.annotation.MethodsReturnNotNullByDefault; -import core.annotation.ParametersAreNotNullByDefault; -import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From 887093f803f2997ee2886b9dd05c917f16686ea3 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:44:04 +0200 Subject: [PATCH 114/182] Refactor DimensionArgument and add plugin dependency Refactor `DimensionArgument` to throw an exception for unsupported custom dimensions and require `WorldsPlugin` as a constructor parameter. Update `DimensionSuggestionProvider` to support dynamic suggestions using a map and inject the `WorldsPlugin` dependency. --- .../command/argument/DimensionArgument.java | 17 +++++++-------- .../DimensionSuggestionProvider.java | 21 ++++++++++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java index f97e7196..34e5ec14 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/DimensionArgument.java @@ -3,19 +3,18 @@ import core.paper.command.WrappedArgumentType; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import net.kyori.adventure.key.Key; +import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.DimensionSuggestionProvider; import org.bukkit.World; @SuppressWarnings("UnstableApiUsage") public class DimensionArgument extends WrappedArgumentType { - public DimensionArgument() { - super(ArgumentTypes.key(), (reader, type) -> { - return switch (type.asString()) { - case "minecraft:overworld" -> World.Environment.NORMAL; - case "minecraft:the_end" -> World.Environment.THE_END; - case "minecraft:the_nether" -> World.Environment.NETHER; - default -> World.Environment.CUSTOM; - }; - }, new DimensionSuggestionProvider()); + public DimensionArgument(WorldsPlugin plugin) { + super(ArgumentTypes.key(), (reader, type) -> switch (type.asString()) { + case "minecraft:overworld" -> World.Environment.NORMAL; + case "minecraft:the_end" -> World.Environment.THE_END; + case "minecraft:the_nether" -> World.Environment.NETHER; + default -> throw new IllegalArgumentException("Custom dimensions are not yet supported"); + }, new DimensionSuggestionProvider(plugin)); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java index ca4ea9e6..05485924 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/DimensionSuggestionProvider.java @@ -4,16 +4,31 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import core.paper.command.SuggestionProvider; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import java.util.Map; import java.util.concurrent.CompletableFuture; +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") public class DimensionSuggestionProvider implements SuggestionProvider { + private final WorldsPlugin plugin; + + private final Map dimensions = Map.of( + "minecraft:overworld", "environment.normal", + "minecraft:the_end", "environment.end", + "minecraft:the_nether", "environment.nether" + ); @Override public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { - builder.suggest("minecraft:overworld"); - builder.suggest("minecraft:the_end"); - builder.suggest("minecraft:the_nether"); + var sender = ((CommandSourceStack) context.getSource()).getSender(); + dimensions.entrySet().stream() + .filter(entry -> entry.getKey().contains(builder.getRemaining())) + .forEach(entry -> builder.suggest(entry.getKey(), () -> + plugin.bundle().format(sender, entry.getValue()))); return builder.buildFuture(); } } From 056699d2627d46728c8788f3b71a566e2febcdd0 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:44:14 +0200 Subject: [PATCH 115/182] Add GeneratorArgument and GeneratorSuggestionProvider Introduce `GeneratorArgument` to handle generator string arguments and convert them into `Generator` objects. Implement `GeneratorSuggestionProvider` to offer automatic suggestions for enabled plugins with generators during command input. --- .../command/argument/GeneratorArgument.java | 23 +++++++++ .../GeneratorSuggestionProvider.java | 50 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java new file mode 100644 index 00000000..4f2666a5 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java @@ -0,0 +1,23 @@ +package net.thenextlvl.worlds.command.argument; + +import com.mojang.brigadier.arguments.StringArgumentType; +import core.paper.command.WrappedArgumentType; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.GeneratorSuggestionProvider; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +public class GeneratorArgument extends WrappedArgumentType { + public GeneratorArgument(WorldsPlugin plugin) { + super(StringArgumentType.string(), (reader, type) -> { + var split = type.split(":", 2); + var generator = plugin.getServer().getPluginManager().getPlugin(split[0]); + if (generator == null) throw new IllegalArgumentException("Unknown plugin"); + if (!generator.isEnabled()) throw new IllegalStateException("Plugin is not enabled"); + return new Generator(generator, split.length > 1 ? split[1] : null); + }, new GeneratorSuggestionProvider(plugin)); + } + + public record Generator(Plugin plugin, @Nullable String id) { + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java new file mode 100644 index 00000000..cf3371e9 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java @@ -0,0 +1,50 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import core.paper.command.SuggestionProvider; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.plugin.Plugin; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +public class GeneratorSuggestionProvider implements SuggestionProvider { + private final WorldsPlugin plugin; + + @Override + public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { + Arrays.stream(plugin.getServer().getPluginManager().getPlugins()) + .filter(Plugin::isEnabled) + .filter(this::hasGenerator) + .map(Plugin::getName) + .filter(s -> s.contains(builder.getRemaining())) + .forEach(builder::suggest); + return builder.buildFuture(); + } + + private boolean hasGenerator(Plugin plugin) { + return hasChunkGenerator(plugin.getClass()) || hasBiomeProvider(plugin.getClass()); + } + + private boolean hasChunkGenerator(Class clazz) { + try { + return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); + } catch (NoSuchMethodException e) { + e.printStackTrace(); // todo remove + return false; + } + } + + private boolean hasBiomeProvider(Class clazz) { + try { + return clazz.getMethod("getDefaultBiomeProvider", String.class, String.class).getDeclaringClass().equals(clazz); + } catch (NoSuchMethodException e) { + e.printStackTrace(); // todo remove + return false; + } + } +} From 30ba1cb1230a43534d05c3302b43cc91283935fd Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:44:27 +0200 Subject: [PATCH 116/182] Add SeedArgument for handling seed input in commands Introduced a new `SeedArgument` class to handle seed inputs in commands. It converts a string input into a Long value, defaulting to the hash code if parsing fails. --- .../worlds/command/argument/SeedArgument.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/SeedArgument.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/SeedArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/SeedArgument.java new file mode 100644 index 00000000..069f5deb --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/SeedArgument.java @@ -0,0 +1,16 @@ +package net.thenextlvl.worlds.command.argument; + +import com.mojang.brigadier.arguments.StringArgumentType; +import core.paper.command.WrappedArgumentType; + +public class SeedArgument extends WrappedArgumentType { + public SeedArgument() { + super(StringArgumentType.string(), (reader, type) -> { + try { + return Long.parseLong(type); + } catch (NumberFormatException ignored) { + return (long) type.hashCode(); + } + }); + } +} From c6fd499b69a811381f0a5199280c3596bd3b5887 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:44:48 +0200 Subject: [PATCH 117/182] Add WorldPresetArgument and WorldPresetSuggestionProvider Introduce a new class `WorldPresetArgument` to handle world preset arguments and their verification. Additionally, add `WorldPresetSuggestionProvider` to assist with command suggestions for available world presets in the plugin. --- .../command/argument/WorldPresetArgument.java | 24 ++++++++++++++ .../WorldPresetSuggestionProvider.java | 31 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldPresetSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java new file mode 100644 index 00000000..d380167d --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java @@ -0,0 +1,24 @@ +package net.thenextlvl.worlds.command.argument; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.arguments.StringArgumentType; +import core.file.format.JsonFile; +import core.io.IO; +import core.paper.command.WrappedArgumentType; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldPresetSuggestionProvider; +import net.thenextlvl.worlds.preset.Preset; + +import java.io.File; + +public class WorldPresetArgument extends WrappedArgumentType { + public WorldPresetArgument(WorldsPlugin plugin) { + super(StringArgumentType.string(), (reader, type) -> { + var file = new File(plugin.presetsFolder(), type + ".json"); + if (!file.exists()) throw new IllegalStateException("No preset found"); + var root = new JsonFile<>(IO.of(file), new JsonObject()).getRoot(); + if (root.isJsonObject()) return Preset.deserialize(root); + throw new IllegalStateException("Not a valid preset"); + }, new WorldPresetSuggestionProvider(plugin)); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldPresetSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldPresetSuggestionProvider.java new file mode 100644 index 00000000..387c7c0a --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldPresetSuggestionProvider.java @@ -0,0 +1,31 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import core.paper.command.SuggestionProvider; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +import java.io.File; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +public class WorldPresetSuggestionProvider implements SuggestionProvider { + private final WorldsPlugin plugin; + + @Override + public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { + var files = plugin.presetsFolder().listFiles((file, name) -> + name.endsWith(".json")); + if (files != null) Arrays.stream(files) + .map(File::getName) + .map(name -> name.substring(0, name.length() - 5)) + .map(StringArgumentType::escapeIfRequired) + .filter(s -> s.contains(builder.getRemaining())) + .forEach(builder::suggest); + return builder.buildFuture(); + } +} From 80796d1782760c497cd9233aa1b9d3c7b6452768 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:45:03 +0200 Subject: [PATCH 118/182] Add WorldType command argument and suggestion provider This commit introduces a new command argument, `WorldTypeArgument`, and its corresponding suggestion provider, `WorldTypeSuggestionProvider`. The argument maps specific Minecraft world types to the appropriate `WorldType` enum, enhancing command functionality in the plugin. --- .../command/argument/WorldTypeArgument.java | 23 ++++++++++++ .../WorldTypeSuggestionProvider.java | 35 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldTypeArgument.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldTypeSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldTypeArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldTypeArgument.java new file mode 100644 index 00000000..a6fceff5 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldTypeArgument.java @@ -0,0 +1,23 @@ +package net.thenextlvl.worlds.command.argument; + +import core.paper.command.WrappedArgumentType; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import net.kyori.adventure.key.Key; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.suggestion.WorldTypeSuggestionProvider; +import org.bukkit.WorldType; + +@SuppressWarnings("UnstableApiUsage") +public class WorldTypeArgument extends WrappedArgumentType { + public WorldTypeArgument(WorldsPlugin plugin) { + super(ArgumentTypes.key(), (reader, type) -> switch (type.asString()) { + case "minecraft:amplified" -> WorldType.AMPLIFIED; + case "minecraft:flat" -> WorldType.FLAT; + case "minecraft:large_biomes" -> WorldType.LARGE_BIOMES; + case "minecraft:normal" -> WorldType.NORMAL; + // case "minecraft:single_biome" -> ; + // case "minecraft:debug_world" -> ; + default -> throw new IllegalArgumentException("Custom dimensions are not yet supported"); + }, new WorldTypeSuggestionProvider(plugin)); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldTypeSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldTypeSuggestionProvider.java new file mode 100644 index 00000000..dcbc6fc6 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldTypeSuggestionProvider.java @@ -0,0 +1,35 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import core.paper.command.SuggestionProvider; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +@SuppressWarnings("UnstableApiUsage") +public class WorldTypeSuggestionProvider implements SuggestionProvider { + private final WorldsPlugin plugin; + + private final Map dimensions = Map.of( + "minecraft:amplified", "world.type.amplified", + "minecraft:flat", "world.type.flat", + "minecraft:large_biomes", "world.type.large_biomes", + "minecraft:normal", "world.type.normal" + ); + + @Override + public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { + var sender = ((CommandSourceStack) context.getSource()).getSender(); + dimensions.entrySet().stream() + .filter(entry -> entry.getKey().contains(builder.getRemaining())) + .forEach(entry -> builder.suggest(entry.getKey(), () -> + plugin.bundle().format(sender, entry.getValue()))); + return builder.buildFuture(); + } +} From f68d11ae8587ec71f8c91384963682d6c2284d32 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:45:31 +0200 Subject: [PATCH 119/182] Add toString methods to Preset and Layer classes Implemented a toString method in Preset to provide a string representation of layers and biome. Added a toString method in Layer to format its material and height for better readability. --- api/src/main/java/net/thenextlvl/worlds/preset/Layer.java | 5 +++++ .../main/java/net/thenextlvl/worlds/preset/Preset.java | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java b/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java index a38a0954..4f065e47 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java @@ -6,4 +6,9 @@ public record Layer(String block, int height) { Layer(Material material, int height) { this(material.key().asString(), height); } + + @Override + public String toString() { + return height() != 1 ? height() + "*" + block() : block(); + } } diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java index 5ed38791..abc8c80a 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java @@ -91,4 +91,12 @@ public JsonObject serialize() { public static Preset deserialize(JsonObject object) { return gson.fromJson(object, Preset.class); } + + @Override + public String toString() { + var layers = layers().stream() + .map(Layer::toString) + .collect(Collectors.joining(",")); + return layers + ";" + biome(); + } } From c27f04d606fd730adce6083f75a050053c4a5555 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:45:43 +0200 Subject: [PATCH 120/182] Switch to LinkedHashSet for layers and structures Replaced HashSet with LinkedHashSet for the `layers` and `structures` fields to maintain insertion order. This change ensures the order of elements is preserved, which can be crucial for consistent processing or display. --- .../main/java/net/thenextlvl/worlds/preset/Preset.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java index abc8c80a..d3d7087e 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java +++ b/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java @@ -15,8 +15,8 @@ import org.bukkit.Material; import java.io.File; -import java.util.HashSet; -import java.util.Set; +import java.util.LinkedHashSet; +import java.util.stream.Collectors; @Getter @Setter @@ -27,9 +27,9 @@ public class Preset { private boolean features; private boolean decoration; - private Set layers = new HashSet<>(); + private LinkedHashSet layers = new LinkedHashSet<>(); @SerializedName("structure_overrides") - private Set structures = new HashSet<>(); + private LinkedHashSet structures = new LinkedHashSet<>(); /** * Add a layer to the preset From e9a03d531fc7424b83a586c3778c156cb79886d4 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:46:07 +0200 Subject: [PATCH 121/182] Add environment and world type translations Added new translations for environment types and world types in both German and English properties files. Additionally, updated the world info messages to include extra details for world name and type. --- plugin/src/main/resources/worlds.properties | 15 +++++++++------ .../src/main/resources/worlds_german.properties | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 732cce7c..133d62cc 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -1,4 +1,11 @@ prefix=Worlds » +environment.normal=Normal Environment +environment.end=End Environment +environment.nether=Nether Environment +world.type.amplified=Amplified +world.type.flat=Superflat +world.type.large_biomes=Large biomes +world.type.normal=Default world.save= Saving the world (this may take a moment!) world.save.success= Saved the world command.world.save.saving= @@ -18,19 +25,15 @@ world.teleport.none= No entity was found world.teleport.self= You got teleported to world.teleport.other= Teleported to world.teleport.others= Teleported entities to -world.known= A world called does already exist -world.unknown= A world called does not exist -world.preset.invalid= The world preset is not a valid json string -world.preset.flat= Presets are only applicable on flat maps world.clone.success= Successfully cloned world world.clone.failed= Failed to clone world world.load.success= Successfully loaded the world world.load.failed= Failed to load the world world.spawn.set.success= Set world spawn at , , [] world.spawn.set.failed= Failed to change world spawn -world.info.name= Name: +world.info.name= Name: () world.info.players= Players: -world.info.type= Type: +world.info.type= Type: () world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Seed: '>'> diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 48a40bb6..cf4d0ed4 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -1,3 +1,10 @@ +environment.normal=Normale Umgebung +environment.end=Endumgebung +environment.nether=Nether Umgebung +world.type.amplified=Erweitert +world.type.flat=Superflach +world.type.large_biomes=Große Biome +world.type.normal=Standard world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) world.save.success= Die Welt wurde gespeichert world.save.failed= Die Welt konnte nicht gespeichert werden @@ -15,19 +22,15 @@ world.teleport.none= Es wurde kein Objekt gefunden world.teleport.self= Du wurdest zu teleportiert world.teleport.other= wurde zu teleportiert world.teleport.others= Objekte wurden zu teleportiert -world.known= Eine Welt mit dem namen existiert bereits -world.unknown= Eine Welt mit dem namen existiert nicht -world.preset.invalid= Die Welten Voreinstellung ist kein gültiger json Text -world.preset.flat= Voreinstellungen sind nur auf flache Welten anwendbar world.clone.success= Die Welt wurde erfolgreich geklont world.clone.failed= Die Welt konnte nicht geklont werden world.load.success= Die Welt wurde erfolgreich geladen world.load.failed= Die Welt konnte nicht geladen werden world.spawn.set.success= Der spawn ist jetzt bei , , [] world.spawn.set.failed= Der spawn konnte nicht neu gesetzt werden -world.info.name= Name: +world.info.name= Name: () world.info.players= Spieler: -world.info.type= Typ: +world.info.type= Typ: () world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Startwert: '>'> From 5a772e6bbb61c512c526ab2dac7415aa2859062c Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:46:57 +0200 Subject: [PATCH 122/182] Add WorldPreset support and refactor related methods Introduced a WorldPreset class for predefined world settings, replacing direct use of WorldType. Refactored methods in LevelView to utilize the new WorldPreset for more consistent and scalable preset handling, including specialized methods for flat world settings and other presets. --- .../thenextlvl/worlds/model/WorldPreset.java | 14 ++++ .../net/thenextlvl/worlds/view/LevelView.java | 69 ++++++++++++++----- 2 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java b/plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java new file mode 100644 index 00000000..9c466a09 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java @@ -0,0 +1,14 @@ +package net.thenextlvl.worlds.model; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; + +public record WorldPreset(Key key) implements Keyed { + public static final WorldPreset AMPLIFIED = new WorldPreset(Key.key("minecraft", "amplified")); + public static final WorldPreset CHECKERBOARD = new WorldPreset(Key.key("minecraft", "checkerboard")); + public static final WorldPreset DEBUG = new WorldPreset(Key.key("minecraft", "debug")); + public static final WorldPreset FLAT = new WorldPreset(Key.key("minecraft", "flat")); + public static final WorldPreset LARGE_BIOMES = new WorldPreset(Key.key("minecraft", "large_biomes")); + public static final WorldPreset NORMAL = new WorldPreset(Key.key("minecraft", "noise")); + public static final WorldPreset SINGLE_BIOME = new WorldPreset(Key.key("minecraft", "fixed")); +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 3f2a927d..6dd3a2d1 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.model.LevelExtras; +import net.thenextlvl.worlds.model.WorldPreset; import net.thenextlvl.worlds.preset.Biome; import net.thenextlvl.worlds.preset.Layer; import net.thenextlvl.worlds.preset.Preset; @@ -19,6 +20,7 @@ import java.io.File; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.Predicate; @@ -85,10 +87,10 @@ public World.Environment getEnvironment(File level) { var dimension = dimensions.flatMap(tag -> tag.optional(getDimension(tag, environment))); var generator = dimension.flatMap(tag -> tag.optional("generator")); - var type = generator.flatMap(this::getWorldType); + var worldPreset = generator.flatMap(this::getWorldPreset); - var generatorSettings = type.filter(worldType -> worldType.equals(WorldType.FLAT)) - .flatMap(worldType -> generator.flatMap(this::getSettings)); + var generatorSettings = worldPreset.filter(preset -> preset.equals(WorldPreset.FLAT)) + .flatMap(worldType -> generator.flatMap(this::getFlatPreset)); var hardcore = data.flatMap(tag -> tag.optional("hardcore")) .orElseThrow(() -> new NoSuchElementException("hardcore")) @@ -112,13 +114,20 @@ public World.Environment getEnvironment(File level) { .generateStructures(structures) .hardcore(hardcore) .seed(seed) - .type(type.orElse(WorldType.NORMAL)); + .type(typeOf(worldPreset.orElse(WorldPreset.NORMAL))); - generatorSettings.ifPresent(preset -> creator.generator(preset.serialize().toString())); + generatorSettings.ifPresent(preset -> creator.generatorSettings(preset.serialize().toString())); return creator.createWorld(); } + private WorldType typeOf(WorldPreset worldPreset) { + if (worldPreset.equals(WorldPreset.AMPLIFIED)) return WorldType.AMPLIFIED; + if (worldPreset.equals(WorldPreset.FLAT)) return WorldType.FLAT; + if (worldPreset.equals(WorldPreset.LARGE_BIOMES)) return WorldType.LARGE_BIOMES; + return WorldType.NORMAL; + } + public Optional getExtras(CompoundTag data) { return data.optional("BukkitValues") .map(Tag::getAsCompound) @@ -134,7 +143,7 @@ public Optional getExtras(CompoundTag data) { }); } - public Optional getSettings(CompoundTag generator) { + public Optional getFlatPreset(CompoundTag generator) { var settings = generator.optional("settings"); if (settings.isEmpty()) return Optional.empty(); @@ -154,18 +163,18 @@ public Optional getSettings(CompoundTag generator) { .map(Tag::getAsBoolean) .ifPresent(preset::lakes); - settings.flatMap(tag -> tag.optional("layers")) - .map(tag -> tag.getAsList().stream().map(layer -> { + settings.flatMap(tag -> tag.>optional("layers")) + .map(tag -> tag.stream().map(layer -> { var block = layer.optional("block").orElseThrow().getAsString(); var height = layer.optional("height").orElseThrow().getAsInt(); return new Layer(block, height); - }).collect(Collectors.toSet())) + }).collect(Collectors.toCollection(LinkedHashSet::new))) .ifPresent(preset::layers); - settings.flatMap(tag -> tag.optional("structure_overrides")) - .map(tag -> tag.getAsList().stream() + settings.flatMap(tag -> tag.>optional("structure_overrides")) + .map(tag -> tag.getAsList().stream() .map(structure -> new Structure(structure.getAsString())) - .collect(Collectors.toSet())) + .collect(Collectors.toCollection(LinkedHashSet::new))) .ifPresent(preset::structures); return Optional.of(preset); @@ -189,12 +198,36 @@ public String getDimension(CompoundTag dimensions, World.Environment environment }; } - public Optional getWorldType(CompoundTag generator) { - return getGeneratorType(generator).map(string -> switch (string) { - case "minecraft:noise", "minecraft:debug" -> WorldType.NORMAL; - case "minecraft:flat" -> WorldType.FLAT; - default -> throw new IllegalArgumentException("Unexpected generator type: " + string); - }); + public Optional getWorldPreset(CompoundTag generator) { + + var settings = getGeneratorSettings(generator); + if (settings.filter(s -> s.equals(WorldPreset.LARGE_BIOMES.key().asString())).isPresent()) + return Optional.of(WorldPreset.LARGE_BIOMES); + if (settings.filter(s -> s.equals(WorldPreset.AMPLIFIED.key().asString())).isPresent()) + return Optional.of(WorldPreset.AMPLIFIED); + + var type = generator.optional("biome_source") + .flatMap(tag -> tag.optional("type")) + .map(Tag::getAsString); + + if (type.filter(s -> s.equals(WorldPreset.SINGLE_BIOME.key().asString())).isPresent()) + return Optional.of(WorldPreset.SINGLE_BIOME); + if (type.filter(s -> s.equals(WorldPreset.CHECKERBOARD.key().asString())).isPresent()) + return Optional.of(WorldPreset.CHECKERBOARD); + + var generatorType = getGeneratorType(generator); + if (generatorType.filter(s -> s.equals(WorldPreset.DEBUG.key().asString())).isPresent()) + return Optional.of(WorldPreset.DEBUG); + if (generatorType.filter(s -> s.equals(WorldPreset.FLAT.key().asString())).isPresent()) + return Optional.of(WorldPreset.FLAT); + if (generatorType.filter(s -> s.equals(WorldPreset.NORMAL.key().asString())).isPresent()) + return Optional.of(WorldPreset.NORMAL); + + return Optional.empty(); + } + + public Optional getGeneratorSettings(CompoundTag generator) { + return generator.optional("settings").filter(Tag::isString).map(Tag::getAsString); } public Optional getGeneratorType(CompoundTag generator) { From 70448f5cf3b65f56acb4ab196c81bbd6e8112fa1 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:47:15 +0200 Subject: [PATCH 123/182] Fix level loading errors on server startup Handle exceptions during level loading in ServerLoadEvent. Log an error message if a level fails to load, with details of the level's path and the exception encountered. --- .../net/thenextlvl/worlds/listener/ServerListener.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java index e84e36b1..3427ecc5 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java @@ -16,6 +16,12 @@ public void onServerLoad(ServerLoadEvent event) { if (!event.getType().equals(ServerLoadEvent.LoadType.STARTUP)) return; plugin.levelView().listLevels() .filter(plugin.levelView()::canLoad) - .forEach(plugin.levelView()::loadLevel); + .forEach(level -> { + try { + var world = plugin.levelView().loadLevel(level); + } catch (Exception e) { + plugin.getComponentLogger().error("Could not load level {}", level.getPath(), e); + } + }); } } From 7d4761350329d05e3f2b1b2f1ad42aab204ee17f Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:47:54 +0200 Subject: [PATCH 124/182] Add teleport cause in world commands Updated WorldImportCommand, WorldLoadCommand, WorldRegenerateCommand, and WorldCloneCommand to specify the teleport cause as COMMAND when using teleportAsync and teleport methods. This ensures consistent and explicit handling of teleportation events triggered by these commands. --- .../net/thenextlvl/worlds/command/WorldCloneCommand.java | 4 +++- .../net/thenextlvl/worlds/command/WorldImportCommand.java | 4 +++- .../net/thenextlvl/worlds/command/WorldLoadCommand.java | 4 +++- .../thenextlvl/worlds/command/WorldRegenerateCommand.java | 6 ++++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java index ad6918a8..df889062 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCloneCommand.java @@ -21,6 +21,8 @@ import java.io.FileOutputStream; import java.io.IOException; +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; + @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldCloneCommand { @@ -46,7 +48,7 @@ private int clone(CommandContext context, boolean full) { var message = clone != null ? "world.clone.success" : "world.clone.failed"; if (clone != null && context.getSource().getSender() instanceof Player player) - player.teleportAsync(clone.getSpawnLocation()); + player.teleportAsync(clone.getSpawnLocation(), COMMAND); plugin.bundle().sendMessage(context.getSource().getSender(), message, placeholder); return clone != null ? Command.SINGLE_SUCCESS : 0; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 3b3e49c7..caf62138 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -18,6 +18,8 @@ import java.io.File; import java.util.Optional; +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; + @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldImportCommand { @@ -46,7 +48,7 @@ private int execute(CommandContext context, @Nullable World. plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : name)); if (world != null && context.getSource().getSender() instanceof Entity entity) - entity.teleportAsync(world.getSpawnLocation()); + entity.teleportAsync(world.getSpawnLocation(), COMMAND); return world != null ? Command.SINGLE_SUCCESS : 0; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java index c2f27c4a..f54d53b6 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLoadCommand.java @@ -14,6 +14,8 @@ import java.io.File; +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; + @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldLoadCommand { @@ -36,7 +38,7 @@ private int load(CommandContext context) { plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : name)); if (world != null && context.getSource().getSender() instanceof Entity entity) - entity.teleportAsync(world.getSpawnLocation()); + entity.teleportAsync(world.getSpawnLocation(), COMMAND); return world != null ? Command.SINGLE_SUCCESS : 0; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java index 70fbb72d..7b7bfd75 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java @@ -16,6 +16,8 @@ import java.io.File; import java.util.Set; +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; + @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") class WorldRegenerateCommand { @@ -63,7 +65,7 @@ private String regenerateNow(World world) { var players = world.getPlayers(); var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); - players.forEach(player -> player.teleport(fallback)); + players.forEach(player -> player.teleport(fallback, COMMAND)); if (!plugin.getServer().unloadWorld(world, false)) return "world.unload.failed"; @@ -72,7 +74,7 @@ private String regenerateNow(World world) { var regenerated = plugin.levelView().loadLevel(worldFolder, environment); if (regenerated != null) players.forEach(player -> - player.teleportAsync(regenerated.getSpawnLocation())); + player.teleportAsync(regenerated.getSpawnLocation(), COMMAND)); return regenerated != null ? "world.regenerate.success" : "world.regenerate.failed"; } From e6ddc78e5a61998e79aa6e405a1d324d45e4a181 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:48:19 +0200 Subject: [PATCH 125/182] Add detailed world creation command with configurable options This update enhances the world creation command to allow specifying various world parameters such as generator, preset, type, dimension, structures, and seed. The new functionality increases flexibility and allows more fine-grained control over world generation. --- .../worlds/command/WorldCreateCommand.java | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index 32756582..c9a36bed 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -1,12 +1,28 @@ package net.thenextlvl.worlds.command; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.argument.*; +import net.thenextlvl.worlds.preset.Preset; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.ThreadLocalRandom; + +import static org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -16,9 +32,93 @@ class WorldCreateCommand { ArgumentBuilder create() { return Commands.literal("create") .requires(source -> source.getSender().hasPermission("worlds.command.create")) - .then(Commands.argument("key", ArgumentTypes.key()) + .then(Commands.argument("key", ArgumentTypes.namespacedKey()) + .then(Commands.literal("generator") + .then(Commands.argument("generator", new GeneratorArgument(plugin)) + .executes(context -> createGenerator(context, World.Environment.NORMAL, + true, ThreadLocalRandom.current().nextLong())) + .then(tree(this::createGenerator)))) + .then(Commands.literal("preset") + .then(Commands.argument("preset", new WorldPresetArgument(plugin)) + .executes(context -> createPreset(context, World.Environment.NORMAL, + true, ThreadLocalRandom.current().nextLong())) + .then(tree(this::createPreset)))) + .then(Commands.literal("type") + .then(Commands.argument("type", new WorldTypeArgument(plugin)) + .executes(context -> createType(context, World.Environment.NORMAL, + true, ThreadLocalRandom.current().nextLong())) + .then(tree(this::createType)))) + .executes(context -> create(context, World.Environment.NORMAL, true, + ThreadLocalRandom.current().nextLong(), WorldType.NORMAL, null, null))); + } + + private RequiredArgumentBuilder tree(Creator creator) { + return Commands.argument("dimension", new DimensionArgument(plugin)) + .then(Commands.argument("structures", BoolArgumentType.bool()) + .then(Commands.argument("seed", new SeedArgument()) + .executes(context -> { + var environment = context.getArgument("dimension", World.Environment.class); + var structures = context.getArgument("structures", boolean.class); + var seed = context.getArgument("seed", long.class); + return creator.create(context, environment, structures, seed); + })) .executes(context -> { - return Command.SINGLE_SUCCESS; - })); + var environment = context.getArgument("dimension", World.Environment.class); + var structures = context.getArgument("structures", boolean.class); + return creator.create(context, environment, structures, + ThreadLocalRandom.current().nextLong()); + })) + .executes(context -> { + var environment = context.getArgument("dimension", World.Environment.class); + return creator.create(context, environment, true, ThreadLocalRandom.current().nextLong()); + }); + } + + private int create(CommandContext context, World.Environment environment, boolean structures, + long seed, WorldType worldType, @Nullable Preset preset, @Nullable GeneratorArgument.Generator generator) { + var key = context.getArgument("key", NamespacedKey.class); + var name = key.getKey(); + var creator = new WorldCreator(name, key) + .environment(environment) + .generateStructures(structures) + .seed(seed) + .type(worldType); + + if (preset != null) creator.generatorSettings(preset.serialize().toString()); + + if (generator != null) { + creator.generator(generator.plugin().getDefaultWorldGenerator(name, generator.id())); + creator.biomeProvider(generator.plugin().getDefaultBiomeProvider(name, generator.id())); + } + + var world = plugin.getServer().getWorld(creator.key()) == null + && plugin.getServer().getWorld(name) == null + ? creator.createWorld() : null; + + var message = world != null ? "world.create.success" : "world.create.failed"; + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("world", world != null ? world.key().asString() : key.asString())); + if (world != null && context.getSource().getSender() instanceof Entity entity) + entity.teleportAsync(world.getSpawnLocation(), COMMAND); + return world != null ? Command.SINGLE_SUCCESS : 0; + } + + private int createGenerator(CommandContext context, World.Environment environment, boolean structures, long seed) { + var generator = context.getArgument("generator", GeneratorArgument.Generator.class); + return create(context, environment, structures, seed, WorldType.NORMAL, null, generator); + } + + private int createPreset(CommandContext context, World.Environment environment, boolean structures, long seed) { + var preset = context.getArgument("preset", Preset.class); + return create(context, environment, structures, seed, WorldType.FLAT, preset, null); + } + + private int createType(CommandContext context, World.Environment environment, boolean structures, long seed) { + var type = context.getArgument("type", WorldType.class); + return create(context, environment, structures, seed, type, null, null); + } + + private interface Creator { + int create(CommandContext context, World.Environment environment, boolean structures, long seed); } } From dcbbca380586233a642a9927c20b34e4d570f5a4 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:48:53 +0200 Subject: [PATCH 126/182] Enhance WorldInfoCommand with detailed world type information Added support for displaying both the new world preset and the deprecated world type in the command output. This ensures users receive comprehensive details about the world's generation settings. --- .../worlds/command/WorldInfoCommand.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index 15124c9b..b156d141 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -8,10 +8,13 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import net.thenextlvl.worlds.model.WorldPreset; import org.bukkit.World; +import org.bukkit.WorldType; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -39,6 +42,7 @@ class WorldInfoCommand { }); } + @SuppressWarnings("deprecation") private int list(CommandSender sender, World world) { var root = plugin.levelView().getLevelDataFile(world.getWorldFolder()).getRoot(); var data = root.optional("Data"); @@ -49,14 +53,18 @@ private int list(CommandSender sender, World world) { var generator = dimension.flatMap(tag -> tag.optional("generator")); var environment = dimensions.map(tag -> plugin.levelView().getDimension(tag, world.getEnvironment())); - var type = generator.flatMap(plugin.levelView()::getGeneratorType); + var worldPreset = generator.flatMap(tag -> plugin.levelView().getWorldPreset(tag)); plugin.bundle().sendMessage(sender, "world.info.name", - Placeholder.parsed("world", world.key().asString())); + Placeholder.parsed("world", world.key().asString()), + Placeholder.parsed("name", world.getName())); plugin.bundle().sendMessage(sender, "world.info.players", Placeholder.parsed("players", String.valueOf(world.getPlayers().size()))); plugin.bundle().sendMessage(sender, "world.info.type", - Placeholder.parsed("type", type.orElse("unknown"))); + Placeholder.parsed("type", worldPreset.map(WorldPreset::key) + .map(Key::asString).orElse("unknown")), + Placeholder.parsed("old", Optional.ofNullable(world.getWorldType()) + .orElse(WorldType.NORMAL).getName().toLowerCase())); plugin.bundle().sendMessage(sender, "world.info.dimension", Placeholder.parsed("dimension", environment.orElse("unknown"))); getGenerator(world).ifPresent(gen -> plugin.bundle().sendMessage(sender, From ba270e115b1f0a72e2180f584cc9d09ef4e461b8 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 8 Aug 2024 20:49:48 +0200 Subject: [PATCH 127/182] Fix null key handling in WorldImportCommand Address a potential NullPointerException by checking if 'key' is null before converting to a string. This ensures more robust error handling and prevents crashes when importing worlds. --- .../java/net/thenextlvl/worlds/command/WorldImportCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index caf62138..87e2911a 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -46,7 +46,8 @@ private int execute(CommandContext context, @Nullable World. : plugin.levelView().loadLevel(level, Optional::isEmpty) : null; var message = world != null ? "world.import.success" : "world.import.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, - Placeholder.parsed("world", world != null ? world.key().asString() : name)); + Placeholder.parsed("world", world != null ? world.key().asString() + : key != null ? key.asString() : name)); if (world != null && context.getSource().getSender() instanceof Entity entity) entity.teleportAsync(world.getSpawnLocation(), COMMAND); return world != null ? Command.SINGLE_SUCCESS : 0; From 1a0ca190ed69f8e22993c40ac5aec87f5133c9c9 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 13 Aug 2024 19:45:13 +0200 Subject: [PATCH 128/182] Add World linking feature with LinkController interface Introduced the `LinkController` interface and implemented it via `WorldLinkController` to manage world linking. Added the `Relative` enum to represent different world dimensions. This setup provides a foundation for defining and managing the relationships between worlds in the plugin.Add WorldLinkController initialization Introduce the WorldLinkController to manage world links within the plugin. This adds a LinkController instance to the WorldsPlugin class for enhanced functionality. --- .../worlds/link/LinkController.java | 22 +++++++ .../net/thenextlvl/worlds/link/Relative.java | 18 ++++++ .../net/thenextlvl/worlds/WorldsPlugin.java | 3 + .../controller/WorldLinkController.java | 58 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 api/src/main/java/net/thenextlvl/worlds/link/LinkController.java create mode 100644 api/src/main/java/net/thenextlvl/worlds/link/Relative.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java b/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java new file mode 100644 index 00000000..d588398f --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java @@ -0,0 +1,22 @@ +package net.thenextlvl.worlds.link; + +import org.bukkit.NamespacedKey; +import org.bukkit.PortalType; +import org.bukkit.World; + +import java.util.Optional; + +public interface LinkController { + + Optional getChild(World world, Relative relative); + + Optional getParent(World world); + + Optional getTarget(World world, Relative relative); + + Optional getTarget(World world, PortalType type); + + boolean canLink(World source, World destination); + + boolean link(World source, World destination); +} diff --git a/api/src/main/java/net/thenextlvl/worlds/link/Relative.java b/api/src/main/java/net/thenextlvl/worlds/link/Relative.java new file mode 100644 index 00000000..68190d2b --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/link/Relative.java @@ -0,0 +1,18 @@ +package net.thenextlvl.worlds.link; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; + +@Getter +@RequiredArgsConstructor +@Accessors(fluent = true) +public enum Relative implements Keyed { + OVERWORLD(Key.key("relative", "overworld")), + NETHER(Key.key("relative", "nether")), + THE_END(Key.key("relative", "the_end")); + + private final Key key; +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index c56d63d3..2ff1da5c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -9,6 +9,8 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.thenextlvl.worlds.command.WorldCommand; +import net.thenextlvl.worlds.controller.WorldLinkController; +import net.thenextlvl.worlds.link.LinkController; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.ServerListener; import net.thenextlvl.worlds.listener.WorldListener; @@ -33,6 +35,7 @@ @ParametersAreNotNullByDefault public class WorldsPlugin extends JavaPlugin { private final LevelView levelView = new LevelView(this); + private final LinkController linkController = new WorldLinkController(this); private final File presetsFolder = new File(getDataFolder(), "presets"); private final File translations = new File(getDataFolder(), "translations"); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java new file mode 100644 index 00000000..74fa081a --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java @@ -0,0 +1,58 @@ +package net.thenextlvl.worlds.controller; + +import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.link.LinkController; +import net.thenextlvl.worlds.link.Relative; +import org.bukkit.NamespacedKey; +import org.bukkit.PortalType; +import org.bukkit.World; + +import java.util.Optional; + +@RequiredArgsConstructor +public class WorldLinkController implements LinkController { + private final WorldsPlugin plugin; + + @Override + public Optional getChild(World world, Relative relative) { + return Optional.empty(); + } + + @Override + public Optional getParent(World world) { + return Optional.empty(); + } + + @Override + public Optional getTarget(World world, Relative relative) { + return Optional.empty(); + } + + @Override + public Optional getTarget(World world, PortalType type) { + return switch (type) { + case NETHER -> switch (world.getEnvironment()) { + case NORMAL, THE_END -> getTarget(world, Relative.NETHER); + case NETHER -> getTarget(world, Relative.OVERWORLD); + default -> Optional.empty(); + }; + case ENDER -> switch (world.getEnvironment()) { + case NORMAL, NETHER -> getTarget(world, Relative.THE_END); + case THE_END -> getTarget(world, Relative.OVERWORLD); + default -> Optional.empty(); + }; + default -> Optional.empty(); + }; + } + + @Override + public boolean canLink(World source, World destination) { + return false; + } + + @Override + public boolean link(World source, World destination) { + return false; + } +} From 0619632c4dc544fe6fba311a21f1567628faf88e Mon Sep 17 00:00:00 2001 From: david Date: Tue, 13 Aug 2024 19:45:28 +0200 Subject: [PATCH 129/182] Add @Nullable annotation to ScheduledTask in PortalCooldown This enhances code clarity and null-safety by explicitly marking the possibility of ScheduledTask being null. It helps to prevent null pointer exceptions and improves code maintainability. --- .../main/java/net/thenextlvl/worlds/model/PortalCooldown.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java b/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java index cd12aded..6f6751dc 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/model/PortalCooldown.java @@ -3,10 +3,11 @@ import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; import java.util.WeakHashMap; -public class PortalCooldown extends WeakHashMap { +public class PortalCooldown extends WeakHashMap { public boolean isActive(Entity entity) { return containsKey(entity); } From 0f5fee496f534056d08543e538c87037dba99e36 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 13 Aug 2024 19:45:40 +0200 Subject: [PATCH 130/182] Update portal handling logic and adjust spawn location Refactor the onEntityPortal method to use linkController for target world resolution and remove commented code. Adjust the spawn location Y coordinate for THE_END environment to 49 for correct teleportation. --- .../worlds/listener/PortalListener.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index 39f57087..2db15ad7 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -4,10 +4,7 @@ import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.model.PortalCooldown; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.PortalType; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -26,14 +23,9 @@ public class PortalListener implements Listener { @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityPortal(EntityPortalReadyEvent event) { - /* - plugin.linkRegistry().getLinks().stream() - .filter(link -> event.getPortalType().equals(link.portalType())) - .filter(link -> event.getEntity().getWorld().equals(link.source())) - .findFirst() - .map(Link::destination) - .ifPresent(event::setTargetWorld); - */ + if (event.getPortalType().equals(PortalType.CUSTOM)) return; + plugin.linkController().getTarget(event.getEntity().getWorld(), event.getPortalType()) + .map(Bukkit::getWorld).ifPresent(event::setTargetWorld); } @SuppressWarnings("UnstableApiUsage") @@ -45,7 +37,7 @@ public void onEntityPortalEnter(EntityPortalEnterEvent event) { if (!readyEvent.callEvent() || readyEvent.getTargetWorld() == null) return; if (readyEvent.getTargetWorld().getEnvironment().equals(World.Environment.THE_END)) { generateEndPlatform(readyEvent.getTargetWorld()); - var spawn = new Location(readyEvent.getTargetWorld(), 100.5, 50, 0.5, 90, 0); + var spawn = new Location(readyEvent.getTargetWorld(), 100.5, 49, 0.5, 90, 0); event.getEntity().teleportAsync(spawn, END_PORTAL); } else if (readyEvent.getTargetWorld().getEnvironment().equals(World.Environment.NETHER)) { var spawn = event.getLocation().clone(); From a58111b6694239cbe0eb5bf3d76d82b51e6b182f Mon Sep 17 00:00:00 2001 From: david Date: Tue, 13 Aug 2024 19:45:52 +0200 Subject: [PATCH 131/182] Refactor World link command handlers to improve clarity Renamed commands and updated arguments for better readability and usage. Added new command for listing world links and added a key argument to the import command. --- .../worlds/command/WorldImportCommand.java | 17 +++++++++++++---- .../worlds/command/WorldLinkCreateCommand.java | 2 +- .../worlds/command/WorldLinkListCommand.java | 15 +++++++++++++-- .../worlds/command/WorldLinkRemoveCommand.java | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 87e2911a..37e619dd 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -6,11 +6,13 @@ import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.argument.DimensionArgument; import net.thenextlvl.worlds.command.suggestion.LevelSuggestionProvider; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.entity.Entity; import org.jetbrains.annotations.Nullable; @@ -30,17 +32,24 @@ class WorldImportCommand { .requires(source -> source.getSender().hasPermission("worlds.command.import")) .then(Commands.argument("world", StringArgumentType.string()) .suggests(new LevelSuggestionProvider<>(plugin)) - .then(Commands.argument("dimension", new DimensionArgument()) + .then(Commands.argument("dimension", new DimensionArgument(plugin)) + .then(Commands.argument("key", ArgumentTypes.namespacedKey()) + .executes(context -> { + var environment = context.getArgument("dimension", World.Environment.class); + var key = context.getArgument("key", NamespacedKey.class); + return execute(context, environment, key); + })) .executes(context -> { var environment = context.getArgument("dimension", World.Environment.class); - return execute(context, environment); + return execute(context, environment, null); })) - .executes(context -> execute(context, null))); + .executes(context -> execute(context, null, null))); } - private int execute(CommandContext context, @Nullable World.Environment environment) { + private int execute(CommandContext context, @Nullable World.Environment environment, @Nullable NamespacedKey key) { var name = context.getArgument("world", String.class); var level = new File(plugin.getServer().getWorldContainer(), name); + // todo: define key var world = plugin.levelView().isLevel(level) ? environment != null ? plugin.levelView().loadLevel(level, environment, Optional::isEmpty) : plugin.levelView().loadLevel(level, Optional::isEmpty) : null; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java index d8b69d40..8f0b699b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java @@ -15,7 +15,7 @@ public class WorldLinkCreateCommand { private final WorldsPlugin plugin; ArgumentBuilder create() { - return Commands.literal("link") + return Commands.literal("create") .requires(source -> source.getSender().hasPermission("worlds.command.link.create")) .then(Commands.argument("source", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java index 47fe706d..30a41586 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java @@ -1,10 +1,16 @@ package net.thenextlvl.worlds.command; +import com.mojang.brigadier.Command; import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import lombok.RequiredArgsConstructor; +import net.minecraft.server.level.PlayerRespawnLogic; import net.thenextlvl.worlds.WorldsPlugin; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerRespawnEvent; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -12,7 +18,12 @@ public class WorldLinkListCommand { private final WorldsPlugin plugin; ArgumentBuilder create() { - return Commands.literal("link") - .requires(source -> source.getSender().hasPermission("worlds.command.link.list")); + return Commands.literal("list") + .requires(source -> source.getSender().hasPermission("worlds.command.link.list")) + .executes(this::list); + } + + private int list(CommandContext context) { + return Command.SINGLE_SUCCESS; } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java index 558e71a8..ec9a558f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -15,7 +15,7 @@ public class WorldLinkRemoveCommand { private final WorldsPlugin plugin; ArgumentBuilder create() { - return Commands.literal("link") + return Commands.literal("remove") .requires(source -> source.getSender().hasPermission("worlds.command.link.remove")) .then(Commands.argument("source", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) From fb75cb221f83dfe2083ffcc5e7fb3a1109af6b9e Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:38:56 +0200 Subject: [PATCH 132/182] Update link-related messages in properties files Revised linking and unlinking messages for better clarity and consistency in German and English properties files. Removed obsolete and redundant messages to streamline the configuration. --- plugin/src/main/resources/worlds.properties | 17 ++++++----------- .../main/resources/worlds_german.properties | 19 ++++++------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 133d62cc..cd440097 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -46,19 +46,14 @@ world.unload.failed= Failed to unload the world world.unload.success= Successfully unloaded the world world.unload.disallowed= The overworld cannot be unloaded world.unload.fallback= The fallback and target world cannot match -world.delete.nothing= There is nothing to delete world.delete.failed= Failed to delete the world world.delete.scheduled= The world will be deleted on the next restart -image.delete.failed= Failed to delete the image -link.exists= The link : -> does already exists -link.exists.not= The link does not exist -link.deleted= Deleted the link : -> -link.created= Created a new link : -> -link.list.empty= There are no links yet -link.list= Links (): -image.exists.not= An image called does not exist -environment.custom= Cannot generate world using environment custom -player.unknown= The player is not online +world.link.failed= Failed to link and +world.link.list.empty= There are no links yet +world.link.list= Links (): +world.link.success= Linked and +world.unlink.failed= Failed to unlink from +world.unlink.success= Unlinked from command.sender= You cannot use this command command.argument= Invalid command argument command.confirmation= '>Confirm your '>action, this cannot be undone! \ No newline at end of file diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index cf4d0ed4..2c94e689 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -7,7 +7,6 @@ world.type.large_biomes=Große Biome world.type.normal=Standard world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) world.save.success= Die Welt wurde gespeichert -world.save.failed= Die Welt konnte nicht gespeichert werden command.world.save.already-off= Automatisches Speichern ist bereits deaktiviert command.world.save.off= Automatisches Speichern ist jetzt deaktiviert command.world.save.already-on= Automatisches Speichern ist bereits aktiviert @@ -45,18 +44,12 @@ world.unload.disallowed= Die Oberwelt kann nicht entladen werden world.unload.fallback= Die zu löschende und Rückfallwelt können nicht die gleiche sein world.delete.failed= Die Welt konnte nicht gelöscht werden world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht -image.delete.failed= Das Abbild konnte nicht gelöscht werden -link.exists= Der Link : -> existiert bereits -link.exists.not= Der Link existiert nicht -link.deleted= Der Link : -> wurde gelöscht -link.created= Der Link : -> wurde erstellt -link.list.empty= Es existieren noch keine links -link.list= Links (): -image.exists.not= Ein Abbild mit dem namen existiert nicht -environment.custom= Custom kann nicht als umwelt genutzt werden -player.unknown= Der Spieler ist nicht online -command.permission= Darauf hast du keine rechte () +world.link.failed= und konnten nicht verbunden werden +world.link.list.empty= Es existieren noch keine links +world.link.list= Links (): +world.link.success= und sind jetzt verbunden +world.unlink.failed= konnte nicht von getrennt werden +world.unlink.success= wurde von getrennt command.sender= Du kannst diesen command nicht nutzen command.argument= Ungültiges command Argument -command.flag.combination= Du kannst und nicht kombinieren command.confirmation= Bestätige deine '>Eingabe mit '> Diese Aktion ist unwiderruflich! \ No newline at end of file From 8ca3f180fa91bee7f41e856df278659f1b4c58b4 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:39:52 +0200 Subject: [PATCH 133/182] Switch to NamespacedKey and add convenience methods. Replaced Key with NamespacedKey for relative world identifiers. Added two static methods to fetch Relative enum instances by Key or World.Environment for improved usability. --- .../net/thenextlvl/worlds/link/Relative.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/link/Relative.java b/api/src/main/java/net/thenextlvl/worlds/link/Relative.java index 68190d2b..4a8248bb 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/Relative.java +++ b/api/src/main/java/net/thenextlvl/worlds/link/Relative.java @@ -5,14 +5,34 @@ import lombok.experimental.Accessors; import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.World; + +import java.util.Arrays; +import java.util.Optional; @Getter @RequiredArgsConstructor @Accessors(fluent = true) public enum Relative implements Keyed { - OVERWORLD(Key.key("relative", "overworld")), - NETHER(Key.key("relative", "nether")), - THE_END(Key.key("relative", "the_end")); + OVERWORLD(new NamespacedKey("relative", "overworld")), + NETHER(new NamespacedKey("relative", "nether")), + THE_END(new NamespacedKey("relative", "the_end")); + + private final NamespacedKey key; + + public static Optional valueOf(Key key) { + return Arrays.stream(values()) + .filter(value -> value.key().equals(key)) + .findAny(); + } - private final Key key; + public static Optional valueOf(World.Environment environment) { + return Optional.ofNullable(switch (environment) { + case NORMAL -> Relative.OVERWORLD; + case NETHER -> Relative.NETHER; + case THE_END -> Relative.THE_END; + default -> null; + }); + } } From 2ca491c560b6832600e4c9fa9ab9f8e75558f4bf Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:40:07 +0200 Subject: [PATCH 134/182] Refactor LinkController and WorldLinkController Refactor interface and implementation in LinkController and WorldLinkController to streamline methods and improve linking. Removed redundant methods and added logic for linking and unlinking worlds based on environment. Enhanced canLink method to ensure environments do not overlap. --- .../worlds/link/LinkController.java | 9 ++-- .../controller/WorldLinkController.java | 53 ++++++++++++++----- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java b/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java index d588398f..4329421d 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java +++ b/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java @@ -7,16 +7,15 @@ import java.util.Optional; public interface LinkController { - - Optional getChild(World world, Relative relative); - - Optional getParent(World world); + Optional getTarget(World world, PortalType type); Optional getTarget(World world, Relative relative); - Optional getTarget(World world, PortalType type); + Optional getTarget(World world, World.Environment type); boolean canLink(World source, World destination); boolean link(World source, World destination); + + boolean unlink(World source, Relative relative); } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java index 74fa081a..ebfb9c47 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java @@ -10,23 +10,17 @@ import java.util.Optional; +import static org.bukkit.persistence.PersistentDataType.STRING; + @RequiredArgsConstructor public class WorldLinkController implements LinkController { private final WorldsPlugin plugin; - @Override - public Optional getChild(World world, Relative relative) { - return Optional.empty(); - } - - @Override - public Optional getParent(World world) { - return Optional.empty(); - } - @Override public Optional getTarget(World world, Relative relative) { - return Optional.empty(); + return Optional.ofNullable(world.getPersistentDataContainer() + .get(relative.key(), STRING) + ).map(NamespacedKey::fromString); } @Override @@ -46,13 +40,46 @@ public Optional getTarget(World world, PortalType type) { }; } + @Override + public Optional getTarget(World world, World.Environment type) { + return switch (type) { + case NETHER -> getTarget(world, Relative.NETHER); + case THE_END -> getTarget(world, Relative.THE_END); + case NORMAL -> getTarget(world, Relative.OVERWORLD); + default -> Optional.empty(); + }; + } + @Override public boolean canLink(World source, World destination) { - return false; + return !source.getEnvironment().equals(destination.getEnvironment()) + && getTarget(source, destination.getEnvironment()).isEmpty() + && getTarget(destination, source.getEnvironment()).isEmpty(); } @Override public boolean link(World source, World destination) { - return false; + if (!canLink(source, destination)) return false; + var child = switch (destination.getEnvironment()) { + case NETHER -> Relative.NETHER.key(); + case THE_END -> Relative.THE_END.key(); + default -> null; + }; + var parent = Relative.valueOf(source.getEnvironment()).map(Relative::key); + if (child == null || parent.isEmpty()) return false; + destination.getPersistentDataContainer().set(parent.get(), STRING, source.key().asString()); + source.getPersistentDataContainer().set(child, STRING, destination.key().asString()); + return true; + } + + @Override + public boolean unlink(World source, Relative relative) { + var world = getTarget(source, relative).map(plugin.getServer()::getWorld); + var parent = Relative.valueOf(source.getEnvironment()).map(Relative::key); + parent.ifPresent(key -> world.ifPresent(destination -> { + destination.getPersistentDataContainer().remove(key); + source.getPersistentDataContainer().remove(relative.key()); + })); + return world.isPresent(); } } From b68b08bf43febed107a49a0a149ede5e1f02feeb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:40:17 +0200 Subject: [PATCH 135/182] Add RelativeArgument and RelativeSuggestionProvider Introduce `RelativeArgument` to wrap argument types and handle relative values. Implement `RelativeSuggestionProvider` to suggest values for relative arguments, enhancing the command suggestion capabilities. --- .../command/argument/RelativeArgument.java | 16 ++++++++++++++ .../RelativeSuggestionProvider.java | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java new file mode 100644 index 00000000..f6153865 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java @@ -0,0 +1,16 @@ +package net.thenextlvl.worlds.command.argument; + +import core.paper.command.WrappedArgumentType; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import net.kyori.adventure.key.Key; +import net.thenextlvl.worlds.command.suggestion.RelativeSuggestionProvider; +import net.thenextlvl.worlds.link.Relative; + +@SuppressWarnings("UnstableApiUsage") +public class RelativeArgument extends WrappedArgumentType { + public RelativeArgument() { + super(ArgumentTypes.key(), (reader, type) -> Relative.valueOf(type).orElseThrow(() -> + new IllegalArgumentException("Unknown relative: " + type.asString())), + new RelativeSuggestionProvider()); + } +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java new file mode 100644 index 00000000..e5177326 --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java @@ -0,0 +1,21 @@ +package net.thenextlvl.worlds.command.suggestion; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import core.paper.command.SuggestionProvider; +import net.thenextlvl.worlds.link.Relative; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +public class RelativeSuggestionProvider implements SuggestionProvider { + @Override + public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { + Arrays.stream(Relative.values()) + .map(relative -> relative.key().asString()) + .filter(s -> s.contains(builder.getRemaining())) + .forEach(builder::suggest); + return builder.buildFuture(); + } +} From 9edc6dd42ae822094dbdc57d2d5941053125f14f Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:40:41 +0200 Subject: [PATCH 136/182] Add logic to execute world linking command Previously, the command only returned a success status without any actual functionality. The new implementation retrieves source and destination worlds, creates a link, and sends feedback messages to the user based on the success of the operation. --- .../worlds/command/WorldLinkCreateCommand.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java index 8f0b699b..6496e4dd 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java @@ -6,8 +6,10 @@ import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import org.bukkit.World; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -21,7 +23,14 @@ public class WorldLinkCreateCommand { .suggests(new WorldSuggestionProvider<>(plugin)) .then(Commands.argument("destination", ArgumentTypes.world()) .executes(context -> { - return Command.SINGLE_SUCCESS; + var source = context.getArgument("source", World.class); + var destination = context.getArgument("destination", World.class); + var link = plugin.linkController().link(source, destination); + var message = link ? "world.link.success" : "world.link.failed"; + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("source", source.key().asString()), + Placeholder.parsed("destination", destination.key().asString())); + return link ? Command.SINGLE_SUCCESS : 0; }))); } } From 64e4c2ca4ce224461a4eecbe03005692159bca1a Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 17:41:01 +0200 Subject: [PATCH 137/182] Refactor WorldLinkRemoveCommand argument handling Replaced the 'destination' argument with 'relative' and added logic to handle unlinking a world. Updated messages to reflect the success or failure of the unlinking operation for better feedback. --- .../worlds/command/WorldLinkRemoveCommand.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java index ec9a558f..c9a27ee6 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -6,8 +6,12 @@ import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.command.argument.RelativeArgument; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; +import net.thenextlvl.worlds.link.Relative; +import org.bukkit.World; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -17,11 +21,18 @@ public class WorldLinkRemoveCommand { ArgumentBuilder create() { return Commands.literal("remove") .requires(source -> source.getSender().hasPermission("worlds.command.link.remove")) - .then(Commands.argument("source", ArgumentTypes.world()) + .then(Commands.argument("world", ArgumentTypes.world()) .suggests(new WorldSuggestionProvider<>(plugin)) - .then(Commands.argument("destination", ArgumentTypes.world()) + .then(Commands.argument("relative", new RelativeArgument()) .executes(context -> { - return Command.SINGLE_SUCCESS; + var world = context.getArgument("world", World.class); + var relative = context.getArgument("relative", Relative.class); + var unlink = plugin.linkController().unlink(world, relative); + var message = unlink ? "world.unlink.success" : "world.unlink.failed"; + plugin.bundle().sendMessage(context.getSource().getSender(), message, + Placeholder.parsed("relative", relative.key().asString()), + Placeholder.parsed("world", world.key().asString())); + return unlink ? Command.SINGLE_SUCCESS : 0; }))); } } From e28b31bd2f8f53b5067516f58254dce2433b9d8f Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:11:55 +0200 Subject: [PATCH 138/182] Refactor canLink method in WorldLinkController Simplified the conditions in the canLink method to improve clarity, ensuring only links between NORMAL and non-NORMAL environments are considered. Refined the linking process to use predefined keys, removing unnecessary parent-child key checks. --- .../worlds/controller/WorldLinkController.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java index ebfb9c47..2b132e49 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java @@ -52,9 +52,9 @@ public Optional getTarget(World world, World.Environment type) { @Override public boolean canLink(World source, World destination) { - return !source.getEnvironment().equals(destination.getEnvironment()) - && getTarget(source, destination.getEnvironment()).isEmpty() - && getTarget(destination, source.getEnvironment()).isEmpty(); + return source.getEnvironment().equals(World.Environment.NORMAL) + && !destination.getEnvironment().equals(World.Environment.NORMAL) + && getTarget(source, destination.getEnvironment()).isEmpty(); } @Override @@ -65,9 +65,8 @@ public boolean link(World source, World destination) { case THE_END -> Relative.THE_END.key(); default -> null; }; - var parent = Relative.valueOf(source.getEnvironment()).map(Relative::key); - if (child == null || parent.isEmpty()) return false; - destination.getPersistentDataContainer().set(parent.get(), STRING, source.key().asString()); + if (child == null) return false; + destination.getPersistentDataContainer().set(Relative.OVERWORLD.key(), STRING, source.key().asString()); source.getPersistentDataContainer().set(child, STRING, destination.key().asString()); return true; } From 2d638b7cdbab8d1419b5d12f949780cdc70b5eba Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:12:03 +0200 Subject: [PATCH 139/182] Add filtering to command argument and suggestion providers Update `RelativeArgument` and `RelativeSuggestionProvider` to include filtering predicates. Extend `WorldSuggestionProvider` with a filter for more controlled suggestions. --- .../worlds/command/argument/RelativeArgument.java | 6 ++++-- .../command/suggestion/RelativeSuggestionProvider.java | 6 ++++++ .../worlds/command/suggestion/WorldSuggestionProvider.java | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java index f6153865..4332c9be 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java @@ -6,11 +6,13 @@ import net.thenextlvl.worlds.command.suggestion.RelativeSuggestionProvider; import net.thenextlvl.worlds.link.Relative; +import java.util.function.Predicate; + @SuppressWarnings("UnstableApiUsage") public class RelativeArgument extends WrappedArgumentType { - public RelativeArgument() { + public RelativeArgument(Predicate filter) { super(ArgumentTypes.key(), (reader, type) -> Relative.valueOf(type).orElseThrow(() -> new IllegalArgumentException("Unknown relative: " + type.asString())), - new RelativeSuggestionProvider()); + new RelativeSuggestionProvider(filter)); } } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java index e5177326..eaecfe1c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java @@ -4,15 +4,21 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import core.paper.command.SuggestionProvider; +import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.link.Relative; import java.util.Arrays; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; +@RequiredArgsConstructor public class RelativeSuggestionProvider implements SuggestionProvider { + private final Predicate filter; + @Override public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { Arrays.stream(Relative.values()) + .filter(filter) .map(relative -> relative.key().asString()) .filter(s -> s.contains(builder.getRemaining())) .forEach(builder::suggest); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java index 03434121..5f08f9ee 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/WorldSuggestionProvider.java @@ -4,20 +4,26 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import net.kyori.adventure.key.Key; import org.bukkit.Keyed; +import org.bukkit.World; import org.bukkit.plugin.Plugin; import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; +@AllArgsConstructor @RequiredArgsConstructor public class WorldSuggestionProvider implements SuggestionProvider { private final Plugin plugin; + private Predicate filter = world -> true; @Override public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { plugin.getServer().getWorlds().stream() + .filter(filter) .map(Keyed::key) .map(Key::asString) .filter(s -> s.contains(builder.getRemaining())) From 712f500c5b763d3206e01615626d2c40c6d7d5e5 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:12:52 +0200 Subject: [PATCH 140/182] Fix message in scheduleRegeneration return value. Changed the return value from "world.delete.scheduled" to "world.regenerate.scheduled" to more accurately reflect the action being taken. This ensures that the returned message is consistent with the regeneration process initiated. --- .../net/thenextlvl/worlds/command/WorldRegenerateCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java index 7b7bfd75..a0990b7d 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java @@ -81,7 +81,7 @@ private String regenerateNow(World world) { private String scheduleRegeneration(World world) { Runtime.getRuntime().addShutdownHook(new Thread(() -> regenerate(world.getWorldFolder()))); - return "world.delete.scheduled"; + return "world.regenerate.scheduled"; } private void regenerate(File level) { From a296391c48cb1f197e56573ed6ba27e8bf75b32d Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:13:09 +0200 Subject: [PATCH 141/182] Add message for scheduled world regeneration This commit introduces a new property to notify users that a world is scheduled for regeneration upon the next restart. The update includes both English and German translations. --- plugin/src/main/resources/worlds.properties | 1 + plugin/src/main/resources/worlds_german.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index cd440097..b096bdf4 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -37,6 +37,7 @@ world.info.type= Type: ( Dimension: world.info.generator= Generator: world.info.seed= Seed: '>'> +world.regenerate.scheduled= The world will be regenerated on the next restart world.regenerate.disallowed= The overworld can only be scheduled for regeneration world.regenerate.success= Successfully regenerated the world world.regenerate.failed= Failed to regenerate the world diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 2c94e689..1c7c7aae 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -36,6 +36,7 @@ world.info.seed= Startwert: The overworld can only be scheduled for regeneration world.regenerate.success= Successfully regenerated the world world.regenerate.failed= Failed to regenerate the world +world.regenerate.scheduled= Die Welt wird beim nächsten Neustart regeneriert world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden world.delete.success= Die Welt wurde erfolgreich gelöscht world.unload.failed= Die Welt konnte nicht entladen werden From a0293526db1a98141dbd00f7d5a6f0be6dcfbc94 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:13:37 +0200 Subject: [PATCH 142/182] Update German translations in worlds_german.properties Corrected terminology and added missing translations to ensure consistency and clarity in the German language file. This includes updates to spawn points and regeneration messages. --- plugin/src/main/resources/worlds_german.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 1c7c7aae..8bfae0f0 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -25,18 +25,18 @@ world.clone.success= Die Welt wurde erfolgr world.clone.failed= Die Welt konnte nicht geklont werden world.load.success= Die Welt wurde erfolgreich geladen world.load.failed= Die Welt konnte nicht geladen werden -world.spawn.set.success= Der spawn ist jetzt bei , , [] -world.spawn.set.failed= Der spawn konnte nicht neu gesetzt werden +world.spawn.set.success= Der Welteinstiegspunk ist jetzt bei , , [] +world.spawn.set.failed= Der Welteinstiegspunk konnte nicht neu gesetzt werden world.info.name= Name: () world.info.players= Spieler: world.info.type= Typ: () world.info.dimension= Dimension: world.info.generator= Generator: world.info.seed= Startwert: '>'> -world.regenerate.disallowed= The overworld can only be scheduled for regeneration -world.regenerate.success= Successfully regenerated the world -world.regenerate.failed= Failed to regenerate the world world.regenerate.scheduled= Die Welt wird beim nächsten Neustart regeneriert +world.regenerate.disallowed= Die Oberwelt kann nur zur Regeneration eingeplant werden +world.regenerate.success= Die Welt wurde erfolgreich regeneriert +world.regenerate.failed= Die Welt konnte nicht regeneriert werden world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden world.delete.success= Die Welt wurde erfolgreich gelöscht world.unload.failed= Die Welt konnte nicht entladen werden From 328f745c1a51b79877259148d9ccbcdbe84613eb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 18:13:50 +0200 Subject: [PATCH 143/182] Enhance command argument suggestions based on world environment Updated the WorldLinkCreateCommand and WorldLinkRemoveCommand to suggest arguments based on the world environment. This ensures that only appropriate worlds are suggested for source, destination, and relative arguments, improving the user experience and command accuracy. --- .../thenextlvl/worlds/command/WorldLinkCreateCommand.java | 5 ++++- .../thenextlvl/worlds/command/WorldLinkRemoveCommand.java | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java index 6496e4dd..7cbd2469 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCreateCommand.java @@ -20,8 +20,11 @@ public class WorldLinkCreateCommand { return Commands.literal("create") .requires(source -> source.getSender().hasPermission("worlds.command.link.create")) .then(Commands.argument("source", ArgumentTypes.world()) - .suggests(new WorldSuggestionProvider<>(plugin)) + .suggests(new WorldSuggestionProvider<>(plugin, world -> + world.getEnvironment().equals(World.Environment.NORMAL))) .then(Commands.argument("destination", ArgumentTypes.world()) + .suggests(new WorldSuggestionProvider<>(plugin, world -> + !world.getEnvironment().equals(World.Environment.NORMAL))) .executes(context -> { var source = context.getArgument("source", World.class); var destination = context.getArgument("destination", World.class); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java index c9a27ee6..699661dc 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -22,8 +22,10 @@ public class WorldLinkRemoveCommand { return Commands.literal("remove") .requires(source -> source.getSender().hasPermission("worlds.command.link.remove")) .then(Commands.argument("world", ArgumentTypes.world()) - .suggests(new WorldSuggestionProvider<>(plugin)) - .then(Commands.argument("relative", new RelativeArgument()) + .suggests(new WorldSuggestionProvider<>(plugin, world -> + world.getEnvironment().equals(World.Environment.NORMAL))) + .then(Commands.argument("relative", new RelativeArgument(relative -> + !relative.equals(Relative.OVERWORLD))) .executes(context -> { var world = context.getArgument("world", World.class); var relative = context.getArgument("relative", Relative.class); From 798c4012c0a643326fb5d74111af56c35f3435d1 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:18:54 +0200 Subject: [PATCH 144/182] Remove WorldListener and update persistence logic Removed the WorldListener class and replaced the world persistence mechanism. Persistence is now handled directly within WorldsPlugin with an additional parameter to control the enabled state. This streamlines event handling and centralizes persistence logic. --- .../net/thenextlvl/worlds/WorldsPlugin.java | 11 ++--------- .../worlds/listener/WorldListener.java | 18 ------------------ 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 2ff1da5c..9fe2ae19 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -13,7 +13,6 @@ import net.thenextlvl.worlds.link.LinkController; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.ServerListener; -import net.thenextlvl.worlds.listener.WorldListener; import net.thenextlvl.worlds.preset.Presets; import net.thenextlvl.worlds.version.PluginVersionChecker; import net.thenextlvl.worlds.view.LevelView; @@ -67,7 +66,6 @@ public void onEnable() { @Override public void onDisable() { metrics().shutdown(); - persistWorlds(); unloadWorlds(); } @@ -78,21 +76,16 @@ private void unloadWorlds() { }); } - private void persistWorlds() { - getServer().getWorlds().forEach(this::persistWorld); - } - - public void persistWorld(World world) { + public void persistWorld(World world, boolean enabled) { if (world.key().asString().equals("minecraft:overworld")) return; var container = world.getPersistentDataContainer(); container.set(new NamespacedKey("worlds", "world_key"), STRING, world.getKey().asString()); - container.set(new NamespacedKey("worlds", "enabled"), BOOLEAN, true); + container.set(new NamespacedKey("worlds", "enabled"), BOOLEAN, enabled); } private void registerListeners() { getServer().getPluginManager().registerEvents(new PortalListener(this), this); getServer().getPluginManager().registerEvents(new ServerListener(this), this); - getServer().getPluginManager().registerEvents(new WorldListener(this), this); } private void registerCommands() { diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java deleted file mode 100644 index 22aff577..00000000 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/WorldListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.thenextlvl.worlds.listener; - -import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.WorldsPlugin; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldSaveEvent; - -@RequiredArgsConstructor -public class WorldListener implements Listener { - private final WorldsPlugin plugin; - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onWorldSave(WorldSaveEvent event) { - plugin.persistWorld(event.getWorld()); - } -} From 0ea0f91958a057704c7288de012aa8cdbe558a8d Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:19:23 +0200 Subject: [PATCH 145/182] Add world and level data saving methods to LevelView Introduced methods to save world levels and level-specific data in `LevelView`. The `saveLevel` method ensures the world data is saved, while `saveLevelData` method handles specific level data, including dragon fight and custom boss events. --- .../net/thenextlvl/worlds/view/LevelView.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java index 6dd3a2d1..0bd487d2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -16,6 +16,7 @@ import org.bukkit.World; import org.bukkit.WorldCreator; import org.bukkit.WorldType; +import org.bukkit.craftbukkit.CraftWorld; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -233,4 +234,24 @@ public Optional getGeneratorSettings(CompoundTag generator) { public Optional getGeneratorType(CompoundTag generator) { return generator.optional("type").map(Tag::getAsString); } + + public void saveLevel(World world, boolean flush) { + var level = ((CraftWorld) world).getHandle(); + var oldSave = level.noSave; + level.noSave = false; + level.save(null, flush, false); + level.noSave = oldSave; + } + + public void saveLevelData(World world, boolean async) { + var level = ((CraftWorld) world).getHandle(); + if (level.getDragonFight() != null) { + level.serverLevelData.setEndDragonFightData(level.getDragonFight().saveData()); + } + level.getChunkSource().getDataStorage().save(async); + + level.serverLevelData.setWorldBorder(level.getWorldBorder().createSettings()); + level.serverLevelData.setCustomBossEvents(level.getServer().getCustomBossEvents().save(level.registryAccess())); + level.convertable.saveDataTag(level.getServer().registryAccess(), level.serverLevelData, level.getServer().getPlayerList().getSingleplayerData()); + } } From 229fe71ba6c616a594a8adb7c7cade51bb697d4e Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:19:44 +0200 Subject: [PATCH 146/182] Enhance portal handling and cancel logic Improved handling of custom portals and entity teleportation. Added cancellation and check for end portal inclusion. Refactored teleportation logic for better clarity and maintainability. --- .../worlds/listener/PortalListener.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index 2db15ad7..2deb3c37 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -25,32 +25,32 @@ public class PortalListener implements Listener { public void onEntityPortal(EntityPortalReadyEvent event) { if (event.getPortalType().equals(PortalType.CUSTOM)) return; plugin.linkController().getTarget(event.getEntity().getWorld(), event.getPortalType()) - .map(Bukkit::getWorld).ifPresent(event::setTargetWorld); + .map(Bukkit::getWorld).ifPresentOrElse(event::setTargetWorld, () -> + event.setTargetWorld(null)); } @SuppressWarnings("UnstableApiUsage") - @EventHandler(priority = EventPriority.HIGH) + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityPortalEnter(EntityPortalEnterEvent event) { - if (!event.getLocation().getBlock().getType().equals(Material.END_PORTAL)) return; + if (!event.getPortalType().equals(PortalType.ENDER)) return; + + event.setCancelled(true); + if (!cooldown.start(plugin, event.getEntity())) return; + var readyEvent = new EntityPortalReadyEvent(event.getEntity(), null, PortalType.ENDER); - if (!readyEvent.callEvent() || readyEvent.getTargetWorld() == null) return; + onEntityPortal(readyEvent); + + if (readyEvent.getTargetWorld() == null) return; + if (readyEvent.getTargetWorld().getEnvironment().equals(World.Environment.THE_END)) { generateEndPlatform(readyEvent.getTargetWorld()); var spawn = new Location(readyEvent.getTargetWorld(), 100.5, 49, 0.5, 90, 0); event.getEntity().teleportAsync(spawn, END_PORTAL); - } else if (readyEvent.getTargetWorld().getEnvironment().equals(World.Environment.NETHER)) { - var spawn = event.getLocation().clone(); - spawn.setWorld(readyEvent.getTargetWorld()); - spawn.setX(spawn.getX() * readyEvent.getTargetWorld().getCoordinateScale()); - spawn.setZ(spawn.getZ() * readyEvent.getTargetWorld().getCoordinateScale()); - event.getEntity().teleportAsync(spawn, END_PORTAL); } else if (event.getEntity() instanceof Player player) { - var location = player.getRespawnLocation(); - if (location == null || !location.getWorld().equals(readyEvent.getTargetWorld())) - player.teleportAsync(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); - else player.teleportAsync(location, END_PORTAL); - } else event.getEntity().teleportAsync(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); + if (player.getRespawnLocation() != null) player.teleportAsync(player.getRespawnLocation(), END_PORTAL); + else player.teleportAsync(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); + } else event.getEntity().teleport(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); } private void generateEndPlatform(World world) { From 8896aa4456335dad4df04bb15404188810d94b18 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:19:50 +0200 Subject: [PATCH 147/182] Persist world data on creation Ensure newly created worlds are persisted by adding calls to `persistWorld` and `saveLevelData` methods. This guarantees that world data is saved immediately upon creation, enhancing data integrity and recovery. --- .../net/thenextlvl/worlds/command/WorldCreateCommand.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index c9a36bed..ad60ac0c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -98,8 +98,15 @@ private int create(CommandContext context, World.Environment var message = world != null ? "world.create.success" : "world.create.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : key.asString())); + if (world != null && context.getSource().getSender() instanceof Entity entity) entity.teleportAsync(world.getSpawnLocation(), COMMAND); + + if (world != null) { + plugin.persistWorld(world, true); + plugin.levelView().saveLevelData(world, true); + } + return world != null ? Command.SINGLE_SUCCESS : 0; } From 67651a6c07939764791f8c5517a819712ba6e4eb Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:28:19 +0200 Subject: [PATCH 148/182] Persist world data after import Add logic to persist and save level data when a world is successfully imported. This ensures that the world's state is maintained and properly recorded. --- .../net/thenextlvl/worlds/command/WorldImportCommand.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 37e619dd..583366b6 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -53,12 +53,20 @@ private int execute(CommandContext context, @Nullable World. var world = plugin.levelView().isLevel(level) ? environment != null ? plugin.levelView().loadLevel(level, environment, Optional::isEmpty) : plugin.levelView().loadLevel(level, Optional::isEmpty) : null; + var message = world != null ? "world.import.success" : "world.import.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, Placeholder.parsed("world", world != null ? world.key().asString() : key != null ? key.asString() : name)); + if (world != null && context.getSource().getSender() instanceof Entity entity) entity.teleportAsync(world.getSpawnLocation(), COMMAND); + + if (world != null) { + plugin.persistWorld(world, true); + plugin.levelView().saveLevelData(world, true); + } + return world != null ? Command.SINGLE_SUCCESS : 0; } } From 808da3a8187495a8674e2d89bfd71004ba2e1acf Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:29:07 +0200 Subject: [PATCH 149/182] Remove Ender Dragon boss bar before world regeneration and deletion Ensure that the Ender Dragon boss bar is removed when regenerating or deleting a world. This prevents potential issues related to the persistent boss bar visuals. --- .../net/thenextlvl/worlds/command/WorldDeleteCommand.java | 4 ++++ .../net/thenextlvl/worlds/command/WorldRegenerateCommand.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java index cdaa74e4..c22c5dbd 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldDeleteCommand.java @@ -51,6 +51,10 @@ private int delete(CommandContext context) { } private String delete(World world, boolean schedule) { + + var dragonBattle = world.getEnderDragonBattle(); + if (dragonBattle != null) dragonBattle.getBossBar().removeAll(); + return schedule ? scheduleDeletion(world) : deleteNow(world); } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java index a0990b7d..a770e070 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java @@ -53,6 +53,10 @@ private int regenerate(CommandContext context) { } private String regenerate(World world, boolean schedule) { + + var dragonBattle = world.getEnderDragonBattle(); + if (dragonBattle != null) dragonBattle.getBossBar().removeAll(); + return schedule ? scheduleRegeneration(world) : regenerateNow(world); } From 4d802faa7431b965382713b7c0ef061b6928d4fd Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:29:23 +0200 Subject: [PATCH 150/182] Refactor WorldSaveCommand to use new saveLevel method Replaces direct manipulation of CraftWorld internals with a call to the plugin's saveLevel method. This change enhances code readability, encapsulation, and maintainability by leveraging existing abstractions. --- .../net/thenextlvl/worlds/command/WorldSaveCommand.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java index 032e437a..5cd4ad9c 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveCommand.java @@ -11,7 +11,6 @@ import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import org.bukkit.World; -import org.bukkit.craftbukkit.CraftWorld; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -32,13 +31,7 @@ private int save(CommandContext context, boolean flush) { var world = context.getArgument("world", World.class); var placeholder = Placeholder.parsed("world", world.key().asString()); plugin.bundle().sendMessage(context.getSource().getSender(), "world.save", placeholder); - - var level = ((CraftWorld) world).getHandle(); - var oldSave = level.noSave; - level.noSave = false; - level.save(null, flush, false); - level.noSave = oldSave; - + plugin.levelView().saveLevel(world, flush); plugin.bundle().sendMessage(context.getSource().getSender(), "world.save.success", placeholder); return Command.SINGLE_SUCCESS; } From 5cf82d06c94ec495890b896fa97012730c8310d2 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:31:17 +0200 Subject: [PATCH 151/182] Add persisting and saving logic to world commands Implemented persisting of world data and saving level data during world unload and regenerate commands. This ensures data integrity and correct handling of auto-save settings. --- .../net/thenextlvl/worlds/command/WorldRegenerateCommand.java | 3 +++ .../net/thenextlvl/worlds/command/WorldUnloadCommand.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java index a770e070..c8cf99e2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldRegenerateCommand.java @@ -71,6 +71,9 @@ private String regenerateNow(World world) { var fallback = plugin.getServer().getWorlds().getFirst().getSpawnLocation(); players.forEach(player -> player.teleport(fallback, COMMAND)); + plugin.persistWorld(world, true); + plugin.levelView().saveLevelData(world, false); + if (!plugin.getServer().unloadWorld(world, false)) return "world.unload.failed"; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java index fe6cafae..c803e6d4 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -50,6 +50,10 @@ private String unload(World world, @Nullable World fallback) { : plugin.getServer().getWorlds().getFirst().getSpawnLocation(); world.getPlayers().forEach(player -> player.teleport(fallbackSpawn)); + plugin.persistWorld(world, false); + + if (!world.isAutoSave()) plugin.levelView().saveLevelData(world, false); + return plugin.getServer().unloadWorld(world, world.isAutoSave()) ? "world.unload.success" : "world.unload.failed"; From 100c70722e641e01b0d3c0df100af4116c53f265 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 21:31:41 +0200 Subject: [PATCH 152/182] Remove all BossBar entries on world unload Added logic to remove all EnderDragon BossBar entries when unloading a world. This ensures that the BossBar UI elements are properly cleared. --- .../java/net/thenextlvl/worlds/command/WorldUnloadCommand.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java index c803e6d4..1cf16c4e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldUnloadCommand.java @@ -52,6 +52,9 @@ private String unload(World world, @Nullable World fallback) { plugin.persistWorld(world, false); + var dragonBattle = world.getEnderDragonBattle(); + if (dragonBattle != null) dragonBattle.getBossBar().removeAll(); + if (!world.isAutoSave()) plugin.levelView().saveLevelData(world, false); return plugin.getServer().unloadWorld(world, world.isAutoSave()) From fe849b5930140c5c1feb15f3b27261db52c70a99 Mon Sep 17 00:00:00 2001 From: david Date: Wed, 14 Aug 2024 22:38:05 +0200 Subject: [PATCH 153/182] Handle End credits for CraftPlayer Replace Player with CraftPlayer to access NMS methods. Ensure End credits are shown when a player enters an End portal and has not seen them before. --- .../java/net/thenextlvl/worlds/listener/PortalListener.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java index 2deb3c37..798712f7 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/PortalListener.java @@ -6,7 +6,7 @@ import net.thenextlvl.worlds.model.PortalCooldown; import org.bukkit.*; import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; +import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -47,7 +47,8 @@ public void onEntityPortalEnter(EntityPortalEnterEvent event) { generateEndPlatform(readyEvent.getTargetWorld()); var spawn = new Location(readyEvent.getTargetWorld(), 100.5, 49, 0.5, 90, 0); event.getEntity().teleportAsync(spawn, END_PORTAL); - } else if (event.getEntity() instanceof Player player) { + } else if (event.getEntity() instanceof CraftPlayer player) { + if (!player.getHandle().seenCredits) player.getHandle().showEndCredits(); if (player.getRespawnLocation() != null) player.teleportAsync(player.getRespawnLocation(), END_PORTAL); else player.teleportAsync(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); } else event.getEntity().teleport(readyEvent.getTargetWorld().getSpawnLocation(), END_PORTAL); From ddcdb8ef967b1df51d6f70c0992b090a3b251195 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:10:24 +0200 Subject: [PATCH 154/182] Split world link lists onto new lines Update the properties files to display world link lists on separate lines for better readability. This change applies to both the German and English localization files. --- plugin/src/main/resources/worlds.properties | 2 +- plugin/src/main/resources/worlds_german.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index b096bdf4..27eeb9f1 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -51,7 +51,7 @@ world.delete.failed= Failed to delete the world world.delete.scheduled= The world will be deleted on the next restart world.link.failed= Failed to link and world.link.list.empty= There are no links yet -world.link.list= Links (): +world.link.list= Links (): world.link.success= Linked and world.unlink.failed= Failed to unlink from world.unlink.success= Unlinked from diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 8bfae0f0..5257735a 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -47,7 +47,7 @@ world.delete.failed= Die Welt konnte nicht g world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht world.link.failed= und konnten nicht verbunden werden world.link.list.empty= Es existieren noch keine links -world.link.list= Links (): +world.link.list= Links (): world.link.success= und sind jetzt verbunden world.unlink.failed= konnte nicht von getrennt werden world.unlink.success= wurde von getrennt From be2a81dc4b07f517951e3606886547ed564ce6d5 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:10:39 +0200 Subject: [PATCH 155/182] Remove unnecessary blank line The redundant blank line at the start of the file was removed to keep the code clean and organized. This helps improve readability and maintain consistency in the codebase. --- .../java/net/thenextlvl/worlds/command/WorldLinkCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java index ce2f20b5..b775cfaf 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkCommand.java @@ -1,4 +1,3 @@ - package net.thenextlvl.worlds.command; import com.mojang.brigadier.builder.ArgumentBuilder; From d88f8a2ed6c8c13e6dbbda2af50043f877211a23 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:12:22 +0200 Subject: [PATCH 156/182] Refactor world link listing command Update WorldLinkListCommand to include detailed world link information. Filter worlds by environment and generate a list of links using the Relative enum. Send appropriate messages to the command sender based on the results. --- .../worlds/command/WorldLinkListCommand.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java index 30a41586..c66a3aae 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java @@ -5,12 +5,14 @@ import com.mojang.brigadier.context.CommandContext; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import lombok.RequiredArgsConstructor; -import net.minecraft.server.level.PlayerRespawnLogic; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerRespawnEvent; +import net.thenextlvl.worlds.link.Relative; +import org.bukkit.World; + +import java.util.Arrays; +import java.util.Objects; @RequiredArgsConstructor @SuppressWarnings("UnstableApiUsage") @@ -24,6 +26,19 @@ public class WorldLinkListCommand { } private int list(CommandContext context) { - return Command.SINGLE_SUCCESS; + var sender = context.getSource().getSender(); + var links = plugin.getServer().getWorlds().stream() + .filter(world -> world.getEnvironment().equals(World.Environment.NORMAL)) + .mapMulti((world, consumer) -> Arrays.stream(Relative.values()) + .filter(relative -> !relative.equals(Relative.OVERWORLD)) + .map(relative -> plugin.linkController().getTarget(world, relative).orElse(null)) + .filter(Objects::nonNull) + .forEach(key -> consumer.accept(world.key().asString() + " <-> " + key.asString()))) + .toList(); + if (links.isEmpty()) plugin.bundle().sendMessage(sender, "world.link.list.empty"); + else plugin.bundle().sendMessage(sender, "world.link.list", + Placeholder.parsed("links", String.join(",", links)), + Placeholder.parsed("amount", String.valueOf(links.size()))); + return links.isEmpty() ? 0 : Command.SINGLE_SUCCESS; } } From 83fffd228377a2543b8c506831c10b2474415ec2 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:45:43 +0200 Subject: [PATCH 157/182] Refactor exception handling in GeneratorArgument Replaced basic exception throws with Preconditions checks for improved readability and clarity. This change ensures that null and state conditions for the generator plugin are explicitly checked before proceeding. --- .../worlds/command/argument/GeneratorArgument.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java index 4f2666a5..573f6edc 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java @@ -1,5 +1,6 @@ package net.thenextlvl.worlds.command.argument; +import com.google.common.base.Preconditions; import com.mojang.brigadier.arguments.StringArgumentType; import core.paper.command.WrappedArgumentType; import net.thenextlvl.worlds.WorldsPlugin; @@ -12,8 +13,8 @@ public GeneratorArgument(WorldsPlugin plugin) { super(StringArgumentType.string(), (reader, type) -> { var split = type.split(":", 2); var generator = plugin.getServer().getPluginManager().getPlugin(split[0]); - if (generator == null) throw new IllegalArgumentException("Unknown plugin"); - if (!generator.isEnabled()) throw new IllegalStateException("Plugin is not enabled"); + Preconditions.checkNotNull(generator, "Unknown plugin"); + Preconditions.checkState(generator.isEnabled(), "Plugin is not enabled"); return new Generator(generator, split.length > 1 ? split[1] : null); }, new GeneratorSuggestionProvider(plugin)); } From a3c578d99ea7847aa4adc2e46f515d78bd4ba334 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:46:53 +0200 Subject: [PATCH 158/182] Update view structure in WorldsPlugin Replaced LevelView with PaperLevelView and added PluginGeneratorView. This improves the modularity and clarity of view-related components in the plugin. --- .../src/main/java/net/thenextlvl/worlds/WorldsPlugin.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 9fe2ae19..f2ce1916 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -10,12 +10,15 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.thenextlvl.worlds.command.WorldCommand; import net.thenextlvl.worlds.controller.WorldLinkController; +import net.thenextlvl.worlds.view.GeneratorView; import net.thenextlvl.worlds.link.LinkController; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.ServerListener; import net.thenextlvl.worlds.preset.Presets; import net.thenextlvl.worlds.version.PluginVersionChecker; import net.thenextlvl.worlds.view.LevelView; +import net.thenextlvl.worlds.view.PaperLevelView; +import net.thenextlvl.worlds.view.PluginGeneratorView; import org.bstats.bukkit.Metrics; import org.bukkit.NamespacedKey; import org.bukkit.World; @@ -33,7 +36,9 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault public class WorldsPlugin extends JavaPlugin { - private final LevelView levelView = new LevelView(this); + private final GeneratorView generatorView = new PluginGeneratorView(); + private final LevelView levelView = new PaperLevelView(this); + private final LinkController linkController = new WorldLinkController(this); private final File presetsFolder = new File(getDataFolder(), "presets"); From 7ebfc97d24126d383a3b8a8dfc159a0658bba9e1 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:47:15 +0200 Subject: [PATCH 159/182] Fix preset folder check condition Replace list() method with isDirectory() to correctly verify if the presets folder exists. This prevents potential null pointer exceptions and ensures default presets are saved only when the folder is missing. --- plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index f2ce1916..72abc9ed 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -58,7 +58,7 @@ public class WorldsPlugin extends JavaPlugin { @Override public void onLoad() { - if (presetsFolder().list() == null) saveDefaultPresets(); + if (!presetsFolder().isDirectory()) saveDefaultPresets(); versionChecker().checkVersion(); } From bcc8cf24b66fd51ce846a070c926524e171ce7d3 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:47:45 +0200 Subject: [PATCH 160/182] Add GeneratorView interface and PluginGeneratorView class This commit introduces the GeneratorView interface and its implementation, PluginGeneratorView. The new classes provide methods to check if a plugin has custom world generators or biome providers. --- .../thenextlvl/worlds/view/GeneratorView.java | 11 ++++++++ .../worlds/view/PluginGeneratorView.java | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java create mode 100644 plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java diff --git a/api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java b/api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java new file mode 100644 index 00000000..0dd23c5a --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java @@ -0,0 +1,11 @@ +package net.thenextlvl.worlds.view; + +import org.bukkit.plugin.Plugin; + +public interface GeneratorView { + boolean hasGenerator(Plugin plugin); + + boolean hasChunkGenerator(Class clazz); + + boolean hasBiomeProvider(Class clazz); +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java new file mode 100644 index 00000000..4fc75ffc --- /dev/null +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java @@ -0,0 +1,28 @@ +package net.thenextlvl.worlds.view; + +import org.bukkit.plugin.Plugin; + +public class PluginGeneratorView implements GeneratorView { + @Override + public boolean hasGenerator(Plugin plugin) { + return hasChunkGenerator(plugin.getClass()) || hasBiomeProvider(plugin.getClass()); + } + + @Override + public boolean hasChunkGenerator(Class clazz) { + try { + return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); + } catch (NoSuchMethodException e) { + return false; + } + } + + @Override + public boolean hasBiomeProvider(Class clazz) { + try { + return clazz.getMethod("getDefaultBiomeProvider", String.class, String.class).getDeclaringClass().equals(clazz); + } catch (NoSuchMethodException e) { + return false; + } + } +} From 4c7e689e6ce17efa395fddb56148b4db9927cd81 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:48:15 +0200 Subject: [PATCH 161/182] Move model package and add null safety annotations Relocate `worlds/model` package from `plugin` to `api` for better modularity. Introduce `@NotNull` annotations to enforce non-null constraints across types, fields, parameters, and methods. --- .../java/net/thenextlvl/worlds/model/LevelExtras.java | 0 .../java/net/thenextlvl/worlds/model/WorldPreset.java | 0 .../java/net/thenextlvl/worlds/model/package-info.java | 10 ++++++++++ 3 files changed, 10 insertions(+) rename {plugin => api}/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java (100%) rename {plugin => api}/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java (100%) create mode 100644 api/src/main/java/net/thenextlvl/worlds/model/package-info.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/api/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java similarity index 100% rename from plugin/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java rename to api/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java diff --git a/plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java b/api/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java similarity index 100% rename from plugin/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java rename to api/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java diff --git a/api/src/main/java/net/thenextlvl/worlds/model/package-info.java b/api/src/main/java/net/thenextlvl/worlds/model/package-info.java new file mode 100644 index 00000000..8c0fb1a1 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/model/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@ParametersAreNotNullByDefault +@MethodsReturnNotNullByDefault +package net.thenextlvl.worlds.model; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From 6a20acd7b0665c4929990e07f1949404de67cf59 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:48:50 +0200 Subject: [PATCH 162/182] Create `LevelView` interface and add nullability annotations Introduced the `LevelView` interface in the API module to standardize level loading and management operations. Added annotations to ensure non-null fields, methods, and parameters by default in the `view` package, and renamed `LevelView.java` to `PaperLevelView.java` in the plugin module for clarity. --- .../net/thenextlvl/worlds/view/LevelView.java | 54 +++++++++++++++++++ .../thenextlvl/worlds/view/package-info.java | 10 ++++ .../{LevelView.java => PaperLevelView.java} | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/net/thenextlvl/worlds/view/LevelView.java create mode 100644 api/src/main/java/net/thenextlvl/worlds/view/package-info.java rename plugin/src/main/java/net/thenextlvl/worlds/view/{LevelView.java => PaperLevelView.java} (99%) diff --git a/api/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/api/src/main/java/net/thenextlvl/worlds/view/LevelView.java new file mode 100644 index 00000000..acef2332 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/view/LevelView.java @@ -0,0 +1,54 @@ +package net.thenextlvl.worlds.view; + +import core.nbt.file.NBTFile; +import core.nbt.tag.CompoundTag; +import net.thenextlvl.worlds.model.LevelExtras; +import net.thenextlvl.worlds.model.WorldPreset; +import net.thenextlvl.worlds.preset.Preset; +import org.bukkit.World; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public interface LevelView { + @Nullable World loadLevel(File level); + + @Nullable World loadLevel(File level, Predicate> predicate); + + @Nullable World loadLevel(File level, World.Environment environment); + + @Nullable World loadLevel(File level, World.Environment environment, Predicate> predicate); + + NBTFile getLevelDataFile(File level); + + Optional getExtras(CompoundTag data); + + Optional getFlatPreset(CompoundTag generator); + + Optional getGeneratorSettings(CompoundTag generator); + + Optional getGeneratorType(CompoundTag generator); + + Optional getWorldPreset(CompoundTag generator); + + Stream listLevels(); + + String getDimension(CompoundTag dimensions, World.Environment environment); + + World.Environment getEnvironment(File level); + + boolean canLoad(File level); + + boolean hasEndDimension(File level); + + boolean hasNetherDimension(File level); + + boolean isLevel(File file); + + void saveLevel(World world, boolean flush); + + void saveLevelData(World world, boolean async); +} diff --git a/api/src/main/java/net/thenextlvl/worlds/view/package-info.java b/api/src/main/java/net/thenextlvl/worlds/view/package-info.java new file mode 100644 index 00000000..cbc4897d --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/view/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@ParametersAreNotNullByDefault +@MethodsReturnNotNullByDefault +package net.thenextlvl.worlds.view; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java similarity index 99% rename from plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java rename to plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index 0bd487d2..851b4263 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -29,7 +29,7 @@ import java.util.stream.Stream; @RequiredArgsConstructor -public class LevelView { +public class PaperLevelView implements LevelView { private final WorldsPlugin plugin; public Stream listLevels() { From 261bea3da3d892f7a6cc1bf6bee93c2cd60a8538 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:49:18 +0200 Subject: [PATCH 163/182] Refactor generator check to use generatorView(). This change simplifies the code by removing private methods for checking generators and instead uses the generatorView() method directly. It improves readability and maintains consistency with the existing codebase. --- .../GeneratorSuggestionProvider.java | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java index cf3371e9..aebc855b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/GeneratorSuggestionProvider.java @@ -19,32 +19,10 @@ public class GeneratorSuggestionProvider implements SuggestionProvider { public CompletableFuture suggest(CommandContext context, SuggestionsBuilder builder) { Arrays.stream(plugin.getServer().getPluginManager().getPlugins()) .filter(Plugin::isEnabled) - .filter(this::hasGenerator) + .filter(plugin.generatorView()::hasGenerator) .map(Plugin::getName) .filter(s -> s.contains(builder.getRemaining())) .forEach(builder::suggest); return builder.buildFuture(); } - - private boolean hasGenerator(Plugin plugin) { - return hasChunkGenerator(plugin.getClass()) || hasBiomeProvider(plugin.getClass()); - } - - private boolean hasChunkGenerator(Class clazz) { - try { - return clazz.getMethod("getDefaultWorldGenerator", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - e.printStackTrace(); // todo remove - return false; - } - } - - private boolean hasBiomeProvider(Class clazz) { - try { - return clazz.getMethod("getDefaultBiomeProvider", String.class, String.class).getDeclaringClass().equals(clazz); - } catch (NoSuchMethodException e) { - e.printStackTrace(); // todo remove - return false; - } - } } From a04730ef0f54f4366a4253ccd2870173e3de5221 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:49:33 +0200 Subject: [PATCH 164/182] Add check for plugin generator existence Ensure that the specified plugin has an associated generator before proceeding. This enhances the validation logic and prevents potential errors during the command execution. --- .../thenextlvl/worlds/command/argument/GeneratorArgument.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java index 573f6edc..923fe26e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/GeneratorArgument.java @@ -15,6 +15,7 @@ public GeneratorArgument(WorldsPlugin plugin) { var generator = plugin.getServer().getPluginManager().getPlugin(split[0]); Preconditions.checkNotNull(generator, "Unknown plugin"); Preconditions.checkState(generator.isEnabled(), "Plugin is not enabled"); + Preconditions.checkState(plugin.generatorView().hasGenerator(generator), "Plugin has no generator"); return new Generator(generator, split.length > 1 ? split[1] : null); }, new GeneratorSuggestionProvider(plugin)); } From d9376a180e755fa458462584a9089bf7f2f50294 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:51:39 +0200 Subject: [PATCH 165/182] Add WorldDeleteEvent class to handle world deletion events Introduced the WorldDeleteEvent class to manage events related to the deletion of Minecraft worlds. This includes creating the event constructor and handler list required for proper event management in Bukkit. --- .../worlds/event/WorldDeleteEvent.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java diff --git a/api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java b/api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java new file mode 100644 index 00000000..2819613b --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java @@ -0,0 +1,19 @@ +package net.thenextlvl.worlds.event; + +import lombok.Getter; +import org.bukkit.World; +import org.bukkit.event.HandlerList; +import org.bukkit.event.world.WorldEvent; + +public class WorldDeleteEvent extends WorldEvent { + private static final @Getter HandlerList handlerList = new HandlerList(); + + public WorldDeleteEvent(World world) { + super(world, false); + } + + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } +} From 504dba300f92591cf34dadca00ec42ca6a63d1af Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:51:58 +0200 Subject: [PATCH 166/182] Add WorldRegenerateEvent class Introduce the WorldRegenerateEvent class extending WorldDeleteEvent to handle world regeneration events. This allows for better event management and modular handling of world-related actions in the system. --- .../thenextlvl/worlds/event/WorldRegenerateEvent.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java diff --git a/api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java b/api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java new file mode 100644 index 00000000..51659873 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java @@ -0,0 +1,9 @@ +package net.thenextlvl.worlds.event; + +import org.bukkit.World; + +public class WorldRegenerateEvent extends WorldDeleteEvent { + public WorldRegenerateEvent(World world) { + super(world); + } +} From 23cdcdd99076addf1410cb301627162cf16ed504 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:52:08 +0200 Subject: [PATCH 167/182] Add nullability annotations to event package Introduced `@TypesAreNotNullByDefault`, `@FieldsAreNotNullByDefault`, `@ParametersAreNotNullByDefault`, and `@MethodsReturnNotNullByDefault` annotations to ensure better null safety in the `net.thenextlvl.worlds.event` package. This change enhances code robustness by enforcing non-null standards across the package. --- .../java/net/thenextlvl/worlds/event/package-info.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 api/src/main/java/net/thenextlvl/worlds/event/package-info.java diff --git a/api/src/main/java/net/thenextlvl/worlds/event/package-info.java b/api/src/main/java/net/thenextlvl/worlds/event/package-info.java new file mode 100644 index 00000000..91533530 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/event/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@ParametersAreNotNullByDefault +@MethodsReturnNotNullByDefault +package net.thenextlvl.worlds.event; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file From bed7a6cb58fd04b3c84dc9fc1e4cd91968bd0db1 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 16:52:58 +0200 Subject: [PATCH 168/182] Refactor package structure to include 'api' subpackage. Moved classes and interfaces to the 'api' subpackage for better organization and clarity. This change affects event, link, model, preset, and view packages and ensures a more modular structure. --- .../worlds/{ => api}/event/WorldDeleteEvent.java | 2 +- .../{ => api}/event/WorldRegenerateEvent.java | 2 +- .../worlds/{model => api/event}/package-info.java | 2 +- .../worlds/{ => api}/link/LinkController.java | 2 +- .../thenextlvl/worlds/{ => api}/link/Relative.java | 2 +- .../worlds/{ => api}/model/LevelExtras.java | 2 +- .../worlds/{ => api}/model/WorldPreset.java | 2 +- .../worlds/{view => api/model}/package-info.java | 2 +- .../thenextlvl/worlds/{ => api}/preset/Biome.java | 2 +- .../thenextlvl/worlds/{ => api}/preset/Layer.java | 2 +- .../thenextlvl/worlds/{ => api}/preset/Preset.java | 6 +++--- .../worlds/{ => api}/preset/PresetFile.java | 2 +- .../thenextlvl/worlds/{ => api}/preset/Presets.java | 2 +- .../worlds/{ => api}/preset/Structure.java | 2 +- .../{ => api}/preset/adapter/BiomeTypeAdapter.java | 4 ++-- .../preset/adapter/StructureTypeAdapter.java | 4 ++-- .../{ => api}/preset/adapter/package-info.java | 2 +- .../worlds/{ => api}/preset/package-info.java | 2 +- .../worlds/{ => api}/view/GeneratorView.java | 2 +- .../thenextlvl/worlds/{ => api}/view/LevelView.java | 8 ++++---- .../worlds/{event => api/view}/package-info.java | 2 +- .../java/net/thenextlvl/worlds/WorldsPlugin.java | 8 ++++---- .../worlds/command/WorldCreateCommand.java | 2 +- .../thenextlvl/worlds/command/WorldInfoCommand.java | 2 +- .../worlds/command/WorldLinkListCommand.java | 2 +- .../worlds/command/WorldLinkRemoveCommand.java | 2 +- .../worlds/command/argument/RelativeArgument.java | 2 +- .../command/argument/WorldPresetArgument.java | 2 +- .../suggestion/RelativeSuggestionProvider.java | 2 +- .../worlds/controller/WorldLinkController.java | 4 ++-- .../net/thenextlvl/worlds/view/PaperLevelView.java | 13 +++++++------ .../thenextlvl/worlds/view/PluginGeneratorView.java | 1 + 32 files changed, 49 insertions(+), 47 deletions(-) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/event/WorldDeleteEvent.java (91%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/event/WorldRegenerateEvent.java (80%) rename api/src/main/java/net/thenextlvl/worlds/{model => api/event}/package-info.java (88%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/link/LinkController.java (93%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/link/Relative.java (96%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/model/LevelExtras.java (80%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/model/WorldPreset.java (95%) rename api/src/main/java/net/thenextlvl/worlds/{view => api/model}/package-info.java (88%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/Biome.java (95%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/Layer.java (87%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/Preset.java (94%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/PresetFile.java (95%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/Presets.java (98%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/Structure.java (89%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/adapter/BiomeTypeAdapter.java (84%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/adapter/StructureTypeAdapter.java (84%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/adapter/package-info.java (86%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/preset/package-info.java (88%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/view/GeneratorView.java (85%) rename api/src/main/java/net/thenextlvl/worlds/{ => api}/view/LevelView.java (87%) rename api/src/main/java/net/thenextlvl/worlds/{event => api/view}/package-info.java (88%) diff --git a/api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java b/api/src/main/java/net/thenextlvl/worlds/api/event/WorldDeleteEvent.java similarity index 91% rename from api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java rename to api/src/main/java/net/thenextlvl/worlds/api/event/WorldDeleteEvent.java index 2819613b..fb947221 100644 --- a/api/src/main/java/net/thenextlvl/worlds/event/WorldDeleteEvent.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/event/WorldDeleteEvent.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.event; +package net.thenextlvl.worlds.api.event; import lombok.Getter; import org.bukkit.World; diff --git a/api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java b/api/src/main/java/net/thenextlvl/worlds/api/event/WorldRegenerateEvent.java similarity index 80% rename from api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java rename to api/src/main/java/net/thenextlvl/worlds/api/event/WorldRegenerateEvent.java index 51659873..a759da78 100644 --- a/api/src/main/java/net/thenextlvl/worlds/event/WorldRegenerateEvent.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/event/WorldRegenerateEvent.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.event; +package net.thenextlvl.worlds.api.event; import org.bukkit.World; diff --git a/api/src/main/java/net/thenextlvl/worlds/model/package-info.java b/api/src/main/java/net/thenextlvl/worlds/api/event/package-info.java similarity index 88% rename from api/src/main/java/net/thenextlvl/worlds/model/package-info.java rename to api/src/main/java/net/thenextlvl/worlds/api/event/package-info.java index 8c0fb1a1..74d0fc35 100644 --- a/api/src/main/java/net/thenextlvl/worlds/model/package-info.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/event/package-info.java @@ -2,7 +2,7 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault @MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.model; +package net.thenextlvl.worlds.api.event; import core.annotation.FieldsAreNotNullByDefault; import core.annotation.MethodsReturnNotNullByDefault; diff --git a/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java b/api/src/main/java/net/thenextlvl/worlds/api/link/LinkController.java similarity index 93% rename from api/src/main/java/net/thenextlvl/worlds/link/LinkController.java rename to api/src/main/java/net/thenextlvl/worlds/api/link/LinkController.java index 4329421d..15a46020 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/LinkController.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/link/LinkController.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.link; +package net.thenextlvl.worlds.api.link; import org.bukkit.NamespacedKey; import org.bukkit.PortalType; diff --git a/api/src/main/java/net/thenextlvl/worlds/link/Relative.java b/api/src/main/java/net/thenextlvl/worlds/api/link/Relative.java similarity index 96% rename from api/src/main/java/net/thenextlvl/worlds/link/Relative.java rename to api/src/main/java/net/thenextlvl/worlds/api/link/Relative.java index 4a8248bb..7ce61c2d 100644 --- a/api/src/main/java/net/thenextlvl/worlds/link/Relative.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/link/Relative.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.link; +package net.thenextlvl.worlds.api.link; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/api/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java b/api/src/main/java/net/thenextlvl/worlds/api/model/LevelExtras.java similarity index 80% rename from api/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java rename to api/src/main/java/net/thenextlvl/worlds/api/model/LevelExtras.java index 9ef8b89c..4af0c735 100644 --- a/api/src/main/java/net/thenextlvl/worlds/model/LevelExtras.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/model/LevelExtras.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.model; +package net.thenextlvl.worlds.api.model; import org.bukkit.NamespacedKey; import org.jetbrains.annotations.Nullable; diff --git a/api/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java b/api/src/main/java/net/thenextlvl/worlds/api/model/WorldPreset.java similarity index 95% rename from api/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java rename to api/src/main/java/net/thenextlvl/worlds/api/model/WorldPreset.java index 9c466a09..11df817b 100644 --- a/api/src/main/java/net/thenextlvl/worlds/model/WorldPreset.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/model/WorldPreset.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.model; +package net.thenextlvl.worlds.api.model; import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Keyed; diff --git a/api/src/main/java/net/thenextlvl/worlds/view/package-info.java b/api/src/main/java/net/thenextlvl/worlds/api/model/package-info.java similarity index 88% rename from api/src/main/java/net/thenextlvl/worlds/view/package-info.java rename to api/src/main/java/net/thenextlvl/worlds/api/model/package-info.java index cbc4897d..4c2724f6 100644 --- a/api/src/main/java/net/thenextlvl/worlds/view/package-info.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/model/package-info.java @@ -2,7 +2,7 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault @MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.view; +package net.thenextlvl.worlds.api.model; import core.annotation.FieldsAreNotNullByDefault; import core.annotation.MethodsReturnNotNullByDefault; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/Biome.java similarity index 95% rename from api/src/main/java/net/thenextlvl/worlds/preset/Biome.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/Biome.java index 2b6f61d5..d3af2bfe 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Biome.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/Biome.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import com.google.common.base.Preconditions; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/Layer.java similarity index 87% rename from api/src/main/java/net/thenextlvl/worlds/preset/Layer.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/Layer.java index 4f065e47..a08db8f3 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Layer.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/Layer.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import org.bukkit.Material; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/Preset.java similarity index 94% rename from api/src/main/java/net/thenextlvl/worlds/preset/Preset.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/Preset.java index d3d7087e..044a8ce0 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Preset.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/Preset.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -10,8 +10,8 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import net.thenextlvl.worlds.preset.adapter.BiomeTypeAdapter; -import net.thenextlvl.worlds.preset.adapter.StructureTypeAdapter; +import net.thenextlvl.worlds.api.preset.adapter.BiomeTypeAdapter; +import net.thenextlvl.worlds.api.preset.adapter.StructureTypeAdapter; import org.bukkit.Material; import java.io.File; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/PresetFile.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/PresetFile.java similarity index 95% rename from api/src/main/java/net/thenextlvl/worlds/preset/PresetFile.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/PresetFile.java index 6cf95e89..0d06bf6a 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/PresetFile.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/PresetFile.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import com.google.gson.Gson; import com.google.gson.JsonObject; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Presets.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/Presets.java similarity index 98% rename from api/src/main/java/net/thenextlvl/worlds/preset/Presets.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/Presets.java index 5d6409cc..e078245d 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Presets.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/Presets.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import org.bukkit.Material; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/Structure.java similarity index 89% rename from api/src/main/java/net/thenextlvl/worlds/preset/Structure.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/Structure.java index ee8bfc05..420bd33a 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/Structure.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/Structure.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; public record Structure(String structure) { Structure(org.bukkit.generator.structure.Structure structure) { diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/BiomeTypeAdapter.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/BiomeTypeAdapter.java similarity index 84% rename from api/src/main/java/net/thenextlvl/worlds/preset/adapter/BiomeTypeAdapter.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/BiomeTypeAdapter.java index 1f00d618..1c6fbf13 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/BiomeTypeAdapter.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/BiomeTypeAdapter.java @@ -1,7 +1,7 @@ -package net.thenextlvl.worlds.preset.adapter; +package net.thenextlvl.worlds.api.preset.adapter; import com.google.gson.*; -import net.thenextlvl.worlds.preset.Biome; +import net.thenextlvl.worlds.api.preset.Biome; import java.lang.reflect.Type; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/StructureTypeAdapter.java similarity index 84% rename from api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/StructureTypeAdapter.java index 02a2c179..93322700 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/StructureTypeAdapter.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/StructureTypeAdapter.java @@ -1,7 +1,7 @@ -package net.thenextlvl.worlds.preset.adapter; +package net.thenextlvl.worlds.api.preset.adapter; import com.google.gson.*; -import net.thenextlvl.worlds.preset.Structure; +import net.thenextlvl.worlds.api.preset.Structure; import java.lang.reflect.Type; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/package-info.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/package-info.java similarity index 86% rename from api/src/main/java/net/thenextlvl/worlds/preset/adapter/package-info.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/package-info.java index f390eb1c..462923d6 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/adapter/package-info.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/adapter/package-info.java @@ -2,7 +2,7 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault @MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.preset.adapter; +package net.thenextlvl.worlds.api.preset.adapter; import core.annotation.FieldsAreNotNullByDefault; import core.annotation.MethodsReturnNotNullByDefault; diff --git a/api/src/main/java/net/thenextlvl/worlds/preset/package-info.java b/api/src/main/java/net/thenextlvl/worlds/api/preset/package-info.java similarity index 88% rename from api/src/main/java/net/thenextlvl/worlds/preset/package-info.java rename to api/src/main/java/net/thenextlvl/worlds/api/preset/package-info.java index 34de6e5d..afced07a 100644 --- a/api/src/main/java/net/thenextlvl/worlds/preset/package-info.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/preset/package-info.java @@ -2,7 +2,7 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault @MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.preset; +package net.thenextlvl.worlds.api.preset; import core.annotation.FieldsAreNotNullByDefault; import core.annotation.MethodsReturnNotNullByDefault; diff --git a/api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java b/api/src/main/java/net/thenextlvl/worlds/api/view/GeneratorView.java similarity index 85% rename from api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java rename to api/src/main/java/net/thenextlvl/worlds/api/view/GeneratorView.java index 0dd23c5a..52a9f31b 100644 --- a/api/src/main/java/net/thenextlvl/worlds/view/GeneratorView.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/view/GeneratorView.java @@ -1,4 +1,4 @@ -package net.thenextlvl.worlds.view; +package net.thenextlvl.worlds.api.view; import org.bukkit.plugin.Plugin; diff --git a/api/src/main/java/net/thenextlvl/worlds/view/LevelView.java b/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java similarity index 87% rename from api/src/main/java/net/thenextlvl/worlds/view/LevelView.java rename to api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java index acef2332..dc748d66 100644 --- a/api/src/main/java/net/thenextlvl/worlds/view/LevelView.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java @@ -1,10 +1,10 @@ -package net.thenextlvl.worlds.view; +package net.thenextlvl.worlds.api.view; import core.nbt.file.NBTFile; import core.nbt.tag.CompoundTag; -import net.thenextlvl.worlds.model.LevelExtras; -import net.thenextlvl.worlds.model.WorldPreset; -import net.thenextlvl.worlds.preset.Preset; +import net.thenextlvl.worlds.api.model.LevelExtras; +import net.thenextlvl.worlds.api.model.WorldPreset; +import net.thenextlvl.worlds.api.preset.Preset; import org.bukkit.World; import org.jetbrains.annotations.Nullable; diff --git a/api/src/main/java/net/thenextlvl/worlds/event/package-info.java b/api/src/main/java/net/thenextlvl/worlds/api/view/package-info.java similarity index 88% rename from api/src/main/java/net/thenextlvl/worlds/event/package-info.java rename to api/src/main/java/net/thenextlvl/worlds/api/view/package-info.java index 91533530..132f3fdc 100644 --- a/api/src/main/java/net/thenextlvl/worlds/event/package-info.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/view/package-info.java @@ -2,7 +2,7 @@ @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault @MethodsReturnNotNullByDefault -package net.thenextlvl.worlds.event; +package net.thenextlvl.worlds.api.view; import core.annotation.FieldsAreNotNullByDefault; import core.annotation.MethodsReturnNotNullByDefault; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index 72abc9ed..feee3f6e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -10,13 +10,13 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.thenextlvl.worlds.command.WorldCommand; import net.thenextlvl.worlds.controller.WorldLinkController; -import net.thenextlvl.worlds.view.GeneratorView; -import net.thenextlvl.worlds.link.LinkController; +import net.thenextlvl.worlds.api.view.GeneratorView; +import net.thenextlvl.worlds.api.link.LinkController; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.ServerListener; -import net.thenextlvl.worlds.preset.Presets; +import net.thenextlvl.worlds.api.preset.Presets; import net.thenextlvl.worlds.version.PluginVersionChecker; -import net.thenextlvl.worlds.view.LevelView; +import net.thenextlvl.worlds.api.view.LevelView; import net.thenextlvl.worlds.view.PaperLevelView; import net.thenextlvl.worlds.view.PluginGeneratorView; import org.bstats.bukkit.Metrics; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index ad60ac0c..61708293 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -12,7 +12,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.argument.*; -import net.thenextlvl.worlds.preset.Preset; +import net.thenextlvl.worlds.api.preset.Preset; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index b156d141..149bb756 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -12,7 +12,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.model.WorldPreset; +import net.thenextlvl.worlds.api.model.WorldPreset; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.command.CommandSender; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java index c66a3aae..cda92517 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkListCommand.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.link.Relative; +import net.thenextlvl.worlds.api.link.Relative; import org.bukkit.World; import java.util.Arrays; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java index 699661dc..e0af5708 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -10,7 +10,7 @@ import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.argument.RelativeArgument; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.link.Relative; +import net.thenextlvl.worlds.api.link.Relative; import org.bukkit.World; @RequiredArgsConstructor diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java index 4332c9be..90f5ca1b 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java @@ -4,7 +4,7 @@ import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import net.kyori.adventure.key.Key; import net.thenextlvl.worlds.command.suggestion.RelativeSuggestionProvider; -import net.thenextlvl.worlds.link.Relative; +import net.thenextlvl.worlds.api.link.Relative; import java.util.function.Predicate; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java index d380167d..6f204e8f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java @@ -7,7 +7,7 @@ import core.paper.command.WrappedArgumentType; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.command.suggestion.WorldPresetSuggestionProvider; -import net.thenextlvl.worlds.preset.Preset; +import net.thenextlvl.worlds.api.preset.Preset; import java.io.File; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java index eaecfe1c..0c39a5e2 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/suggestion/RelativeSuggestionProvider.java @@ -5,7 +5,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import core.paper.command.SuggestionProvider; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.link.Relative; +import net.thenextlvl.worlds.api.link.Relative; import java.util.Arrays; import java.util.concurrent.CompletableFuture; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java index 2b132e49..6f49eeb3 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java @@ -2,8 +2,8 @@ import lombok.RequiredArgsConstructor; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.link.LinkController; -import net.thenextlvl.worlds.link.Relative; +import net.thenextlvl.worlds.api.link.LinkController; +import net.thenextlvl.worlds.api.link.Relative; import org.bukkit.NamespacedKey; import org.bukkit.PortalType; import org.bukkit.World; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index 851b4263..c654068f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -5,13 +5,14 @@ import core.nbt.file.NBTFile; import core.nbt.tag.*; import lombok.RequiredArgsConstructor; +import net.thenextlvl.worlds.api.view.LevelView; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.model.LevelExtras; -import net.thenextlvl.worlds.model.WorldPreset; -import net.thenextlvl.worlds.preset.Biome; -import net.thenextlvl.worlds.preset.Layer; -import net.thenextlvl.worlds.preset.Preset; -import net.thenextlvl.worlds.preset.Structure; +import net.thenextlvl.worlds.api.model.LevelExtras; +import net.thenextlvl.worlds.api.model.WorldPreset; +import net.thenextlvl.worlds.api.preset.Biome; +import net.thenextlvl.worlds.api.preset.Layer; +import net.thenextlvl.worlds.api.preset.Preset; +import net.thenextlvl.worlds.api.preset.Structure; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java index 4fc75ffc..58c82a08 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PluginGeneratorView.java @@ -1,5 +1,6 @@ package net.thenextlvl.worlds.view; +import net.thenextlvl.worlds.api.view.GeneratorView; import org.bukkit.plugin.Plugin; public class PluginGeneratorView implements GeneratorView { From 4511009317addf2cd599638f73ef1c36688f75dc Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:00:16 +0200 Subject: [PATCH 169/182] Define and implement `WorldsProvider` interface. Added a new `WorldsProvider` interface and updated `WorldsPlugin` to implement it. This ensures the core services (GeneratorView, LevelView, LinkController) are registered and available through the plugin lifecycle. --- .../thenextlvl/worlds/api/WorldsProvider.java | 13 +++++++++++++ .../net/thenextlvl/worlds/WorldsPlugin.java | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/net/thenextlvl/worlds/api/WorldsProvider.java diff --git a/api/src/main/java/net/thenextlvl/worlds/api/WorldsProvider.java b/api/src/main/java/net/thenextlvl/worlds/api/WorldsProvider.java new file mode 100644 index 00000000..d712ca8d --- /dev/null +++ b/api/src/main/java/net/thenextlvl/worlds/api/WorldsProvider.java @@ -0,0 +1,13 @@ +package net.thenextlvl.worlds.api; + +import net.thenextlvl.worlds.api.link.LinkController; +import net.thenextlvl.worlds.api.view.GeneratorView; +import net.thenextlvl.worlds.api.view.LevelView; + +public interface WorldsProvider { + GeneratorView generatorView(); + + LevelView levelView(); + + LinkController linkController(); +} diff --git a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java index feee3f6e..a408e7cb 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/WorldsPlugin.java @@ -8,21 +8,23 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.thenextlvl.worlds.api.WorldsProvider; +import net.thenextlvl.worlds.api.link.LinkController; +import net.thenextlvl.worlds.api.preset.Presets; +import net.thenextlvl.worlds.api.view.GeneratorView; +import net.thenextlvl.worlds.api.view.LevelView; import net.thenextlvl.worlds.command.WorldCommand; import net.thenextlvl.worlds.controller.WorldLinkController; -import net.thenextlvl.worlds.api.view.GeneratorView; -import net.thenextlvl.worlds.api.link.LinkController; import net.thenextlvl.worlds.listener.PortalListener; import net.thenextlvl.worlds.listener.ServerListener; -import net.thenextlvl.worlds.api.preset.Presets; import net.thenextlvl.worlds.version.PluginVersionChecker; -import net.thenextlvl.worlds.api.view.LevelView; import net.thenextlvl.worlds.view.PaperLevelView; import net.thenextlvl.worlds.view.PluginGeneratorView; import org.bstats.bukkit.Metrics; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -35,7 +37,7 @@ @Accessors(fluent = true) @FieldsAreNotNullByDefault @ParametersAreNotNullByDefault -public class WorldsPlugin extends JavaPlugin { +public class WorldsPlugin extends JavaPlugin implements WorldsProvider { private final GeneratorView generatorView = new PluginGeneratorView(); private final LevelView levelView = new PaperLevelView(this); @@ -60,6 +62,7 @@ public class WorldsPlugin extends JavaPlugin { public void onLoad() { if (!presetsFolder().isDirectory()) saveDefaultPresets(); versionChecker().checkVersion(); + registerServices(); } @Override @@ -88,6 +91,10 @@ public void persistWorld(World world, boolean enabled) { container.set(new NamespacedKey("worlds", "enabled"), BOOLEAN, enabled); } + private void registerServices() { + getServer().getServicesManager().register(WorldsProvider.class, this, this, ServicePriority.Highest); + } + private void registerListeners() { getServer().getPluginManager().registerEvents(new PortalListener(this), this); getServer().getPluginManager().registerEvents(new ServerListener(this), this); From 03d22e295ff44926cff6b5f2c70357e3b73bcd0c Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:13:20 +0200 Subject: [PATCH 170/182] Optimize dependency declarations Remove unnecessary whitespace in the dependencies block. This enhances readability and maintains consistent code formatting throughout the build script. --- plugin/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index b2ed8d65..c3a63b4b 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -31,9 +31,9 @@ repositories { dependencies { paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") - + compileOnly("org.projectlombok:lombok:1.18.34") - + compileOnly("net.thenextlvl.core:annotations:2.0.1") implementation("org.bstats:bstats-bukkit:3.0.2") From 0e319ad207d27e58fe7eda0c810e61e62240d1d7 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:13:35 +0200 Subject: [PATCH 171/182] Organize import statements Reorganize and optimize import statements to improve code readability and maintainability. Group related imports together and ensure consistency across multiple files. --- .../java/net/thenextlvl/worlds/command/WorldCreateCommand.java | 2 +- .../java/net/thenextlvl/worlds/command/WorldInfoCommand.java | 2 +- .../net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java | 2 +- .../thenextlvl/worlds/command/argument/RelativeArgument.java | 2 +- .../thenextlvl/worlds/command/argument/WorldPresetArgument.java | 2 +- .../main/java/net/thenextlvl/worlds/view/PaperLevelView.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java index 61708293..6a262d66 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldCreateCommand.java @@ -11,8 +11,8 @@ import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.command.argument.*; import net.thenextlvl.worlds.api.preset.Preset; +import net.thenextlvl.worlds.command.argument.*; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java index 149bb756..951d12ee 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldInfoCommand.java @@ -11,8 +11,8 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import net.thenextlvl.worlds.api.model.WorldPreset; +import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.command.CommandSender; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java index e0af5708..16e8253f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldLinkRemoveCommand.java @@ -8,9 +8,9 @@ import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; +import net.thenextlvl.worlds.api.link.Relative; import net.thenextlvl.worlds.command.argument.RelativeArgument; import net.thenextlvl.worlds.command.suggestion.WorldSuggestionProvider; -import net.thenextlvl.worlds.api.link.Relative; import org.bukkit.World; @RequiredArgsConstructor diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java index 90f5ca1b..b31c9d86 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/RelativeArgument.java @@ -3,8 +3,8 @@ import core.paper.command.WrappedArgumentType; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import net.kyori.adventure.key.Key; -import net.thenextlvl.worlds.command.suggestion.RelativeSuggestionProvider; import net.thenextlvl.worlds.api.link.Relative; +import net.thenextlvl.worlds.command.suggestion.RelativeSuggestionProvider; import java.util.function.Predicate; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java index 6f204e8f..bff402cb 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/argument/WorldPresetArgument.java @@ -6,8 +6,8 @@ import core.io.IO; import core.paper.command.WrappedArgumentType; import net.thenextlvl.worlds.WorldsPlugin; -import net.thenextlvl.worlds.command.suggestion.WorldPresetSuggestionProvider; import net.thenextlvl.worlds.api.preset.Preset; +import net.thenextlvl.worlds.command.suggestion.WorldPresetSuggestionProvider; import java.io.File; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index c654068f..be00ae94 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -5,7 +5,6 @@ import core.nbt.file.NBTFile; import core.nbt.tag.*; import lombok.RequiredArgsConstructor; -import net.thenextlvl.worlds.api.view.LevelView; import net.thenextlvl.worlds.WorldsPlugin; import net.thenextlvl.worlds.api.model.LevelExtras; import net.thenextlvl.worlds.api.model.WorldPreset; @@ -13,6 +12,7 @@ import net.thenextlvl.worlds.api.preset.Layer; import net.thenextlvl.worlds.api.preset.Preset; import net.thenextlvl.worlds.api.preset.Structure; +import net.thenextlvl.worlds.api.view.LevelView; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.WorldCreator; From e38c9279d2c4f0f28bf8d7b172c49fa3013013df Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:23:50 +0200 Subject: [PATCH 172/182] Remove unused 'command.argument' property Deleted the 'command.argument' property from both German and English resource files. This property was no longer in use, simplifying the resource file content. --- plugin/src/main/resources/worlds.properties | 1 - plugin/src/main/resources/worlds_german.properties | 1 - 2 files changed, 2 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 27eeb9f1..092a2e9c 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -56,5 +56,4 @@ world.link.success= Linked and Failed to unlink from world.unlink.success= Unlinked from command.sender= You cannot use this command -command.argument= Invalid command argument command.confirmation= '>Confirm your '>action, this cannot be undone! \ No newline at end of file diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index 5257735a..c7eb9ca6 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -52,5 +52,4 @@ world.link.success= und konnte nicht von getrennt werden world.unlink.success= wurde von getrennt command.sender= Du kannst diesen command nicht nutzen -command.argument= Ungültiges command Argument command.confirmation= Bestätige deine '>Eingabe mit '> Diese Aktion ist unwiderruflich! \ No newline at end of file From 1afeb94e761350cb366f3e86040df716501426a0 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:27:03 +0200 Subject: [PATCH 173/182] Refactor world-related messages and commands Reorganized and standardized the order of messages and commands for better clarity and consistency in both English and German resource files. Ensuring all properties are properly aligned. --- plugin/src/main/resources/worlds.properties | 90 +++++++++---------- .../main/resources/worlds_german.properties | 82 ++++++++--------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 092a2e9c..4d114ef4 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -1,59 +1,59 @@ -prefix=Worlds » -environment.normal=Normal Environment -environment.end=End Environment -environment.nether=Nether Environment -world.type.amplified=Amplified -world.type.flat=Superflat -world.type.large_biomes=Large biomes -world.type.normal=Default -world.save= Saving the world (this may take a moment!) -world.save.success= Saved the world -command.world.save.saving= -command.world.save.success= -command.world.save.failed= +command.confirmation= '>Confirm your '>action, this cannot be undone! +command.sender= You cannot use this command command.world.save.already-off= Saving is already turned off -command.world.save.off= Automatic saving is now disabled command.world.save.already-on= Saving is already turned on +command.world.save.failed= +command.world.save.off= Automatic saving is now disabled command.world.save.on= Automatic saving is now enabled -world.create.success= Successfully created the world +command.world.save.saving= +command.world.save.success= +environment.end=End Environment +environment.nether=Nether Environment +environment.normal=Normal Environment +prefix=Worlds » +world.clone.failed= Failed to clone world +world.clone.success= Successfully cloned world world.create.failed= Failed to create the world -world.import.success= Successfully imported the world +world.create.success= Successfully created the world +world.delete.disallowed= The overworld can only be scheduled for deletion +world.delete.failed= Failed to delete the world +world.delete.scheduled= The world will be deleted on the next restart +world.delete.success= Successfully deleted the world world.import.failed= Failed to import the world -world.list= Worlds (): -world.list.hover=Click to teleport to -world.teleport.none= No entity was found -world.teleport.self= You got teleported to -world.teleport.other= Teleported to -world.teleport.others= Teleported entities to -world.clone.success= Successfully cloned world -world.clone.failed= Failed to clone world -world.load.success= Successfully loaded the world -world.load.failed= Failed to load the world -world.spawn.set.success= Set world spawn at , , [] -world.spawn.set.failed= Failed to change world spawn -world.info.name= Name: () -world.info.players= Players: -world.info.type= Type: () +world.import.success= Successfully imported the world world.info.dimension= Dimension: world.info.generator= Generator: +world.info.name= Name: () +world.info.players= Players: world.info.seed= Seed: '>'> -world.regenerate.scheduled= The world will be regenerated on the next restart -world.regenerate.disallowed= The overworld can only be scheduled for regeneration -world.regenerate.success= Successfully regenerated the world -world.regenerate.failed= Failed to regenerate the world -world.delete.disallowed= The overworld can only be scheduled for deletion -world.delete.success= Successfully deleted the world -world.unload.failed= Failed to unload the world -world.unload.success= Successfully unloaded the world -world.unload.disallowed= The overworld cannot be unloaded -world.unload.fallback= The fallback and target world cannot match -world.delete.failed= Failed to delete the world -world.delete.scheduled= The world will be deleted on the next restart +world.info.type= Type: () world.link.failed= Failed to link and world.link.list.empty= There are no links yet world.link.list= Links (): world.link.success= Linked and +world.list.hover=Click to teleport to +world.list= Worlds (): +world.load.failed= Failed to load the world +world.load.success= Successfully loaded the world +world.regenerate.disallowed= The overworld can only be scheduled for regeneration +world.regenerate.failed= Failed to regenerate the world +world.regenerate.scheduled= The world will be regenerated on the next restart +world.regenerate.success= Successfully regenerated the world +world.save.success= Saved the world +world.save= Saving the world (this may take a moment!) +world.spawn.set.failed= Failed to change world spawn +world.spawn.set.success= Set world spawn at , , [] +world.teleport.none= No entity was found +world.teleport.other= Teleported to +world.teleport.others= Teleported entities to +world.teleport.self= You got teleported to +world.type.amplified=Amplified +world.type.flat=Superflat +world.type.large_biomes=Large biomes +world.type.normal=Default world.unlink.failed= Failed to unlink from world.unlink.success= Unlinked from -command.sender= You cannot use this command -command.confirmation= '>Confirm your '>action, this cannot be undone! \ No newline at end of file +world.unload.disallowed= The overworld cannot be unloaded +world.unload.failed= Failed to unload the world +world.unload.fallback= The fallback and target world cannot match +world.unload.success= Successfully unloaded the world \ No newline at end of file diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index c7eb9ca6..bee0e474 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -1,55 +1,55 @@ -environment.normal=Normale Umgebung -environment.end=Endumgebung -environment.nether=Nether Umgebung -world.type.amplified=Erweitert -world.type.flat=Superflach -world.type.large_biomes=Große Biome -world.type.normal=Standard -world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) -world.save.success= Die Welt wurde gespeichert +command.confirmation= Bestätige deine '>Eingabe mit '> Diese Aktion ist unwiderruflich! +command.sender= Du kannst diesen command nicht nutzen command.world.save.already-off= Automatisches Speichern ist bereits deaktiviert -command.world.save.off= Automatisches Speichern ist jetzt deaktiviert command.world.save.already-on= Automatisches Speichern ist bereits aktiviert +command.world.save.off= Automatisches Speichern ist jetzt deaktiviert command.world.save.on= Automatisches Speichern ist jetzt aktiviert -world.create.success= Die Welt wurde erfolgreich erstellt +environment.end=Endumgebung +environment.nether=Nether Umgebung +environment.normal=Normale Umgebung +world.clone.failed= Die Welt konnte nicht geklont werden +world.clone.success= Die Welt wurde erfolgreich geklont world.create.failed= Die Welt konnte nicht erstellt werden -world.import.success= Die Welt wurde erfolgreich importiert +world.create.success= Die Welt wurde erfolgreich erstellt +world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden +world.delete.failed= Die Welt konnte nicht gelöscht werden +world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht +world.delete.success= Die Welt wurde erfolgreich gelöscht world.import.failed= Die Welt konnte nicht importiert werden -world.list= Welten (): -world.list.hover=Klicke um dich zu zu teleportieren -world.teleport.none= Es wurde kein Objekt gefunden -world.teleport.self= Du wurdest zu teleportiert -world.teleport.other= wurde zu teleportiert -world.teleport.others= Objekte wurden zu teleportiert -world.clone.success= Die Welt wurde erfolgreich geklont -world.clone.failed= Die Welt konnte nicht geklont werden -world.load.success= Die Welt wurde erfolgreich geladen -world.load.failed= Die Welt konnte nicht geladen werden -world.spawn.set.success= Der Welteinstiegspunk ist jetzt bei , , [] -world.spawn.set.failed= Der Welteinstiegspunk konnte nicht neu gesetzt werden -world.info.name= Name: () -world.info.players= Spieler: -world.info.type= Typ: () +world.import.success= Die Welt wurde erfolgreich importiert world.info.dimension= Dimension: world.info.generator= Generator: +world.info.name= Name: () +world.info.players= Spieler: world.info.seed= Startwert: '>'> -world.regenerate.scheduled= Die Welt wird beim nächsten Neustart regeneriert -world.regenerate.disallowed= Die Oberwelt kann nur zur Regeneration eingeplant werden -world.regenerate.success= Die Welt wurde erfolgreich regeneriert -world.regenerate.failed= Die Welt konnte nicht regeneriert werden -world.delete.disallowed= Die Oberwelt kann nur zum Löschen eingeplant werden -world.delete.success= Die Welt wurde erfolgreich gelöscht -world.unload.failed= Die Welt konnte nicht entladen werden -world.unload.success= Die Welt wurde erfolgreich entladen -world.unload.disallowed= Die Oberwelt kann nicht entladen werden -world.unload.fallback= Die zu löschende und Rückfallwelt können nicht die gleiche sein -world.delete.failed= Die Welt konnte nicht gelöscht werden -world.delete.scheduled= Die Welt wird beim nächsten Neustart gelöscht +world.info.type= Typ: () world.link.failed= und konnten nicht verbunden werden world.link.list.empty= Es existieren noch keine links world.link.list= Links (): world.link.success= und sind jetzt verbunden +world.list.hover=Klicke um dich zu zu teleportieren +world.list= Welten (): +world.load.failed= Die Welt konnte nicht geladen werden +world.load.success= Die Welt wurde erfolgreich geladen +world.regenerate.disallowed= Die Oberwelt kann nur zur Regeneration eingeplant werden +world.regenerate.failed= Die Welt konnte nicht regeneriert werden +world.regenerate.scheduled= Die Welt wird beim nächsten Neustart regeneriert +world.regenerate.success= Die Welt wurde erfolgreich regeneriert +world.save.success= Die Welt wurde gespeichert +world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) +world.spawn.set.failed= Der Welteinstiegspunk konnte nicht neu gesetzt werden +world.spawn.set.success= Der Welteinstiegspunk ist jetzt bei , , [] +world.teleport.none= Es wurde kein Objekt gefunden +world.teleport.other= wurde zu teleportiert +world.teleport.others= Objekte wurden zu teleportiert +world.teleport.self= Du wurdest zu teleportiert +world.type.amplified=Erweitert +world.type.flat=Superflach +world.type.large_biomes=Große Biome +world.type.normal=Standard world.unlink.failed= konnte nicht von getrennt werden world.unlink.success= wurde von getrennt -command.sender= Du kannst diesen command nicht nutzen -command.confirmation= Bestätige deine '>Eingabe mit '> Diese Aktion ist unwiderruflich! \ No newline at end of file +world.unload.disallowed= Die Oberwelt kann nicht entladen werden +world.unload.failed= Die Welt konnte nicht entladen werden +world.unload.fallback= Die zu löschende und Rückfallwelt können nicht die gleiche sein +world.unload.success= Die Welt wurde erfolgreich entladen \ No newline at end of file From 13b618e8971887250911f87a5b9ca0938ac64312 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:29:16 +0200 Subject: [PATCH 174/182] Update command messages and resource bundles for world saving Refactor command message keys and add missing translations. This ensures consistency and clarity in user feedback for world saving commands both in English and German resource files. --- .../worlds/command/WorldSaveAllCommand.java | 4 ++-- .../worlds/command/WorldSaveOffCommand.java | 2 +- .../worlds/command/WorldSaveOnCommand.java | 2 +- .../worlds/command/WorldSpawnCommand.java | 2 +- plugin/src/main/resources/worlds.properties | 15 +++++++-------- .../src/main/resources/worlds_german.properties | 8 ++++---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java index d855554c..66c22972 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveAllCommand.java @@ -22,10 +22,10 @@ class WorldSaveAllCommand { } private int saveAll(CommandSourceStack source, boolean flush) { - plugin.bundle().sendMessage(source.getSender(), "command.world.save.saving"); + plugin.bundle().sendMessage(source.getSender(), "world.save.saving"); var server = ((CraftServer) plugin.getServer()).getServer(); var saved = server.saveEverything(!(source.getSender() instanceof ConsoleCommandSender), flush, true); - var message = saved ? "command.world.save.success" : "command.world.save.failed"; + var message = saved ? "world.save.success" : "world.save.failed"; plugin.bundle().sendMessage(source.getSender(), message); return saved ? Command.SINGLE_SUCCESS : 0; } diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java index 95827f1b..03bf5781 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOffCommand.java @@ -28,7 +28,7 @@ class WorldSaveOffCommand { } private int saveOff(CommandSender sender, World world) { - var message = world.isAutoSave() ? "command.world.save.off" : "command.world.save.already-off"; + var message = world.isAutoSave() ? "world.save.off" : "world.save.already-off"; world.setAutoSave(false); plugin.bundle().sendMessage(sender, message); return Command.SINGLE_SUCCESS; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java index f2834760..fd2d13f1 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSaveOnCommand.java @@ -28,7 +28,7 @@ class WorldSaveOnCommand { } private int saveOn(CommandSender sender, World world) { - var message = world.isAutoSave() ? "command.world.save.already-on" : "command.world.save.on"; + var message = world.isAutoSave() ? "world.save.already-on" : "world.save.on"; world.setAutoSave(true); plugin.bundle().sendMessage(sender, message); return Command.SINGLE_SUCCESS; diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java index a1f5466d..7e9015a8 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java @@ -22,7 +22,7 @@ class WorldSpawnCommand { .executes(context -> { var player = (Player) context.getSource().getSender(); player.teleportAsync(player.getWorld().getSpawnLocation(), COMMAND); - // todo: add message + plugin.bundle().sendMessage(player, "worlds.spawn"); return Command.SINGLE_SUCCESS; }); } diff --git a/plugin/src/main/resources/worlds.properties b/plugin/src/main/resources/worlds.properties index 4d114ef4..943d1621 100644 --- a/plugin/src/main/resources/worlds.properties +++ b/plugin/src/main/resources/worlds.properties @@ -1,12 +1,5 @@ command.confirmation= '>Confirm your '>action, this cannot be undone! command.sender= You cannot use this command -command.world.save.already-off= Saving is already turned off -command.world.save.already-on= Saving is already turned on -command.world.save.failed= -command.world.save.off= Automatic saving is now disabled -command.world.save.on= Automatic saving is now enabled -command.world.save.saving= -command.world.save.success= environment.end=End Environment environment.nether=Nether Environment environment.normal=Normal Environment @@ -39,7 +32,13 @@ world.regenerate.disallowed= The overworld can only be scheduled fo world.regenerate.failed= Failed to regenerate the world world.regenerate.scheduled= The world will be regenerated on the next restart world.regenerate.success= Successfully regenerated the world -world.save.success= Saved the world +world.save.already-off= Saving is already turned off +world.save.already-on= Saving is already turned on +world.save.failed= +world.save.off= Automatic saving is now disabled +world.save.on= Automatic saving is now enabled +world.save.saving= +world.save.success= world.save= Saving the world (this may take a moment!) world.spawn.set.failed= Failed to change world spawn world.spawn.set.success= Set world spawn at , , [] diff --git a/plugin/src/main/resources/worlds_german.properties b/plugin/src/main/resources/worlds_german.properties index bee0e474..9f5b26bb 100644 --- a/plugin/src/main/resources/worlds_german.properties +++ b/plugin/src/main/resources/worlds_german.properties @@ -1,9 +1,5 @@ command.confirmation= Bestätige deine '>Eingabe mit '> Diese Aktion ist unwiderruflich! command.sender= Du kannst diesen command nicht nutzen -command.world.save.already-off= Automatisches Speichern ist bereits deaktiviert -command.world.save.already-on= Automatisches Speichern ist bereits aktiviert -command.world.save.off= Automatisches Speichern ist jetzt deaktiviert -command.world.save.on= Automatisches Speichern ist jetzt aktiviert environment.end=Endumgebung environment.nether=Nether Umgebung environment.normal=Normale Umgebung @@ -35,6 +31,10 @@ world.regenerate.disallowed= Die Oberwelt kann nur zur Regeneration world.regenerate.failed= Die Welt konnte nicht regeneriert werden world.regenerate.scheduled= Die Welt wird beim nächsten Neustart regeneriert world.regenerate.success= Die Welt wurde erfolgreich regeneriert +world.save.already-off= Automatisches Speichern ist bereits deaktiviert +world.save.already-on= Automatisches Speichern ist bereits aktiviert +world.save.off= Automatisches Speichern ist jetzt deaktiviert +world.save.on= Automatisches Speichern ist jetzt aktiviert world.save.success= Die Welt wurde gespeichert world.save= Die Welt wird gespeichert (das kann einen Moment dauern!) world.spawn.set.failed= Der Welteinstiegspunk konnte nicht neu gesetzt werden From 487e56e285fbd4a8e75f7e4ba2e4c94009da28c9 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:32:37 +0200 Subject: [PATCH 175/182] Update WorldSpawnCommand to include world name placeholder Replaced the generic spawn message with a detailed message that includes the world name using the Placeholder API. This improves user feedback by specifying the world they have been teleported to. --- .../java/net/thenextlvl/worlds/command/WorldSpawnCommand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java index 7e9015a8..01be84ab 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldSpawnCommand.java @@ -5,6 +5,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.worlds.WorldsPlugin; import org.bukkit.entity.Player; @@ -22,7 +23,8 @@ class WorldSpawnCommand { .executes(context -> { var player = (Player) context.getSource().getSender(); player.teleportAsync(player.getWorld().getSpawnLocation(), COMMAND); - plugin.bundle().sendMessage(player, "worlds.spawn"); + plugin.bundle().sendMessage(player, "world.teleport.self", + Placeholder.parsed("world", player.getWorld().key().asString())); return Command.SINGLE_SUCCESS; }); } From 118e284ebe290e15bbda7968369d29fd5affd87b Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:56:29 +0200 Subject: [PATCH 176/182] Switch to WorldLoadEvent listener Replaced ServerLoadEvent with WorldLoadEvent for better specificity. Enhanced logging to provide clearer information on dimension loads and error handling. --- .../thenextlvl/worlds/listener/ServerListener.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java index 3427ecc5..4c7a3cb4 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/listener/ServerListener.java @@ -5,22 +5,28 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.server.ServerLoadEvent; +import org.bukkit.event.world.WorldLoadEvent; @RequiredArgsConstructor public class ServerListener implements Listener { private final WorldsPlugin plugin; @EventHandler(priority = EventPriority.MONITOR) - public void onServerLoad(ServerLoadEvent event) { - if (!event.getType().equals(ServerLoadEvent.LoadType.STARTUP)) return; + public void onWorldLoad(WorldLoadEvent event) { + if (!event.getWorld().key().asString().equals("minecraft:overworld")) return; plugin.levelView().listLevels() .filter(plugin.levelView()::canLoad) .forEach(level -> { try { var world = plugin.levelView().loadLevel(level); + if (world != null) plugin.getComponentLogger().debug("Loaded dimension {} at {}", + world.key().asString(), level.getPath()); + else plugin.getComponentLogger().error("Failed to load the level {}", level.getPath()); } catch (Exception e) { - plugin.getComponentLogger().error("Could not load level {}", level.getPath(), e); + plugin.getComponentLogger().error("An unexpected error occurred while loading the level {}", + level.getPath(), e); + plugin.getComponentLogger().error("Please report the error above on GitHub: {}", + "https://github.com/TheNextLvl-net/worlds/issues/new/choose"); } }); } From aafa3f0b10fb285b1cd409a84b1141b786a96143 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 17:56:56 +0200 Subject: [PATCH 177/182] Refactor and enhance structure overrides parsing Refactor structure override parsing logic to reduce redundancy and streamline code. Introduce additional handling for `structure_overrides` when they are provided as strings. This improves code maintainability and robustness. --- .../net/thenextlvl/worlds/view/PaperLevelView.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index be00ae94..eb71336f 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -21,10 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.NoSuchElementException; -import java.util.Optional; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -173,11 +170,16 @@ public Optional getFlatPreset(CompoundTag generator) { }).collect(Collectors.toCollection(LinkedHashSet::new))) .ifPresent(preset::layers); - settings.flatMap(tag -> tag.>optional("structure_overrides")) - .map(tag -> tag.getAsList().stream() + settings.flatMap(tag -> tag.optional("structure_overrides") + .filter(Tag::isList).map(Tag::getAsList)) + .map(list -> list.stream() .map(structure -> new Structure(structure.getAsString())) .collect(Collectors.toCollection(LinkedHashSet::new))) .ifPresent(preset::structures); + settings.flatMap(tag -> tag.optional("structure_overrides") + .filter(Tag::isString).map(Tag::getAsString)) + .map(Structure::new) + .ifPresent(preset::addStructure); return Optional.of(preset); } From 36c821b572aed3c8eade53712a7c6b2f48874871 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 18:20:45 +0200 Subject: [PATCH 178/182] Add @Override annotations to methods in PaperLevelView This change ensures that methods in the PaperLevelView class are annotated with @Override, improving code readability and reducing the potential for errors during method overriding. It also enhances consistency and clarity by explicitly indicating overridden methods from the interface. --- .../worlds/view/PaperLevelView.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index eb71336f..fa24e6dd 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -30,30 +30,36 @@ public class PaperLevelView implements LevelView { private final WorldsPlugin plugin; + @Override public Stream listLevels() { return Optional.ofNullable(plugin.getServer().getWorldContainer() .listFiles(File::isDirectory)).stream() .flatMap(files -> Arrays.stream(files).filter(this::isLevel)); } + @Override public boolean isLevel(File file) { return file.isDirectory() && (new File(file, "level.dat").isFile() || new File(file, "level.dat_old").isFile()); } + @Override public boolean hasNetherDimension(File level) { return new File(level, "DIM-1").isDirectory(); } + @Override public boolean hasEndDimension(File level) { return new File(level, "DIM1").isDirectory(); } + @Override public boolean canLoad(File level) { return plugin.getServer().getWorlds().stream() .map(World::getWorldFolder) .noneMatch(level::equals); } + @Override public World.Environment getEnvironment(File level) { var end = hasEndDimension(level); var nether = hasNetherDimension(level); @@ -63,18 +69,22 @@ public World.Environment getEnvironment(File level) { return World.Environment.NORMAL; } + @Override public @Nullable World loadLevel(File level) { return loadLevel(level, getEnvironment(level)); } + @Override public @Nullable World loadLevel(File level, World.Environment environment) { return loadLevel(level, environment, extras -> extras.map(LevelExtras::enabled).isPresent()); } + @Override public @Nullable World loadLevel(File level, Predicate> predicate) { return loadLevel(level, getEnvironment(level), predicate); } + @Override public @Nullable World loadLevel(File level, World.Environment environment, Predicate> predicate) { var data = getLevelDataFile(level).getRoot().optional("Data"); var extras = data.flatMap(this::getExtras); @@ -127,6 +137,7 @@ private WorldType typeOf(WorldPreset worldPreset) { return WorldType.NORMAL; } + @Override public Optional getExtras(CompoundTag data) { return data.optional("BukkitValues") .map(Tag::getAsCompound) @@ -142,6 +153,7 @@ public Optional getExtras(CompoundTag data) { }); } + @Override public Optional getFlatPreset(CompoundTag generator) { var settings = generator.optional("settings"); @@ -184,6 +196,7 @@ public Optional getFlatPreset(CompoundTag generator) { return Optional.of(preset); } + @Override public NBTFile getLevelDataFile(File level) { return new NBTFile<>(Optional.of( IO.of(level, "level.dat") @@ -192,6 +205,7 @@ public NBTFile getLevelDataFile(File level) { ), new CompoundTag()); } + @Override public String getDimension(CompoundTag dimensions, World.Environment environment) { return switch (environment) { case NORMAL -> "minecraft:overworld"; @@ -202,6 +216,7 @@ public String getDimension(CompoundTag dimensions, World.Environment environment }; } + @Override public Optional getWorldPreset(CompoundTag generator) { var settings = getGeneratorSettings(generator); @@ -230,14 +245,17 @@ public Optional getWorldPreset(CompoundTag generator) { return Optional.empty(); } + @Override public Optional getGeneratorSettings(CompoundTag generator) { return generator.optional("settings").filter(Tag::isString).map(Tag::getAsString); } + @Override public Optional getGeneratorType(CompoundTag generator) { return generator.optional("type").map(Tag::getAsString); } + @Override public void saveLevel(World world, boolean flush) { var level = ((CraftWorld) world).getHandle(); var oldSave = level.noSave; @@ -246,6 +264,7 @@ public void saveLevel(World world, boolean flush) { level.noSave = oldSave; } + @Override public void saveLevelData(World world, boolean async) { var level = ((CraftWorld) world).getHandle(); if (level.getDragonFight() != null) { From 99e753f70eaba45dcce671d5548f8a7f7bbfcab3 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 18:21:30 +0200 Subject: [PATCH 179/182] Add NamespacedKey parameter to loadLevel methods Introduce overloaded loadLevel methods with an optional NamespacedKey parameter, enhancing the flexibility and customization of world loading. Adjusted the logic in PaperLevelView to support the new parameter while maintaining backward compatibility. The changes streamline the process of setting unique keys for worlds based on their names or provided values. --- .../thenextlvl/worlds/api/view/LevelView.java | 5 ++++ .../worlds/view/PaperLevelView.java | 30 ++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java b/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java index dc748d66..b5696b54 100644 --- a/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java +++ b/api/src/main/java/net/thenextlvl/worlds/api/view/LevelView.java @@ -5,6 +5,7 @@ import net.thenextlvl.worlds.api.model.LevelExtras; import net.thenextlvl.worlds.api.model.WorldPreset; import net.thenextlvl.worlds.api.preset.Preset; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.jetbrains.annotations.Nullable; @@ -16,10 +17,14 @@ public interface LevelView { @Nullable World loadLevel(File level); + @Nullable World loadLevel(File level, @Nullable NamespacedKey key, Predicate> predicate); + @Nullable World loadLevel(File level, Predicate> predicate); @Nullable World loadLevel(File level, World.Environment environment); + @Nullable World loadLevel(File level, World.Environment environment, @Nullable NamespacedKey key, Predicate> predicate); + @Nullable World loadLevel(File level, World.Environment environment, Predicate> predicate); NBTFile getLevelDataFile(File level); diff --git a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java index fa24e6dd..8152fa3e 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/view/PaperLevelView.java @@ -21,7 +21,10 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.*; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -74,6 +77,11 @@ public World.Environment getEnvironment(File level) { return loadLevel(level, getEnvironment(level)); } + @Override + public @Nullable World loadLevel(File level, @Nullable NamespacedKey key, Predicate> predicate) { + return loadLevel(level, getEnvironment(level), key, predicate); + } + @Override public @Nullable World loadLevel(File level, World.Environment environment) { return loadLevel(level, environment, extras -> extras.map(LevelExtras::enabled).isPresent()); @@ -86,6 +94,11 @@ public World.Environment getEnvironment(File level) { @Override public @Nullable World loadLevel(File level, World.Environment environment, Predicate> predicate) { + return loadLevel(level, environment, null, predicate); + } + + @Override + public @Nullable World loadLevel(File level, World.Environment environment, @Nullable NamespacedKey key, Predicate> predicate) { var data = getLevelDataFile(level).getRoot().optional("Data"); var extras = data.flatMap(this::getExtras); @@ -111,14 +124,15 @@ public World.Environment getEnvironment(File level) { .orElseThrow(() -> new NoSuchElementException("generate_features")) .getAsBoolean(); - var key = extras.map(LevelExtras::key).orElseGet(() -> { - var namespace = level.getName().toLowerCase() - .replace("(", "").replace(")", "") - .replace(" ", "_"); - return new NamespacedKey("worlds", namespace); - }); + var worldKey = Optional.ofNullable(key).orElseGet(() -> + extras.map(LevelExtras::key).orElseGet(() -> { + var namespace = level.getName().toLowerCase() + .replace("(", "").replace(")", "") + .replace(" ", "_"); + return new NamespacedKey("worlds", namespace); + })); - var creator = new WorldCreator(level.getName(), key) + var creator = new WorldCreator(level.getName(), worldKey) .environment(environment) .generateStructures(structures) .hardcore(hardcore) From c05903839ffae86bfe8f21155e0ca2dd5eee737a Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 18:21:56 +0200 Subject: [PATCH 180/182] Add key argument to WorldImportCommand This change updates the WorldImportCommand to include a new "key" argument. The key parameter is used to specify a NamespacedKey for world loading, improving command functionality and flexibility. The added support allows users to import worlds more precisely. --- .../thenextlvl/worlds/command/WorldImportCommand.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java index 583366b6..24d5f781 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/command/WorldImportCommand.java @@ -32,6 +32,11 @@ class WorldImportCommand { .requires(source -> source.getSender().hasPermission("worlds.command.import")) .then(Commands.argument("world", StringArgumentType.string()) .suggests(new LevelSuggestionProvider<>(plugin)) + .then(Commands.argument("key", ArgumentTypes.namespacedKey()) + .executes(context -> { + var key = context.getArgument("key", NamespacedKey.class); + return execute(context, null, key); + })) .then(Commands.argument("dimension", new DimensionArgument(plugin)) .then(Commands.argument("key", ArgumentTypes.namespacedKey()) .executes(context -> { @@ -49,10 +54,10 @@ class WorldImportCommand { private int execute(CommandContext context, @Nullable World.Environment environment, @Nullable NamespacedKey key) { var name = context.getArgument("world", String.class); var level = new File(plugin.getServer().getWorldContainer(), name); - // todo: define key + var world = plugin.levelView().isLevel(level) ? environment != null - ? plugin.levelView().loadLevel(level, environment, Optional::isEmpty) - : plugin.levelView().loadLevel(level, Optional::isEmpty) : null; + ? plugin.levelView().loadLevel(level, environment, key, Optional::isEmpty) + : plugin.levelView().loadLevel(level, key, Optional::isEmpty) : null; var message = world != null ? "world.import.success" : "world.import.failed"; plugin.bundle().sendMessage(context.getSource().getSender(), message, From 3f1191a4fec8a0e10ecfd83eeb0b02461215699a Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 18:36:30 +0200 Subject: [PATCH 181/182] Update world link logic to handle sibling worlds Refactor the linking logic in `WorldLinkController` to include linking sibling worlds when linking a new world. The method now considers the opposite dimension for sibling world associations, enhancing the cross-world referencing structure. --- .../worlds/controller/WorldLinkController.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java index 6f49eeb3..940ec100 100644 --- a/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java +++ b/plugin/src/main/java/net/thenextlvl/worlds/controller/WorldLinkController.java @@ -61,13 +61,18 @@ public boolean canLink(World source, World destination) { public boolean link(World source, World destination) { if (!canLink(source, destination)) return false; var child = switch (destination.getEnvironment()) { - case NETHER -> Relative.NETHER.key(); - case THE_END -> Relative.THE_END.key(); + case NETHER -> Relative.NETHER; + case THE_END -> Relative.THE_END; default -> null; }; if (child == null) return false; + var opposite = child.equals(Relative.NETHER) ? Relative.THE_END : Relative.NETHER; + getTarget(source, opposite).map(plugin.getServer()::getWorld).ifPresent(sibling -> { + sibling.getPersistentDataContainer().set(child.key(), STRING, destination.key().asString()); + destination.getPersistentDataContainer().set(opposite.key(), STRING, sibling.key().asString()); + }); destination.getPersistentDataContainer().set(Relative.OVERWORLD.key(), STRING, source.key().asString()); - source.getPersistentDataContainer().set(child, STRING, destination.key().asString()); + source.getPersistentDataContainer().set(child.key(), STRING, destination.key().asString()); return true; } From f5b6e66b42a9f700be372a87c58181f5740306d5 Mon Sep 17 00:00:00 2001 From: david Date: Thu, 15 Aug 2024 19:14:35 +0200 Subject: [PATCH 182/182] Update Gradle wrapper and add licensing information Upgraded Gradle wrapper distribution from version 8.8 to 8.9 to incorporate latest improvements and fixes. Added SPDX license identifiers to both the Unix and Windows wrapper scripts to comply with licensing standards. Improved script robustness by modifying the way the current working directory is handled. --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 ++++- gradlew.bat | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 8703 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 Ojj7^59?Sdh$^Qpp*~F>< delta 8662 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..09523c0e 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.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13..f5feea6d 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,8 @@ 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 +' "$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 25da30db..9d21a218 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 ##########################################################################