From 9660be1571104419bedd7a697074238ef22924ea Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:39:27 +0100 Subject: [PATCH 01/15] :art: Serialization, Animation, ChunkCoordinate tweaks. --- .../bits/util/serializer/SerializationManager.java | 9 ++++++++- .../paper/location/wrapper/ChunkCoordinate.java | 13 +++++++++++++ .../bits/mc/animation/impl/BasicAnimation.java | 3 +-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java index 6b4dc87f..3802c162 100644 --- a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java +++ b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java @@ -8,7 +8,12 @@ package xyz.bitsquidd.bits.util.serializer; -import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.guava.GuavaModule; @@ -23,6 +28,7 @@ import java.util.Set; import java.util.stream.Collectors; + public final class SerializationManager { private SerializationManager() {} @@ -35,6 +41,7 @@ private static ObjectMapper createMapper() { .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) .disable(MapperFeature.AUTO_DETECT_IS_GETTERS) + .defaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL) .annotationIntrospector(new NullableAwareIntrospector()); getSerializers().forEach(serializer -> registerSerializer(serializer, builder)); diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java index ab9cde47..b93229cf 100644 --- a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java @@ -7,12 +7,15 @@ package xyz.bitsquidd.bits.paper.location.wrapper; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import java.util.Objects; + /** * Immutable record representing the coordinates of a chunk in a specific world. * @@ -40,6 +43,16 @@ public static ChunkCoordinate fromChunk(Chunk chunk) { ); } + public static ChunkCoordinate fromChunk(ChunkAccess chunkAccess) { + if (!(chunkAccess instanceof LevelChunk levelChunk)) throw new IllegalArgumentException("ChunkAccess must be an instance of LevelChunk"); + + return new ChunkCoordinate( + levelChunk.locX, + levelChunk.locZ, + levelChunk.level.getWorld() + ); + } + public int manhattanDistance(ChunkCoordinate other) { if (!Objects.equals(this.world, other.world)) throw new IllegalArgumentException("Cannot calculate distance between chunks in different worlds"); return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); diff --git a/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java b/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java index 391198c2..abc7feaf 100644 --- a/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java +++ b/minecraft/src/main/java/xyz/bitsquidd/bits/mc/animation/impl/BasicAnimation.java @@ -59,8 +59,7 @@ public AnimationPose evaluate(int ticks) { float localProgress = Math.min((progress - kf.progress()) / intervalLength, 1f); float easedProgress = kf.easing().progress(localProgress); - AnimationPose delta = AnimationPose.lerp(AnimationPose.identity(), kf.pose(), easedProgress); - result = AnimationPose.add(result, delta); + result = AnimationPose.lerp(kf.pose(), next.pose(), easedProgress); } return result; } From 2b595cc4cd49cedea53067053cacd053d36eae82 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:42:46 +0100 Subject: [PATCH 02/15] :art: Update README.md --- README.md | 126 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 4dbcb210..ee8b3d13 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,100 @@ +
+ bits_logo -

💾 A Utility library for Orbitary development.

+# Bits + +*- Utility library for Orbitary development -* -[![License](https://img.shields.io/github/license/Orbitary/Bits)](LICENSE) -[![Java CI](https://github.com/Orbitary/Bits/actions/workflows/publish.yml/badge.svg)](https://github.com/Orbitary/Bits/actions/workflows/publish.yml) -[![Modrinth Downloads](https://img.shields.io/badge/dynamic/json?color=1bd96a&label=Modrinth&query=downloads&suffix=%20downloads&url=https%3A%2F%2Fapi.modrinth.com%2Fv2%2Fproject%2F8ZZ61TMj)](https://modrinth.com/mod/orbits) +[![](https://img.shields.io/github/license/Orbitary/Bits?style=flat-square)](LICENSE) +[![](https://img.shields.io/github/actions/workflow/status/Orbitary/Bits/publish.yml?style=flat-square)](https://github.com/Orbitary/Bits/actions/workflows/publish.yml) +[![](https://img.shields.io/badge/dynamic/json?color=1bd96a&label=modrinth&style=flat-square&query=downloads&suffix=%20downloads&url=https%3A%2F%2Fapi.modrinth.com%2Fv2%2Fproject%2F8ZZ61TMj)](https://modrinth.com/mod/orbits) -## About +
-Bits is a multi-platform utility library designed to streamline: +* * * -- Minecraft plugin development across Paper and Velocity platforms. -- Minecraft mod development using Fabric (both client and server). -- Discord Bot Development -- And more! +# ✨ Introduction -### Features: +**Bits** is a multi-platform utility library designed to streamline Minecraft plugin and mod development, as well as Discord bot development. -- Multi-platform support -- Annotation-based [Brigadier](https://github.com/Mojang/brigadier) Command API -- Sendable lifecycle builders (Actionbars, Sidebars, Waypoints, +) -- Automatic Text formatting -- Common Plugin utilities (Color, Sound, Location, Item, Math, Permission APIs) +- Built for **Paper**, **Velocity**, and **Fabric** (client & server). +- Annotation-based [Brigadier](https://github.com/Mojang/brigadier) **Command API**. +- **Sendable lifecycle builders** — Actionbars, Sidebars, Waypoints, and more. +- **Automatic text formatting** and common plugin utilities. +- Color, Sound, Location, Item, Math, and Permission APIs included. > [!NOTE] -> This project is in active development, expect regular changes and additions! +> This project is in active development — expect regular changes and additions! + +## 🛠️ Build info -## Installation +#### Modules -### Gradle (Kotlin DSL) +- **api** — Core interfaces and abstractions +- **paper** — Paper platform implementation + - **paper:sendable** — Actionbars, Sidebars, Waypoints + - **paper:command** — Brigadier command framework +- **velocity** — Velocity platform implementation + - **velocity:command** — Brigadier command framework +- **fabric** — Fabric platform implementation (client + server) + +## 💻 API + +
+Gradle (Kotlin) ```kotlin repositories { - maven { url = uri("https://repo.bitsquidd.xyz/repository/bit/") } + maven("https://repo.bitsquidd.xyz/repository/bit/") } dependencies { - // Core API - implementation("xyz.bitsquidd.bits:api:0.0.19") - - // Platform implementation - implementation("xyz.bitsquidd.bits.paper:paper:0.0.19") - implementation("xyz.bitsquidd.bits.velocity:velocity:0.0.19") - implementation("xyz.bitsquidd.bits.fabric:fabric:0.0.19") - + // Core API (required) + implementation("xyz.bitsquidd.bits:api:0.0.20") + + // Platform implementations + implementation("xyz.bitsquidd.bits.paper:paper:0.0.20") + implementation("xyz.bitsquidd.bits.velocity:velocity:0.0.20") + implementation("xyz.bitsquidd.bits.fabric:fabric:0.0.20") + // Optional: Platform-specific modules - implementation("xyz.bitsquidd.bits.paper:sendable:0.0.19") - implementation("xyz.bitsquidd.bits.paper:command:0.0.19") - implementation("xyz.bitsquidd.bits.velocity:command:0.0.19") + implementation("xyz.bitsquidd.bits.paper:sendable:0.0.20") + implementation("xyz.bitsquidd.bits.paper:command:0.0.20") + implementation("xyz.bitsquidd.bits.velocity:command:0.0.20") } ``` -### Maven +
+ +
+Gradle (Groovy) + +```groovy +repositories { + maven { url 'https://repo.bitsquidd.xyz/repository/bit/' } +} + +dependencies { + // Core API (required) + implementation 'xyz.bitsquidd.bits:api:0.0.20' + + // Platform implementations + implementation 'xyz.bitsquidd.bits.paper:paper:0.0.20' + implementation 'xyz.bitsquidd.bits.velocity:velocity:0.0.20' + implementation 'xyz.bitsquidd.bits.fabric:fabric:0.0.20' + + // Optional: Platform-specific modules + implementation 'xyz.bitsquidd.bits.paper:sendable:0.0.20' + implementation 'xyz.bitsquidd.bits.paper:command:0.0.20' + implementation 'xyz.bitsquidd.bits.velocity:command:0.0.20' +} +``` + +
+ +
+Maven ```xml @@ -67,36 +110,29 @@ dependencies { xyz.bitsquidd.bits api - 0.0.19 + 0.0.20 - + xyz.bitsquidd.bits.paper paper - 0.0.19 + 0.0.20 xyz.bitsquidd.bits.velocity velocity - 0.0.19 + 0.0.20 xyz.bitsquidd.bits.fabric fabric - 0.0.19 + 0.0.20 ``` -## Libraries - -- **API** - Core interfaces and abstractions -- **Paper** - Paper platform implementation -- **Velocity** - Velocity platform implementation -- **Fabric** - Fabric platform implementation - - **Client** - Fabric client implementation - - **Server** - Fabric server implementation +
--- Made with 🦑 by [ImBit](https://github.com/ImBit) From d52918b13bfd116fe9d955ae82fa55f05cc4941c Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:50:33 +0100 Subject: [PATCH 03/15] :art: Add icon. --- .idea/icon.png | Bin 0 -> 5407 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .idea/icon.png diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3475e3570aa7290dfd4d60c4937ca9dc65d467f9 GIT binary patch literal 5407 zcmV+)72xWLP)@3d010qNS#tmYE+YT{E+YYWr9XB6000McNliru>IM=IFdI-LK+*sJ02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{02GHwL_t(|+U1>Xk0r-d$NyE` z_uko=S)SJW=*7E8NC-i|8xlLRWyc@|WJls9uLFFM{R**T;&p5%5Tp>qJ3>^qkP0!rEeQ(#V&;Oh{b*ft7 z;Bf<`0HpyaH2bRCN3h@iM(`T?``&Tj{|3l==xfNoHrZy#t`WROew*0ehP)@-C&#(} zd9RV*?fw=p26YDrJfLD&Aq15G#r#hUNe$;`0ubJ3BLJkjHz{Qsk2&6*PTBVu^131K zr^gx9y{^E#$L*W8P3kBVF{}`Fupho>FenbtKw`@H@AUwNfI@I?Hw4FkbnlD8NI;{< z6413Tf$$qVgj5PE1lO}X#Q(=a$^lY&$zx@g0BPZIR>b_D{x3XErS?N;96V!xckK|d zFw%&FV2$)$SxHodymBB_1YTekJ&OxvGGG^L$)rZH)Da zpn(rQdj3pN3kzKg=kUkE%2`zk6az>NgcizK2qy)u4clXuH3S;@j24~=pf#3QN97L* z-zI`pVpT#=DM0r;2UH_e=5fggz<>E@Ew*QD&hbB8ePO8rDntU*IIM z5EciI1*HU*q<~Yu15^v3nl(SV4uBb8y9Ksu;QV92W(91Hfd9GSw?wSJ<-uUU^{g$h zt}Ha>21)kEfSQ*=)t5vL;8<9_U?OEr!QW>e2I@ugQ_X>@?toDM+l~EM0qZ5OSpsJs z0{-h?`jSXLvuAY<5d086=f_xR9H3AV-D|!yGX1cW--<}02$o_x#GEWmGK z6TocISXk{Wtm<;2t%W&Y>jv0**+9PV81U9VBAR33`dmFr5&AI|izc~%hVy3#G9B4a zp0lVSuo9`M94IK@>_fnOADHcT!0Ne`#JU4U8{2FfV4HO#iK`=EZQpb86TpxDA<~Hs z!J%($J&7!U=JnSyl1Q4M=A|$ek;Q@!$Y6=ADiVyGeFRt>SYRiB`H2QrZRN1409C30oK*Txe~cZ_7agvk%7Nzy~81Y~7POF1Y9g8ORK) z#6JRR^$iU|lrYGX~gdKySNawFa~UYy*|cp9J3cJ3qL9Vlw!q z9GZ$mo~%FPLwdgBjMRuT%7#;lVUhoxd!(@}2WzqK!>Vf=Xjo8M_cJtxi~_gEs18(n zZMyqzEi^=;Ld3)Q5@@j`a3=7m`G6yq9T$QV!-`1j*@p=z3Tr`NIlo0fsE9f^VB2l; zdlTG<;gHhhPXcdzHIkCb>iF0aXL%8DNoy(WIG=vN1{XBISTvS3 z3oMJuTCQY3p|PYbX)KkS`$C;84~TW7dmU>q{;f1i zZ}(>>y0G0vgPzY~U?~+ZBQU|Bq^8Hz>8yVXE!T05Ktm)g747rI;B~)sa^!+7wn3yb zjS)jCcK^)BfO@aVM9gg3$%XE95MXFV&SF6my&;{YRrWkp)trAeYd&+n*W4_NgQhES z`HA5wq7p$>GLas?kLsj#A(r?>!f%zp3eHu{NN1Q@6cY(@z9%Z4Mi z(p@Z_AAMJm@hPSQJqA|0ziq2rn?hJx0gFuG!(9%IZKp^(NA63V^j%3=L_S3&uJ|Pv z`1;?HA(4$u+!Bae>ad+UhL^5Hv0&3@rjnHpEAWp{5q-;5Hkj(}BQX`8zUS}r+|9Q%`7nJ%*~L7>5vQLEG_bLt%S zd)#~uSXxV4S&3}r(GY03bI`bqNVc6+qR-1)nT|(&1Fb9irH8;G+s)^RJQ8S|4S{AN zqqWBkZGikItt=XJq@182&E%xPurqAS|0D&XKE@cx^b zx_ac3qphV`Xi5StmpYmd8bWBVWj-Sjd??St0$`d!S)?o*W)?s*c@3z?U;3Dw; z56Tjxsm;k_78>p>w0B<{EsJqr66?C~7_j$q4Xix^YtGeS1cjRi7lHSFkWU~dXJC>W z=bc-?X#q5KhoMoQRuX)4z&?C6FNchEU3jeVS=6!?d%)aT*1UtIv=7UjuJUsyVi<`^nRV&5&IDU4buF5LZv#`n(-WTiO9orcNXrlF22G zlle(5VN9;Mo?AuQvu(K_PqZ`F7_UnV52hv5z2G9K$kl=GaR1ZH1#r;reJqil8BpeS z^MwesU`@i(Y`z3qc(cyt_Ghol2Hp3#*k9{>!Gi;~=;jo=&Ttj@tPoUKCY^)UmPj7} z7ao}gmfK!=-cCwi1y*K87hpwYOd*($R@v*GbzPs@TAT11b-seu&Y7wfT^V)F%zWe~ zCU^lu0-C!3C$)2HZRZ}I2A12df3B+y*>0E$EF+i2q?OUg5ZyJqDN^o6uL-nEPawZc zQ(J_i(*COotjdM#UL+Xl>!=|48bZ^R53QXNYxVuigVVrr+e@GIWpRic7DH2hTWJby z)-4Q^@?-XMw8(!|wLhQO^I1Akmdy1p0%-1hEwouD6V5UJ(*2lp^UCE&$=sx}$c)rG zV084_O^~l@ksqr?{)9U+>ympK1KHu!Q*wojt;ftb*vuaF3+Oz(T~TTtVo1-DD%a@x^Hy?$Rgx zeq?W{BUO~5td2qt4juwBZEy)mU!pU|>*S2$a*ERsPv7pw=Cigex zcJIgS>ICx$v>o|vPyK=$v{jQe@UMR^KBdlv^ltmNzieb`?IzlhTwE_>V0efzSnHLY z=x_wwcy8K8JNJ-HT!$;^Xg)(Tm34*wc^i@a&5Do6M}_t7T3&tLpBdTKwTCk!X;i`Z ztR5^2?E16Q!aDo0t_s;pT)QNI>aXD;851joWsEO_PWIO;;2SR#-6vW`iflKYu`@`R z@uh9}D5X#e7=N|0tAt*^Hmxq5c`#U~$_)u|3$$QImIdE$d9aqiE7yugBlMsVjO(B2 ztRl3ut?{R`y5B6UC4n{JcIJU@Y;C@86Ut=#F1AohiFJu!ZTo8CU|oN@*j9q!mC~&D zlg$UO!ns;oe?&L@(xK0q8rJELc4Z9h8jDU2nVOR!5eH{oA*}G)9jw(7cj-#xN3eP;!7aKmyif|c6xkH8+_kUQMEaM&i%*V8 zdlUp6QD~nAmPce}WDt;0-ChyPdij~%TGk>}$}lj~Q3`8gVXf^ofBn;=FH=v_ST8== z)l95cz-rxrYo)-#TJ9RwzF&7Tn*((fS!z~TlQMaP(8)_D_M^KLK9-!kvYs z?f|TpriNv9WMOXm_b&mr-vhq&<^PAUp59rr>V9f@!tM31+#Rq=`o?!9EJJ}Eb4|l8 zVX-Nvf>hIJSbw(TMlg&dniAkc>jMhkcKRbSSiNVkKsMjYTk&)ku$Xo-!`eodzspQ! z-lB7#{s@_x%fPAzzyg@1X-B|%`LmejZq2u?K0Zww0J^oTgD!(HU-+=90kBH5?pavJ z=T`Le?LARZ<~<+oR@L#IY_#wvW886A6SRSu1Q;h5M((VKXtvu<-EW^6MuteZL@oyl z#|7(|j8B)gkztNArNE9eatw-ge(^)iOwTz@;w~bvm@+kW%&?Bxh3Mgo%!n@$dGE(WTxkw)ME68NVIGs#!g_F3EH9ja4kQX*^cFU54 z^hOL;_ezH+2|rE0a5VWgdwfM7loL+L4QG4aC9n*m9!A(kVrX4Wr%qc>0jJ_wAY8Ao z>i>k(ob^xVQnR_5?@?=lSurRAmHeLMOK1IF6EQE zLo%``wc?vEkcSE|6~?_OPm$m&oRCOsCD$<-EKr)P917aam~_BluVH+)-blaT04oE4 z8=vbx!lPFfPH#G9v6Qw{JSZ7ole$3)Ym{}F0(F`qTW?BEUsC@T}l^{G4y1`>q0cx$KHtZ#*lM!LimgAXtlZ zd31TWnUh7SM#ol8%Y3Fm^OU0_TTSdr!FSlHtMT=`Q!LF|1eD<^Ie{btoocpsc3FoGmE_qSms z&U-D4bYVO9JF9 zzyYow7VF5F0aj*Y84OA1*~B`PW(2&%wHe8HRw}GdB)!GfW#Wkn(@J7O4o&3l%JrR$ zk>%AS#XQ+@sT4q=|1yG3t{Hi)N$W~y>D`YmmkptniDaV?nrH>=692_nl@yj#3Qa-U zOPZ}WF_ScDi!iB9WF(|5hL!?1LK35(#=wdN8UpJ|1{>XO-ER=4Y|NOTQ`_rs#rs)p zUo6!>b6-@EzF+uxL5Dd6tOT4&qQqP@YfL`jEA@BCOm`wb{|m{3u1btlx Date: Thu, 18 Jun 2026 19:55:24 +0100 Subject: [PATCH 04/15] :arrow_up: Update to MC 26.2 --- gradle/libs.versions.toml | 2 +- minecraft/paper/gradle/libs.versions.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8bdf4cf..17ef1b19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -adventure = "4.26.1" +adventure = "5.1.1" jackson = "2.21.4" [libraries] diff --git a/minecraft/paper/gradle/libs.versions.toml b/minecraft/paper/gradle/libs.versions.toml index b9bcfb98..cc649d63 100644 --- a/minecraft/paper/gradle/libs.versions.toml +++ b/minecraft/paper/gradle/libs.versions.toml @@ -1,8 +1,7 @@ [versions] -paper-api = "26.1.2.build.+" +paper-api = "26.2-rc-2.build.+" [libraries] -paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper-api" } bstats = { group = "org.bstats", name = "bstats-bukkit", version = "3.2.1" } [plugins] From 821db2495ee5b288104fbd76581a73f7acae63bf Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Thu, 18 Jun 2026 19:58:20 +0100 Subject: [PATCH 05/15] :bug: Fix ChunkCoordinte#fromChunk NMS. --- .../bits/paper/location/wrapper/ChunkCoordinate.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java index b93229cf..aa840a33 100644 --- a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/location/wrapper/ChunkCoordinate.java @@ -47,17 +47,23 @@ public static ChunkCoordinate fromChunk(ChunkAccess chunkAccess) { if (!(chunkAccess instanceof LevelChunk levelChunk)) throw new IllegalArgumentException("ChunkAccess must be an instance of LevelChunk"); return new ChunkCoordinate( - levelChunk.locX, - levelChunk.locZ, - levelChunk.level.getWorld() + levelChunk.getPos().x(), + levelChunk.getPos().z(), + levelChunk.getLevel().getWorld() ); } + public int manhattanDistance(ChunkCoordinate other) { if (!Objects.equals(this.world, other.world)) throw new IllegalArgumentException("Cannot calculate distance between chunks in different worlds"); return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); } + public int euclideanDistance(ChunkCoordinate other) { + if (!Objects.equals(this.world, other.world)) throw new IllegalArgumentException("Cannot calculate distance between chunks in different worlds"); + return (int)Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.z - other.z, 2)); + } + @Override public int hashCode() { From 3248376c455b7b9397a49d88891d435fa3595d10 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:54:12 +0100 Subject: [PATCH 06/15] :art: Update fabric to 26.2 --- gradle/wrapper/gradle-wrapper.properties | 2 +- minecraft/fabric/build.gradle.kts | 2 +- minecraft/fabric/gradle/libs.versions.toml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118f..1a704683 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-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/minecraft/fabric/build.gradle.kts b/minecraft/fabric/build.gradle.kts index c5b8eb55..2f75d59a 100644 --- a/minecraft/fabric/build.gradle.kts +++ b/minecraft/fabric/build.gradle.kts @@ -34,7 +34,7 @@ repositories { dependencies { group = "xyz.bitsquidd.bits.fabric" - minecraft("com.mojang:minecraft:26.1.2") + minecraft("com.mojang:minecraft:26.2") shade(project(":minecraft"), transitive = true) shade(project(":minecraft:module:command"), transitive = true) diff --git a/minecraft/fabric/gradle/libs.versions.toml b/minecraft/fabric/gradle/libs.versions.toml index 4c7da3a6..6bd89243 100644 --- a/minecraft/fabric/gradle/libs.versions.toml +++ b/minecraft/fabric/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [libraries] -fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.19.1" } -fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.145.4+26.1.2" } +fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.19.3" } +fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version = "0.152.2+26.2" } [plugins] -fabric-loom = { id = "net.fabricmc.fabric-loom", version = "1.16-SNAPSHOT" } +fabric-loom = { id = "net.fabricmc.fabric-loom", version = "1.17-SNAPSHOT" } From 73697fcc1e7e8ac701650120ce65f7fc1de42d45 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:54:52 +0100 Subject: [PATCH 07/15] :sparkles: WorldDefinition wrapper for unloaded Paper worlds. --- .../parser/impl/WorldArgumentParser.java | 14 +++- .../impl/WorldDefinitionArgumentParser.java | 79 +++++++++++++++++++ .../bits/paper/wrapper/WorldDefinition.java | 49 ++++++++++++ .../bits/paper/wrapper/package-info.java | 4 + 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java create mode 100644 minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java create mode 100644 minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java index 9603dcbb..f0877c9e 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java @@ -9,6 +9,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.World; import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; @@ -19,6 +20,7 @@ import java.util.List; import java.util.function.Supplier; + public final class WorldArgumentParser extends AbstractArgumentParser { public WorldArgumentParser() { @@ -29,14 +31,22 @@ public WorldArgumentParser() { public World parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { String inputString = singletonInputValidation(inputObjects, String.class); - World world = Bukkit.getWorld(inputString); + NamespacedKey key; + try { + key = NamespacedKey.fromString(inputString); + if (key == null) throw new IllegalArgumentException(); + } catch (IllegalArgumentException e) { + throw ExceptionBuilder.createCommandException("Invalid world key: " + inputString + "."); + } + + World world = Bukkit.getWorld(key); if (world == null) throw ExceptionBuilder.createCommandException("World not found: " + inputString + "."); return world; } @Override public Supplier> getSuggestions() { - return () -> Bukkit.getWorlds().stream().map(World::getName).toList(); + return () -> Bukkit.getWorlds().stream().map(w -> "\"" + w.key() + "\"").toList(); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java new file mode 100644 index 00000000..7506fd30 --- /dev/null +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java @@ -0,0 +1,79 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser.impl; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; + +import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.paper.wrapper.WorldDefinition; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + + +public final class WorldDefinitionArgumentParser extends AbstractArgumentParser { + private static final Path DIMENSIONS_PATH = Bukkit.getWorldContainer().toPath().resolve("world/dimensions"); + + public WorldDefinitionArgumentParser() { + super(TypeSignature.of(WorldDefinition.class), "WorldDefinition"); + } + + @Override + public WorldDefinition parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { + String inputString = singletonInputValidation(inputObjects, String.class); + + NamespacedKey key; + try { + key = NamespacedKey.fromString(inputString); + if (key == null) throw new IllegalArgumentException(); + } catch (IllegalArgumentException e) { + throw ExceptionBuilder.createCommandException("Invalid world key: " + inputString + "."); + } + + WorldDefinition worldDefinition = WorldDefinition.of(key); + return worldDefinition; + } + + @Override + public Supplier> getSuggestions() { + Set worldKeys = new HashSet<>(); + try (Stream namespaces = Files.list(DIMENSIONS_PATH)) { + namespaces.forEach(namespacePath -> { + String namespace = namespacePath.getFileName().toString(); + try (Stream dimensions = Files.list(namespacePath)) { + dimensions.forEach(p -> { + File dimensionFile = p.toFile(); + if (!dimensionFile.isDirectory()) return; + worldKeys.add(Key.key(namespace, dimensionFile.getName())); + }); + } catch (IOException e) { + // If we can't read the dimensions directory, we just skip it. + } + }); + } catch (IOException e) { + // If we can't read the dimensions directory, we just return an empty list of suggestions. + return List::of; + } + + return () -> worldKeys.stream().map(k -> "\"" + k + "\"").toList(); + } + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java new file mode 100644 index 00000000..0a00452f --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/WorldDefinition.java @@ -0,0 +1,49 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.paper.wrapper; + +import net.kyori.adventure.key.Key; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.WorldCreator; + +import java.util.Objects; + + +/** + * A representation of a Bukkit world definition. May be currently loaded or unloaded. + * + * @since 0.0.20 + */ +public final class WorldDefinition { + public final NamespacedKey key; + + private WorldDefinition(NamespacedKey key) { + this.key = key; + } + + public static WorldDefinition of(Key key) { + return new WorldDefinition(new NamespacedKey(key.namespace(), key.value())); + } + + + public World load() { + World loaded = Bukkit.getWorld(key); + if (loaded != null) return loaded; + + return Objects.requireNonNull(Bukkit.createWorld(WorldCreator.ofKey(key)), "Failed to load world with key: " + key); + } + + public void unload(final boolean save) { + World loaded = Bukkit.getWorld(key); + if (loaded != null) Bukkit.unloadWorld(loaded, save); + } + + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java new file mode 100644 index 00000000..1f9ea5e4 --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/wrapper/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package xyz.bitsquidd.bits.paper.wrapper; + +import org.jetbrains.annotations.NotNullByDefault; \ No newline at end of file From b180a15811dedd1a267268c9427fe1ebbd234171 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:55:23 +0100 Subject: [PATCH 08/15] :art: Updated SERIALIZER to be a raw ObjectMapper rather than a JsonMapper. --- .../util/serializer/SerializationManager.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java index 3802c162..ecffd1e4 100644 --- a/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java +++ b/api/src/main/java/xyz/bitsquidd/bits/util/serializer/SerializationManager.java @@ -14,7 +14,6 @@ import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.guava.GuavaModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -35,22 +34,23 @@ private SerializationManager() {} public static final ObjectMapper SERIALIZER = createMapper(); private static ObjectMapper createMapper() { - JsonMapper.Builder builder = JsonMapper.builder() - .enable(MapperFeature.DEFAULT_VIEW_INCLUSION) - .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES) - .disable(MapperFeature.AUTO_DETECT_IS_GETTERS) - .defaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL) - .annotationIntrospector(new NullableAwareIntrospector()); - - getSerializers().forEach(serializer -> registerSerializer(serializer, builder)); - builder.addModule(new GuavaModule()); - builder.addModule(new JavaTimeModule()); - return builder.build(); + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(MapperFeature.DEFAULT_VIEW_INCLUSION); + mapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + mapper.disable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES); + mapper.disable(MapperFeature.AUTO_DETECT_IS_GETTERS); + mapper.setDefaultPropertyInclusion(JsonInclude.Value.ALL_NON_NULL); + mapper.setAnnotationIntrospector(new NullableAwareIntrospector()); + + getSerializers().forEach(serializer -> registerSerializer(serializer, mapper)); + + mapper.registerModule(new GuavaModule()); + mapper.registerModule(new JavaTimeModule()); + return mapper; } - private static void registerSerializer(MultiSerializer serializer, JsonMapper.Builder builder) { + private static void registerSerializer(MultiSerializer serializer, ObjectMapper mapper) { SimpleModule module = new SimpleModule(); Class targetClass = serializer.getTargetClass(); module.addSerializer(targetClass, serializer.jacksonSerializer()); @@ -61,7 +61,7 @@ private static void registerSerializer(MultiSerializer serializer, JsonMa KeyDeserializer keyDeserializer = serializer.jacksonKeyDeserializer(); if (keyDeserializer != null) module.addKeyDeserializer(targetClass, keyDeserializer); - builder.addModule(module); + mapper.registerModule(module); } @SuppressWarnings("unchecked") From 1302d469de4bdb396e9ddbfa3536c6769c5373bb Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:39:08 +0100 Subject: [PATCH 09/15] :sparkles: Added SuggestionSupplier for context-driven suggestions. --- .../argument/parser/SuggestionSupplier.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java new file mode 100644 index 00000000..bc128270 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java @@ -0,0 +1,20 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser; + +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; + +import java.util.List; + + +@FunctionalInterface +public interface SuggestionSupplier { + + List getSuggestions(BitsCommandContext ctx); + +} From 80aa7566a10ebb899f53608b1cce4d4afb7cc63c Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:48:10 +0100 Subject: [PATCH 10/15] :sparkles: Added Key utilities, KeyArgumentParser. --- .../parser/impl/KeyArgumentParser.java | 51 +++++++++++++++++++ .../xyz/bitsquidd/bits/paper/util/Keys.java | 29 +++++++++++ 2 files changed, 80 insertions(+) create mode 100644 minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java create mode 100644 minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java new file mode 100644 index 00000000..3bce5f65 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java @@ -0,0 +1,51 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser.impl; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; + +import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.util.Collections; +import java.util.List; + + +/** + * Argument parser for handling {@link Key Keys}. + * + * @since 0.0.20 + */ + +public final class KeyArgumentParser extends DefaultArgumentParser { + public KeyArgumentParser() { + super(TypeSignature.of(Key.class), "Key"); + } + + @Override + public Key parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Key.key(data); + } + + @Override + public SuggestionSupplier getSuggestions() { + return ctx -> { + String lastInput = ctx.getLastInput(); + if (lastInput.isEmpty()) return Collections.emptyList(); + if (lastInput.contains(Key.DEFAULT_SEPARATOR + "")) return Collections.emptyList(); + return List.of( + lastInput + Key.DEFAULT_SEPARATOR, + Key.MINECRAFT_NAMESPACE + Key.DEFAULT_SEPARATOR + lastInput + ); + }; + } + +} diff --git a/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java new file mode 100644 index 00000000..9bc576b9 --- /dev/null +++ b/minecraft/paper/src/main/java/xyz/bitsquidd/bits/paper/util/Keys.java @@ -0,0 +1,29 @@ +package xyz.bitsquidd.bits.paper.util; + +import net.kyori.adventure.key.Key; +import org.bukkit.NamespacedKey; + +import java.util.Locale; + + +public final class Keys { + private Keys() {} + + public static NamespacedKey NSK(final Key key) { + return new NamespacedKey(key.namespace(), key.value()); + } + + public static String sanitize(String key) { + // Normalize to lower\-case and strip surrounding whitespace + String sanitized = key.trim().toLowerCase(Locale.ROOT); + + // Replace any sequence of invalid characters with a single underscore + sanitized = sanitized.replaceAll("[^a-z0-9_]+", "_"); + + // Remove leading/trailing underscores + sanitized = sanitized.replaceAll("^_+|_+$", ""); + + return sanitized; + } + +} From 708cb6f0e2ed73cf63f9d5b7be198cbab1a19838 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:48:22 +0100 Subject: [PATCH 11/15] :art: Refined UUID and Void argument parsers. --- .../parser/impl/UUIDArgumentParser.java | 56 +++++++++++++------ .../parser/impl/VoidArgumentParser.java | 13 ++--- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java index 710b65b8..677a9e95 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java @@ -9,12 +9,13 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -32,32 +33,53 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class UUIDArgumentParser extends AbstractArgumentParser { + +public final class UUIDArgumentParser extends DefaultArgumentParser { + private static final List DASH_POSITIONS = List.of(8, 12, 16, 20); + private static final char DASH_CHAR = '-'; + private static final int HEX_CHAR_COUNT = 32; + private static final int CANONICAL_LENGTH = HEX_CHAR_COUNT + DASH_POSITIONS.size(); public UUIDArgumentParser() { super(TypeSignature.of(UUID.class), "UUID"); } @Override - public UUID parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - String hexOnly = inputString.replaceAll("[^a-fA-F0-9]", ""); - - if (hexOnly.length() != 32) throw ExceptionBuilder.createCommandException("Invalid UUID format: " + inputString); + public UUID parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + String hexOnly = data.replaceAll("[^a-fA-F0-9]", ""); + if (hexOnly.length() != HEX_CHAR_COUNT) throw ExceptionBuilder.createCommandException("Invalid UUID format: " + data); - String canonical = hexOnly.substring(0, 8) + "-" - + hexOnly.substring(8, 12) + "-" - + hexOnly.substring(12, 16) + "-" - + hexOnly.substring(16, 20) + "-" - + hexOnly.substring(20); + StringBuilder canonical = new StringBuilder(CANONICAL_LENGTH); + for (int i = 0; i < hexOnly.length(); i++) { + if (DASH_POSITIONS.contains(i)) canonical.append(DASH_CHAR); + canonical.append(hexOnly.charAt(i)); + } try { - return UUID.fromString(canonical); + return UUID.fromString(canonical.toString()); } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException("Invalid UUID format: " + inputString); + throw ExceptionBuilder.createCommandException("Invalid UUID format: " + data); } } + @Override + public SuggestionSupplier getSuggestions() { + return ctx -> { + String lastInput = ctx.getLastInput(); + if (lastInput.isEmpty()) return Collections.emptyList(); + if (lastInput.length() >= CANONICAL_LENGTH) return Collections.emptyList(); + + String hexOnly = lastInput.replaceAll("[^a-fA-F0-9]", ""); + if (hexOnly.length() > HEX_CHAR_COUNT) return Collections.emptyList(); + + StringBuilder suggestion = new StringBuilder(lastInput); + for (int pos : DASH_POSITIONS) { + if (suggestion.length() > pos && suggestion.charAt(pos) != DASH_CHAR) { + suggestion.insert(pos, DASH_CHAR); + } + } + return List.of(suggestion.toString()); + }; + } + } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java index 85451c0f..2f039616 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java @@ -7,12 +7,10 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * A fallback argument parser that explicitly consumes and returns {@code null} for {@link Void} types. @@ -21,15 +19,16 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class VoidArgumentParser extends AbstractArgumentParser { - public VoidArgumentParser() { +public final class VoidArgumentParser extends DefaultArgumentParser { + public static final VoidArgumentParser INSTANCE = new VoidArgumentParser(); + + private VoidArgumentParser() { super(TypeSignature.of(Void.class), "Void"); } @Override - public Void parse(List inputObjects, BitsCommandContext ctx) { + public Void parse(String inputObjects, BitsCommandContext ctx) { return null; } From 7655b23833f942f0cf691e210a9a6cfbf880644a Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:48:50 +0100 Subject: [PATCH 12/15] :sparkles: Added BasicArgumentParser for basic string-based arguments. --- .../argument/parser/BasicArgumentParser.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java new file mode 100644 index 00000000..4be1e3e1 --- /dev/null +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/BasicArgumentParser.java @@ -0,0 +1,23 @@ +/* + * This file is part of a Bit libraries package. + * Licensed under the GNU Lesser General Public License v3.0. + * + * Copyright (c) 2023-2026 ImBit + */ + +package xyz.bitsquidd.bits.mc.command.argument.parser; + +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + + +/** + * Argument parser designed for types that can be directly parsed from a single string input without any additional validation or transformation. + * + * @param + */ +public abstract class BasicArgumentParser extends ArgumentParser { + protected BasicArgumentParser(TypeSignature typeSignature, String argumentName) { + super(typeSignature, argumentName, String.class); + } + +} From 63e242e3c9317ba3a4cbff96d88a847719787aec Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:49:40 +0100 Subject: [PATCH 13/15] :sparkles: Fully refined suggestion providing to be automatically generated based on argument data classes. --- .../bitsquidd/bits/util/wrapper/Enums.java | 9 + .../FabricClientBitsArgumentRegistry.java | 5 +- .../command/provider/BitsCommandProvider.java | 5 +- .../mc/command/BrigadierTreeGenerator.java | 4 +- .../argument/BitsArgumentRegistry.java | 49 ++-- .../argument/BrigadierArgumentMapping.java | 4 +- .../parser/AbstractArgumentParser.java | 224 ------------------ .../argument/parser/ArgumentParser.java | 216 ++++++++++++++++- .../impl/GreedyStringArgumentParser.java | 18 +- .../parser/impl/KeyArgumentParser.java | 4 +- .../parser/impl/LinkArgumentParser.java | 17 +- .../parser/impl/UUIDArgumentParser.java | 4 +- .../parser/impl/VoidArgumentParser.java | 4 +- ...entParser.java => EnumArgumentParser.java} | 19 +- .../impl/generic/GenericEnumParser.java | 28 +-- .../impl/primitive/BooleanArgumentParser.java | 8 +- .../impl/primitive/DoubleArgumentParser.java | 3 +- .../impl/primitive/FloatArgumentParser.java | 3 +- .../impl/primitive/IntegerArgumentParser.java | 3 +- .../impl/primitive/LongArgumentParser.java | 3 +- .../primitive/PrimitiveArgumentParser.java | 18 +- .../impl/primitive/StringArgumentParser.java | 3 +- .../mc/command/util/CommandParameterInfo.java | 14 +- .../parser/impl/BlockPosArgumentParser.java | 32 +-- .../parser/impl/LocationArgumentParser.java | 35 +-- .../impl/PlayerCollectionArgumentParser.java | 22 +- .../impl/PlayerSingleArgumentParser.java | 25 +- .../parser/impl/WorldArgumentParser.java | 29 +-- .../impl/WorldDefinitionArgumentParser.java | 28 +-- 29 files changed, 366 insertions(+), 470 deletions(-) delete mode 100644 minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java rename minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/{AbstractEnumArgumentParser.java => EnumArgumentParser.java} (72%) diff --git a/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java b/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java index 3f8ae8b0..d4b8f210 100644 --- a/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java +++ b/api/src/main/java/xyz/bitsquidd/bits/util/wrapper/Enums.java @@ -73,6 +73,15 @@ public static > String toIdentifier(E constant) { } + /** + * Resolves an enum constant by a string identifier derived from the constant, ignoring case sensitivity. + * + * @since 0.0.20 + */ + public static > Optional getFromIdentifier(Class enumClass, String identifier) { + return getFromIdentifier(enumClass, constant -> toIdentifier(constant).equals(identifier)); + } + /** * Searches for an enum constant that matches a specific predicate. * diff --git a/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java b/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java index 472e19bd..3acdd95c 100644 --- a/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java +++ b/minecraft/fabric/src/client/java/xyz/bitsquidd/bits/mc/command/FabricClientBitsArgumentRegistry.java @@ -11,19 +11,20 @@ import net.fabricmc.loader.api.FabricLoader; import xyz.bitsquidd.bits.mc.command.argument.BitsArgumentRegistry; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.provider.BitsCommandProvider; import xyz.bitsquidd.bits.wrapper.collection.AddableSet; import static xyz.bitsquidd.bits.mc.command.FabricBitsCommandManager.COMMAND_INSTANCE_ENTRYPOINT; + public class FabricClientBitsArgumentRegistry extends BitsArgumentRegistry { /** * Fabric mods must use the {@code COMMAND_INSTANCE_ENTRYPOINT} entrypoint to provide command parsers. */ @Override - protected AddableSet> initialiseParsers() { + protected AddableSet> initialiseParsers() { return super.initialiseParsers().addAll(FabricLoader.getInstance() .getEntrypoints(COMMAND_INSTANCE_ENTRYPOINT, BitsCommandProvider.class) .stream() diff --git a/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java b/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java index f922568a..4f6af7e5 100644 --- a/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java +++ b/minecraft/fabric/src/main/java/xyz/bitsquidd/bits/mc/command/provider/BitsCommandProvider.java @@ -8,15 +8,16 @@ package xyz.bitsquidd.bits.mc.command.provider; import xyz.bitsquidd.bits.mc.command.BitsCommand; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.collection.AddableSet; + public interface BitsCommandProvider { default AddableSet getCommands() { return AddableSet.empty(); } - default AddableSet> getArguments() { + default AddableSet> getArguments() { return AddableSet.empty(); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java index 2a3654ba..911ba7dd 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/BrigadierTreeGenerator.java @@ -16,7 +16,7 @@ import xyz.bitsquidd.bits.Bits; import xyz.bitsquidd.bits.log.Logger; import xyz.bitsquidd.bits.mc.command.argument.BrigadierArgumentMapping; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.debugging.TreeDebugger; import xyz.bitsquidd.bits.mc.command.exception.CommandBuildException; import xyz.bitsquidd.bits.mc.command.util.BitsCommandBuilder; @@ -221,7 +221,7 @@ private Command createCommandExecution( ArrayList<@Nullable Object> parsedArguments = new ArrayList<>(); for (CommandParameterInfo parameter : methodInfo.getAllParameters()) { - AbstractArgumentParser parser = parameter.getParser(); + ArgumentParser parser = parameter.getParser(); // Collect primitive objects for the parameter ArrayList<@Nullable Object> primitiveObjects = new ArrayList<>(); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java index 79b2bad0..549830ab 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BitsArgumentRegistry.java @@ -18,9 +18,10 @@ import org.jetbrains.annotations.Nullable; import xyz.bitsquidd.bits.log.Logger; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.impl.VoidArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.impl.generic.GenericEnumParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive.PrimitiveArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.CommandBuildException; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.util.reflection.ReflectionUtils; @@ -46,7 +47,7 @@ * Example internal usage: *
{@code
  * BitsArgumentRegistry registry = manager.getArgumentRegistry();
- * AbstractArgumentParser parser = registry.getParser(TypeSignature.of(Player.class));
+ * AbstractArgumentParser parser = registry.getParser(TypeSignature.of(Player.class));
  * }
* * @param the type of the platform's original source object @@ -54,10 +55,10 @@ * @since 0.0.10 */ public abstract class BitsArgumentRegistry { - private final Map, AbstractArgumentParser> parsers = new HashMap<>(); + private final Map, ArgumentParser> parsers = new HashMap<>(); public BitsArgumentRegistry() { - List> initialParsers = new ArrayList<>(initialiseParsers().build()); + List> initialParsers = new ArrayList<>(initialiseParsers().build()); initialParsers.forEach(parser -> parsers.put(parser.getTypeSignature(), parser)); } @@ -100,8 +101,8 @@ public BitsArgumentRegistry() { * @since 0.0.10 */ @SuppressWarnings("unchecked") - protected AddableSet> initialiseParsers() { - return AddableSet.of((Set>)(Set)ReflectionUtils.General.createClassesInDir("*", AbstractArgumentParser.class, ScannerFlags.DEFAULT)); + protected AddableSet> initialiseParsers() { + return AddableSet.of((Set>)(Set)ReflectionUtils.General.createClassesInDir("*", ArgumentParser.class, ScannerFlags.DEFAULT)); } /** @@ -114,11 +115,11 @@ protected AddableSet> initialiseParsers() { * @since 0.0.10 */ @SuppressWarnings({"unchecked", "rawtypes"}) - public AbstractArgumentParser getParser(TypeSignature typeSignature) { + public ArgumentParser getParser(TypeSignature typeSignature) { // We could consider implementing some form of search for inherited types. // This probably shouldn't be implemented as it'll cause type inconsistencies with functions. // Developers should design their command functions accordingly to use the lowest available type. - AbstractArgumentParser parser = parsers.get(typeSignature); + ArgumentParser parser = parsers.get(typeSignature); // If no parser found, we allow generic enums to be parsed. if (parser == null) { @@ -129,7 +130,7 @@ public AbstractArgumentParser getParser(TypeSignature typeSignature) { } Logger.error("No parser registered for type: " + typeSignature); - return new VoidArgumentParser(); + return VoidArgumentParser.INSTANCE; } return parser; @@ -145,18 +146,20 @@ public AbstractArgumentParser getParser(TypeSignature typeSignature) { * * @since 0.0.10 */ - public List getArgumentTypeContainer(AbstractArgumentParser parser, String baseName) { + public List getArgumentTypeContainer(ArgumentParser parser, String baseName) { List holders = new ArrayList<>(); List inputTypes = parser.getInputTypes(); // Break down the type signature into its primitives. for (int i = 0; i < inputTypes.size(); i++) { InputTypeContainer nestedTypeSigature = inputTypes.get(i); + // Get the command parser required for this input type - AbstractArgumentParser nestedParser = getParser(nestedTypeSigature.typeSignature()); + ArgumentParser nestedParser = getParser(nestedTypeSigature.typeSignature()); boolean handled = false; - // If its a primitive, we can directly add it + + // If it's a primitive, we can directly add it if (nestedParser.getInputTypes().size() == 1) { InputTypeContainer inputType = nestedParser.getInputTypes().getFirst(); ArgumentType brigadierType = toArgumentType(inputType.typeSignature()); @@ -196,30 +199,32 @@ public List getArgumentTypeContainer(AbstractArgumentP * @throws CommandSyntaxException if there's an error in parsing validation * @since 0.0.10 */ - public Object parseArguments(AbstractArgumentParser parser, List primitiveList, BitsCommandContext ctx) throws CommandSyntaxException { + public O parseArguments(ArgumentParser parser, List primitiveList, BitsCommandContext ctx) throws CommandSyntaxException { List inputTypes = parser.getInputTypes(); - - // If the input size is 1, we can directly parse it - if (inputTypes.size() == 1) return parser.parse(primitiveList, ctx); - List parsedObjects = new ArrayList<>(); for (InputTypeContainer inputType : inputTypes) { - AbstractArgumentParser nestedParser = getParser(inputType.typeSignature()); + ArgumentParser nestedParser = getParser(inputType.typeSignature()); + + if (nestedParser instanceof PrimitiveArgumentParser) { + // it's a vanilla/terminal type (String, Double, etc.) take the primitive as-is. + if (primitiveList.isEmpty()) throw new CommandBuildException("Not enough arguments for " + inputType.typeName()); + parsedObjects.add(primitiveList.removeFirst()); + continue; + } int requiredSize = nestedParser.getInputTypes().size(); if (primitiveList.size() < requiredSize) throw new CommandBuildException("Not enough arguments for " + inputType.typeName()); - ArrayList inputObjects = new ArrayList<>(primitiveList.subList(0, requiredSize)); + List inputObjects = new ArrayList<>(primitiveList.subList(0, requiredSize)); primitiveList = new ArrayList<>(primitiveList.subList(requiredSize, primitiveList.size())); - // Recursively parse the primitives with the appropriate parser Object parsedObject = parseArguments(nestedParser, inputObjects, ctx); parsedObjects.add(parsedObject); } - // Now that we have all our parsed objects, we can pass them to the main parser - return parser.parse(parsedObjects, ctx); + D data = parser.toData(parsedObjects); + return parser.parse(data, ctx); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java index f0f73c36..2f4334ee 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/BrigadierArgumentMapping.java @@ -11,14 +11,14 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import xyz.bitsquidd.bits.mc.command.BitsCommandManager; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; /** * A data container linking an expected return type with a built Brigadier argument type. *

- * This record is primarily used when compiling custom {@link AbstractArgumentParser} + * This record is primarily used when compiling custom {@link ArgumentParser} * instances into their base Brigadier representations for tree registration. * * @param argumentType the underlying Brigadier argument type implementation diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java deleted file mode 100644 index 579ea5c9..00000000 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/AbstractArgumentParser.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This file is part of a Bit libraries package. - * Licensed under the GNU Lesser General Public License v3.0. - * - * Copyright (c) 2023-2026 ImBit - */ - -package xyz.bitsquidd.bits.mc.command.argument.parser; - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import org.jetbrains.annotations.Nullable; - -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; -import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; -import xyz.bitsquidd.bits.wrapper.type.TypeSignature; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -/** - * A parser responsible for converting a list of raw command arguments into a specific object type. - *

- * This class serves as the foundation for all custom argument parsing within the Bits command framework. - * Implementations define how string inputs or other basic types from Brigadier are structurally resolved - * into complex objects, and optionally provide tab-completion suggestions. - *

- * Example parser implementation: - *

{@code
- * public class PlayerArgumentParser extends AbstractArgumentParser {
- *     public PlayerArgumentParser() {
- *         super(TypeSignature.of(Player.class), "player_name");
- *     }
- *
- *     // Override methods to dictate parsing logic and input types.
- * }
- * }
- * - * @param The type this parser converts to. - * - * @since 0.0.10 - */ -public abstract class AbstractArgumentParser { - private final TypeSignature typeSignature; // The type signature this parser handles - private final String argumentName; // The name of the argument, used while displaying suggestions - - /** - * @param typeSignature the expected type this parser handles - * @param argumentName the label to display in command syntax suggestions - * - * @since 0.0.10 - */ - protected AbstractArgumentParser(TypeSignature typeSignature, String argumentName) { - this.typeSignature = typeSignature; - this.argumentName = argumentName; - } - - - /** - * Parses the provided primitive objects into the target complex object type {@code O}. - * - * @param inputObjects the raw inputs captured from the command context - * @param ctx the command execution context - * - * @return the successfully parsed object - * - * @throws CommandSyntaxException if the input is malformed or invalid - * @since 0.0.10 - */ - public abstract O parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException; - - /** - * Returns a list of required objects the parser expects in. - * In the case of most custom parsers this will be a String. - *

- * In the case a non-vanilla primitive is passed in, we expect a parser to be present for this.

- * For example:

    - *
  • An Integer parser would expect a single int {@code List.of(Integer.class)}
  • - *
  • A Bukkit Location parser may expect three doubles and a World {@code List.of(Double.class, Double.class, Double.class, World.class)}
  • - *
- * - * @return A list of expected required objects. - * - * @since 0.0.10 - */ - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(String.class), getArgumentName())); - } - - - //region Validation - - /** - * Helper function to validate singleton inputs for basic argument parsers. - * - * @param the type parameter of the expected output - * @param inputObjects The list of input objects to validate, expected to contain exactly one object. - * @param expectedType The expected type of the single input object. - * - * @return The validated input object cast to the expected type. - * - * @throws CommandSyntaxException if the validation fails - * @since 0.0.10 - */ - protected I singletonInputValidation(List inputObjects, Class expectedType) throws CommandSyntaxException { - List inputTypes = getInputTypes(); - if (inputTypes.size() != 1) throw ExceptionBuilder.createCommandException("Expected exactly one input type, got " + inputTypes.size() + "."); - if (inputTypes.getFirst().typeSignature().toRawType() != expectedType) { - throw ExceptionBuilder.createCommandException("Expected input type signature to be " + expectedType.getSimpleName() + ", got " + inputTypes.getFirst().typeSignature().toRawType().getSimpleName() + "."); - } - - if (inputObjects.size() != 1) throw ExceptionBuilder.createCommandException("Expected exactly one input object, got " + inputObjects.size() + "."); - Object value = inputObjects.getFirst(); - - if (!expectedType.isInstance(value)) { - throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); - } - - return expectedType.cast(value); - } - - /** - * Helper function to validate multiple arguments for complex argument parsers. - * - * @param inputObjects The list of input objects to validate, expected to match the size and types of the list returned by {@link #getInputTypes()}. - * - * @return A list of validated input objects cast to their expected types. - * - * @throws CommandSyntaxException if the input size or types are incorrect - * @since 0.0.10 - */ - protected List inputValidation(List inputObjects) throws CommandSyntaxException { - List inputTypes = getInputTypes(); - int inputSize = inputTypes.size(); - - if (inputObjects.size() != inputSize) { - throw ExceptionBuilder.createCommandException("Expected exactly " + inputSize + " input object" + (inputSize > 1 ? "s" : "") + ", got " + inputObjects.size() + "."); - } - - List returnList = new ArrayList<>(); - - for (int i = 0; i < inputSize; i++) { - InputTypeContainer expectedTypeContainer = inputTypes.get(i); - Object value = inputObjects.get(i); - Class expectedType = expectedTypeContainer.typeSignature().toRawType(); - - if (!expectedType.isInstance(value)) { - throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); - } - - returnList.add(expectedType.cast(value)); - } - - return returnList; - } - //endregion - - - /** - * Retrieves a Brigadier compatible suggestion provider for this argument parser. - *

- * This method evaluates {@link #getSuggestions()} to dynamically supply tab completion results. - * - * @param the command source type - * - * @return the suggestion provider context for Brigadier nodes - * - * @since 0.0.10 - */ - public final SuggestionProvider getSuggestionProvider() { - return (ctx, builder) -> { - Supplier> suggestionSupplier = getSuggestions(); - if (suggestionSupplier == null) return builder.buildFuture(); - - List suggestions = suggestionSupplier.get(); - String remaining = builder.getRemaining().toLowerCase(); - - for (String suggestion : suggestions) { - if (suggestion.toLowerCase().startsWith(remaining)) { - builder.suggest(suggestion); - } - } - - return builder.buildFuture(); - }; - } - - /** - * Returns a dynamically loaded list of suggestions for the argument at a given context state. - * - * @return a supplier providing available string suggestions, or null if none exist - * - * @since 0.0.10 - */ - public @Nullable Supplier> getSuggestions() { - return null; - } - - - /** - * Retrieves the core type signature handled by this parser. - * - * @return the type signature - * - * @since 0.0.10 - */ - public TypeSignature getTypeSignature() { - return typeSignature; - } - - /** - * Retrieves the argument name meant for display in syntax sequences. - * - * @return the argument label - * - * @since 0.0.10 - */ - public String getArgumentName() { - return argumentName; - } - -} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java index 53a0242d..3d755312 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java @@ -7,17 +7,215 @@ package xyz.bitsquidd.bits.mc.command.argument.parser; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import org.jetbrains.annotations.Nullable; + +import xyz.bitsquidd.bits.mc.command.BitsCommandManager; +import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.GreedyStringArgumentParser; +import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; +import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.wrapper.GreedyString; +import xyz.bitsquidd.bits.wrapper.type.TypeSignature; + +import java.lang.reflect.Constructor; +import java.lang.reflect.RecordComponent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** - * Annotation for marking classes as argument parsers. + * A parser responsible for converting a list of raw command arguments into a specific object type. + *

+ * This class serves as the foundation for all custom argument parsing within the Bits command framework. + * Implementations define how string inputs or other basic types from Brigadier are structurally resolved + * into complex objects, and optionally provide tab-completion suggestions. + *

+ * Example parser implementation: + *

{@code
+ * public class PlayerArgumentParser extends AbstractArgumentParser {
+ *     public PlayerArgumentParser() {
+ *         super(TypeSignature.of(Player.class), "player_name");
+ *     }
+ *
+ *     // Override methods to dictate parsing logic and input types.
+ * }
+ * }
+ * + * @param The type this parser converts to. + * @param The type of the input data (e.g., String, Integer, World) this parser expects to receive from Brigadier or parser. * - * @since 0.0.13 + * @since 0.0.10 */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ArgumentParser {} +public abstract class ArgumentParser { + private final TypeSignature typeSignature; // The type signature this parser handles + private final String argumentName; // The name of the argument, used while displaying suggestions + private final Class dataClass; // The class of the input data type + private final Constructor recordConstructor; + + /** + * @param typeSignature the expected type this parser handles + * @param argumentName the label to display in command syntax suggestions + * @param dataClass the class of the input data type + * + * @since 0.0.10 + */ + protected ArgumentParser(TypeSignature typeSignature, String argumentName, Class dataClass) { + this.typeSignature = typeSignature; + this.argumentName = argumentName; + this.dataClass = dataClass; + this.recordConstructor = dataClass.isRecord() ? resolveCanonicalConstructor(dataClass) : null; + } + + + private static Constructor resolveCanonicalConstructor(Class recordClass) { + Class[] paramTypes = Arrays.stream(recordClass.getRecordComponents()) + .map(RecordComponent::getType) + .toArray(Class[]::new); + + try { + return recordClass.getDeclaredConstructor(paramTypes); + } catch (NoSuchMethodException e) { + // Unreachable: every record has a canonical constructor. + throw new IllegalStateException("No canonical constructor found for record " + recordClass.getSimpleName(), e); + } + } + + + /** + * Parses the provided primitive objects into the target complex object type {@code O}. + * + * @param data the raw inputs captured from the command context + * @param ctx the command execution context + * + * @return the successfully parsed object + * + * @throws CommandSyntaxException if the input is malformed or invalid + * @since 0.0.10 + */ + public abstract O parse(D data, BitsCommandContext ctx) throws CommandSyntaxException; + + /** + * Returns a list of required objects the parser expects in. + * In the case of most custom parsers this will be a String. + *

+ * In the case a non-vanilla primitive is passed in, we expect a parser to be present for this.

+ * For example:

    + *
  • An Integer parser would expect a single int {@code List.of(Integer.class)}
  • + *
  • A Bukkit Location parser may expect three doubles and a World {@code List.of(Double.class, Double.class, Double.class, World.class)}
  • + *
+ * + * @return A list of expected required objects. + * + * @since 0.0.20 + */ + public final List getInputTypes() { + // Special case for GreedyString: it captures all remaining input as a single string. + if (this instanceof GreedyStringArgumentParser) return List.of(new InputTypeContainer(TypeSignature.of(GreedyString.class), getArgumentName())); + + // Special case for non-record types: treat the entire data class the input. + if (!dataClass.isRecord()) return List.of(new InputTypeContainer(TypeSignature.of(dataClass), argumentName)); + + RecordComponent[] components = dataClass.getRecordComponents(); + List inputTypes = new ArrayList<>(components.length); + + for (RecordComponent component : components) { + inputTypes.add(new InputTypeContainer(TypeSignature.of(component.getType()), component.getName())); + } + + return inputTypes; + } + + + public final D toData(List inputObjects) throws CommandSyntaxException { + List inputTypes = getInputTypes(); + + if (inputObjects.size() != inputTypes.size()) throw ExceptionBuilder.createCommandException("Expected exactly " + inputTypes.size() + " input object" + (inputTypes.size() > 1 ? "s" : "") + ", got " + inputObjects.size() + "."); + Object[] validated = new Object[inputTypes.size()]; + + for (int i = 0; i < inputTypes.size(); i++) { + Class expectedType = inputTypes.get(i).typeSignature().toRawType(); + Object value = inputObjects.get(i); + + if (!expectedType.isInstance(value)) throw ExceptionBuilder.createCommandException("Expected input object of type " + expectedType.getSimpleName() + ", got " + value.getClass().getSimpleName() + "."); + validated[i] = value; + } + + // Single-value case: D itself is the scalar type (e.g. D = Double) + if (!dataClass.isRecord()) return dataClass.cast(validated[0]); + + try { + return recordConstructor.newInstance(validated); + } catch (ReflectiveOperationException e) { + throw ExceptionBuilder.createCommandException("Failed to construct " + dataClass.getSimpleName() + ": " + e.getMessage()); + } + } + + + /** + * Retrieves a Brigadier compatible suggestion provider for this argument parser. + *

+ * This method evaluates {@link #getSuggestions} to dynamically supply tab completion results. + * + * @param the command source type + * + * @return the suggestion provider context for Brigadier nodes + * + * @since 0.0.10 + */ + public final SuggestionProvider createSuggestionProvider() { + SuggestionSupplier suggestionSupplier = getSuggestions(); + if (suggestionSupplier == null) return (ctx, builder) -> builder.buildFuture(); // No suggestions available + BitsCommandManager manager = BitsCommandManager.get(); + + return (ctx, builder) -> { + List suggestions = suggestionSupplier.getSuggestions(manager.createContext(ctx)); + String remaining = builder.getRemaining(); + + for (String suggestion : suggestions) { + if (suggestion.equalsIgnoreCase(remaining)) { + builder.suggest(suggestion); + } + } + + return builder.buildFuture(); + }; + } + + /** + * Returns a dynamically loaded list of suggestions for the argument at a given context state. + * + * @return a supplier providing available string suggestions, or null if none exist + * + * @since 0.0.10 + */ + public @Nullable SuggestionSupplier getSuggestions() { + return null; + } + + + /** + * Retrieves the core type signature handled by this parser. + * + * @return the type signature + * + * @since 0.0.10 + */ + public TypeSignature getTypeSignature() { + return typeSignature; + } + + /** + * Retrieves the argument name meant for display in syntax sequences. + * + * @return the argument label + * + * @since 0.0.10 + */ + public String getArgumentName() { + return argumentName; + } + +} diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java index e1aedc2a..7546c63d 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/GreedyStringArgumentParser.java @@ -8,14 +8,11 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; import xyz.bitsquidd.bits.mc.command.BrigadierTreeGenerator; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.GreedyString; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * Argument parser for {@link GreedyString} values. @@ -24,20 +21,15 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class GreedyStringArgumentParser extends AbstractArgumentParser { - public GreedyStringArgumentParser() { - super(TypeSignature.of(GreedyString.class), "String..."); - } - @Override - public GreedyString parse(List inputObjects, BitsCommandContext ctx) { - return GreedyString.of((String)inputObjects.getFirst()); // Note: can't use singletonInputValidation as GreedyString is special... +public final class GreedyStringArgumentParser extends ArgumentParser { + public GreedyStringArgumentParser() { + super(TypeSignature.of(GreedyString.class), "String...", String.class); } @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(GreedyString.class), getArgumentName())); + public GreedyString parse(String data, BitsCommandContext ctx) { + return GreedyString.of(data); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java index 3bce5f65..0664f6b0 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/KeyArgumentParser.java @@ -10,7 +10,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.kyori.adventure.key.Key; -import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -25,7 +25,7 @@ * @since 0.0.20 */ -public final class KeyArgumentParser extends DefaultArgumentParser { +public final class KeyArgumentParser extends BasicArgumentParser { public KeyArgumentParser() { super(TypeSignature.of(Key.class), "Key"); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java index e15fe7f0..0e4bd58d 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java @@ -9,14 +9,12 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.Link; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; @@ -29,20 +27,19 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class LinkArgumentParser extends AbstractArgumentParser { + +public final class LinkArgumentParser extends BasicArgumentParser { public LinkArgumentParser() { super(TypeSignature.of(Link.class), "URL"); } @Override - public Link parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - return Link.of(inputString); + public Link parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Link.of(data); } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { return () -> Stream.of("https://", "http://", "www.").toList(); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java index 677a9e95..327b6786 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/UUIDArgumentParser.java @@ -9,7 +9,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; @@ -34,7 +34,7 @@ * @since 0.0.10 */ -public final class UUIDArgumentParser extends DefaultArgumentParser { +public final class UUIDArgumentParser extends BasicArgumentParser { private static final List DASH_POSITIONS = List.of(8, 12, 16, 20); private static final char DASH_CHAR = '-'; private static final int HEX_CHAR_COUNT = 32; diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java index 2f039616..cb7afb28 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/VoidArgumentParser.java @@ -7,7 +7,7 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import xyz.bitsquidd.bits.mc.command.argument.parser.DefaultArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -20,7 +20,7 @@ * @since 0.0.10 */ -public final class VoidArgumentParser extends DefaultArgumentParser { +public final class VoidArgumentParser extends BasicArgumentParser { public static final VoidArgumentParser INSTANCE = new VoidArgumentParser(); private VoidArgumentParser() { diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java similarity index 72% rename from minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java rename to minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java index 3242f6e7..27014b53 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/AbstractEnumArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java @@ -10,15 +10,16 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.jetbrains.annotations.Nullable; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; + /** * Base abstract parser providing common parsing and suggestion logic for enum types. *

@@ -38,7 +39,7 @@ * * @since 0.0.10 */ -public abstract class AbstractEnumArgumentParser> extends AbstractArgumentParser { +public abstract class EnumArgumentParser> extends BasicArgumentParser { private final Class enumClass; /** @@ -47,28 +48,26 @@ public abstract class AbstractEnumArgumentParser> extends Abst * @throws IllegalArgumentException if the provided class is not an enum * @since 0.0.10 */ - public AbstractEnumArgumentParser(Class enumClass) { + public EnumArgumentParser(Class enumClass) { super(TypeSignature.of(Enum.class), enumClass.getName()); this.enumClass = enumClass; if (!enumClass.isEnum()) throw new IllegalArgumentException("Provided class " + enumClass.getName() + " is not an enum!"); } @Override - public T parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - + public T parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { T enumValue; try { - enumValue = Enum.valueOf(enumClass, inputString); + enumValue = Enum.valueOf(enumClass, data); } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException(inputString + " is not a valid " + enumClass.getSimpleName() + "."); + throw ExceptionBuilder.createCommandException(data + " is not a valid " + enumClass.getSimpleName() + "."); } return enumValue; } @Override - public @Nullable Supplier> getSuggestions() { + public @Nullable SuggestionSupplier getSuggestions() { return () -> enumClass.isEnum() ? Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList() : List.of(); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java index 0f522bcf..27daae97 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java @@ -9,21 +9,21 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; -import xyz.bitsquidd.bits.mc.command.argument.parser.impl.abs.AbstractEnumArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.BasicArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; +import xyz.bitsquidd.bits.mc.command.argument.parser.impl.abs.EnumArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; +import xyz.bitsquidd.bits.util.wrapper.Enums; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; + /** * A fallback, generically instantiated parser that handles unmapped enum types automatically. *

- * Developer Note: Custom enum parsers should override {@link AbstractEnumArgumentParser} + * Developer Note: Custom enum parsers should override {@link EnumArgumentParser} * if you want more fine-grained control over enum arguments and tab completion. *

* This implementation serves as a basic parser that is fallen back upon when no specific enum parser is available. @@ -32,9 +32,8 @@ * * @since 0.0.10 */ -@ArgumentParser -public final class GenericEnumParser> extends AbstractArgumentParser { +public final class GenericEnumParser> extends BasicArgumentParser { private final Class enumClass; public GenericEnumParser(Class enumClass) { @@ -43,18 +42,13 @@ public GenericEnumParser(Class enumClass) { } @Override - public E parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - for (E constant : enumClass.getEnumConstants()) { - if (constant.name().equalsIgnoreCase(inputString)) return constant; - } - - throw ExceptionBuilder.createCommandException("Enum constant not found: " + inputString + " for enum " + enumClass.getSimpleName() + "."); + public E parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + return Enums.getFromIdentifier(enumClass, data) + .orElseThrow(() -> ExceptionBuilder.createCommandException("Enum constant not found: " + data + " for enum " + enumClass.getSimpleName() + ".")); } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { return () -> Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList(); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java index 444646e2..9b2fb11d 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java @@ -7,17 +7,17 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import java.util.List; -import java.util.function.Supplier; + /** * Argument parser for Boolean values. * * @since 0.0.10 */ -@ArgumentParser + public final class BooleanArgumentParser extends PrimitiveArgumentParser { public BooleanArgumentParser() { @@ -25,7 +25,7 @@ public BooleanArgumentParser() { } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { return () -> List.of("true", "false"); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java index 987fa30e..44dd84c1 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/DoubleArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Double values. * * @since 0.0.10 */ -@ArgumentParser + public final class DoubleArgumentParser extends PrimitiveArgumentParser { public DoubleArgumentParser() { super(Double.class, "Double"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java index b0bc401d..22162ca9 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/FloatArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Float values. * * @since 0.0.10 */ -@ArgumentParser + public final class FloatArgumentParser extends PrimitiveArgumentParser { public FloatArgumentParser() { super(Float.class, "Float"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java index 79be0c9a..c04430a7 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/IntegerArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Integer values. * * @since 0.0.10 */ -@ArgumentParser + public final class IntegerArgumentParser extends PrimitiveArgumentParser { public IntegerArgumentParser() { diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java index d59ce089..8e4e88df 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/LongArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for Long values. * * @since 0.0.10 */ -@ArgumentParser + public final class LongArgumentParser extends PrimitiveArgumentParser { public LongArgumentParser() { diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java index 12fb813c..ab6d26bc 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/PrimitiveArgumentParser.java @@ -9,12 +9,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; /** * A foundation parser designed specifically for types that correspond to underlying Brigadier primitives. @@ -26,21 +24,15 @@ * * @since 0.0.10 */ -public abstract class PrimitiveArgumentParser extends AbstractArgumentParser { +public abstract class PrimitiveArgumentParser extends ArgumentParser { protected PrimitiveArgumentParser(Class outputClass, String argumentName) { - super(TypeSignature.of(outputClass), argumentName); + super(TypeSignature.of(outputClass), argumentName, outputClass); } @Override - @SuppressWarnings("unchecked") - public final O parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - return (O)singletonInputValidation(inputObjects, getTypeSignature().toRawType()); - } - - @Override - public final List getInputTypes() { - return List.of(new InputTypeContainer(getTypeSignature(), getArgumentName())); + public final O parse(O data, BitsCommandContext ctx) throws CommandSyntaxException { + return data; } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java index c8373738..fd241bea 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/StringArgumentParser.java @@ -7,14 +7,13 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl.primitive; -import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; /** * Argument parser for String values. * * @since 0.0.10 */ -@ArgumentParser + public final class StringArgumentParser extends PrimitiveArgumentParser { public StringArgumentParser() { super(String.class, "String"); diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java index 145cdc0c..346be50f 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/util/CommandParameterInfo.java @@ -12,7 +12,7 @@ import xyz.bitsquidd.bits.mc.command.BitsCommandManager; import xyz.bitsquidd.bits.mc.command.argument.BrigadierArgumentMapping; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; import java.lang.reflect.Parameter; @@ -39,7 +39,7 @@ public class CommandParameterInfo { private final TypeSignature typeSignature; // TypeSignature of the parameter - private final AbstractArgumentParser parser; + private final ArgumentParser parser; private final List heldArguments = new ArrayList<>(); // Direct mapping from the custom args to brigadier ArgumentTypes @@ -81,13 +81,13 @@ public CommandParameterInfo(Parameter parameter) { if (useArgSuggestions) { TypeSignature inputType = parser.getInputTypes().get(i).typeSignature(); - AbstractArgumentParser inputParser = BitsCommandManager.get().getArgumentRegistry().getParser(inputType); + ArgumentParser inputParser = BitsCommandManager.get().getArgumentRegistry().getParser(inputType); // Use the input-specific parser's suggestions - if (hasSuggestions(inputParser)) argumentBuilder.suggests(inputParser.getSuggestionProvider()); + if (hasSuggestions(inputParser)) argumentBuilder.suggests(inputParser.createSuggestionProvider()); } else { // Use the main parser's suggestions - if (hasSuggestions(parser)) argumentBuilder.suggests(parser.getSuggestionProvider()); + if (hasSuggestions(parser)) argumentBuilder.suggests(parser.createSuggestionProvider()); } brigadierArguments.add(argumentBuilder); @@ -95,7 +95,7 @@ public CommandParameterInfo(Parameter parameter) { return brigadierArguments; } - private boolean hasSuggestions(AbstractArgumentParser parser) { + private boolean hasSuggestions(ArgumentParser parser) { return parser.getSuggestions() != null; } @@ -106,7 +106,7 @@ private boolean hasSuggestions(AbstractArgumentParser parser) { * * @since 0.0.10 */ - public AbstractArgumentParser getParser() { + public ArgumentParser getParser() { return parser; } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java index c885f865..02f8107f 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/BlockPosArgumentParser.java @@ -7,38 +7,28 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import com.mojang.brigadier.exceptions.CommandSyntaxException; - -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.paper.location.wrapper.BlockPos; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -public final class BlockPosArgumentParser extends AbstractArgumentParser { +public final class BlockPosArgumentParser extends ArgumentParser { + + public record BlockPosData( + double x, + double y, + double z + ) {} public BlockPosArgumentParser() { - super(TypeSignature.of(BlockPos.class), "BlockPos"); + super(TypeSignature.of(BlockPos.class), "BlockPos", BlockPosData.class); } - @Override - public BlockPos parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - List inputs = inputValidation(inputObjects); - double x = (double)inputs.get(0); - double y = (double)inputs.get(1); - double z = (double)inputs.get(2); - return BlockPos.of(x, y, z); - } @Override - public List getInputTypes() { - return List.of( - new InputTypeContainer(TypeSignature.of(Double.class), "x"), - new InputTypeContainer(TypeSignature.of(Double.class), "y"), - new InputTypeContainer(TypeSignature.of(Double.class), "z") - ); + public BlockPos parse(BlockPosData data, BitsCommandContext ctx) { + return BlockPos.of(data.x(), data.y(), data.z()); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java index eb438a13..0da13020 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LocationArgumentParser.java @@ -7,42 +7,31 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; -import com.mojang.brigadier.exceptions.CommandSyntaxException; import org.bukkit.Location; import org.bukkit.World; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -public final class LocationArgumentParser extends AbstractArgumentParser { +public final class LocationArgumentParser extends ArgumentParser { + + public record LocationData( + double x, + double y, + double z, + World world + ) {} public LocationArgumentParser() { - super(TypeSignature.of(Location.class), "Location"); + super(TypeSignature.of(Location.class), "Location", LocationData.class); } - @Override - public Location parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - List inputs = inputValidation(inputObjects); - double x = (double)inputs.get(0); - double y = (double)inputs.get(1); - double z = (double)inputs.get(2); - World world = (World)inputs.get(3); - - return new Location(world, x, y, z); - } @Override - public List getInputTypes() { - return List.of( - new InputTypeContainer(TypeSignature.of(Double.class), "x"), - new InputTypeContainer(TypeSignature.of(Double.class), "y"), - new InputTypeContainer(TypeSignature.of(Double.class), "z"), - new InputTypeContainer(TypeSignature.of(World.class), "world") - ); + public Location parse(LocationData data, BitsCommandContext ctx) { + return new Location(data.world(), data.x(), data.y(), data.z()); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java index 0606bd0c..373de809 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java @@ -15,8 +15,7 @@ import org.jetbrains.annotations.Nullable; import xyz.bitsquidd.bits.mc.command.PaperBitsCommandContext; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -25,9 +24,9 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; -import java.util.function.Supplier; -public final class PlayerCollectionArgumentParser extends AbstractArgumentParser> { + +public final class PlayerCollectionArgumentParser extends ArgumentParser, EntitySelector> { // TODO: Just pull from vanilla EntitySelector. Also pull the completion. private enum SelectorType { @@ -58,14 +57,12 @@ public Collection get(BitsCommandContext ctx) { } public PlayerCollectionArgumentParser() { - super(TypeSignature.of(Collection.class, Player.class), "Players"); + super(TypeSignature.of(Collection.class, Player.class), "Players", EntitySelector.class); } @Override - public Collection parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - EntitySelector entitySelctor = singletonInputValidation(inputObjects, EntitySelector.class); - - return entitySelctor.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) + public Collection parse(EntitySelector data, BitsCommandContext ctx) throws CommandSyntaxException { + return data.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) .stream() .map(playerEntity -> playerEntity.getBukkitEntity().getPlayer()) .map(Objects::requireNonNull) @@ -73,12 +70,7 @@ public Collection parse(List inputObjects, BitsCommandContext } @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(EntitySelector.class), getArgumentName())); - } - - @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { List suggestions = new ArrayList<>(); suggestions.add(SelectorType.ALL.selector); suggestions.add(SelectorType.SELF.selector); diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java index fc16ca69..98562bb2 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java @@ -13,41 +13,32 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import xyz.bitsquidd.bits.mc.command.argument.InputTypeContainer; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; import java.util.Objects; -import java.util.function.Supplier; -public final class PlayerSingleArgumentParser extends AbstractArgumentParser { + +public final class PlayerSingleArgumentParser extends ArgumentParser { public PlayerSingleArgumentParser() { - super(TypeSignature.of(Player.class), "Player"); + super(TypeSignature.of(Player.class), "Player", EntitySelector.class); } @Override - public Player parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - EntitySelector entitySelctor = singletonInputValidation(inputObjects, EntitySelector.class); - - return entitySelctor.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) + public Player parse(EntitySelector data, BitsCommandContext ctx) throws CommandSyntaxException { + return data.findPlayers((CommandSourceStack)ctx.getBrigadierContext().getSource()) .stream() .map(playerEntity -> playerEntity.getBukkitEntity().getPlayer()) .map(Objects::requireNonNull) .findFirst() - .orElseThrow(() -> ExceptionBuilder.createCommandException("Player not found: " + entitySelctor)); - } - - @Override - public List getInputTypes() { - return List.of(new InputTypeContainer(TypeSignature.of(EntitySelector.class), getArgumentName())); + .orElseThrow(() -> ExceptionBuilder.createCommandException("Player not found: " + data)); } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { return () -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java index f0877c9e..ef734053 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java @@ -8,44 +8,31 @@ package xyz.bitsquidd.bits.mc.command.argument.parser.impl; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.kyori.adventure.key.Key; import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; import org.bukkit.World; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.List; -import java.util.function.Supplier; - -public final class WorldArgumentParser extends AbstractArgumentParser { +public final class WorldArgumentParser extends ArgumentParser { public WorldArgumentParser() { - super(TypeSignature.of(World.class), "World"); + super(TypeSignature.of(World.class), "World", Key.class); } @Override - public World parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - NamespacedKey key; - try { - key = NamespacedKey.fromString(inputString); - if (key == null) throw new IllegalArgumentException(); - } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException("Invalid world key: " + inputString + "."); - } - - World world = Bukkit.getWorld(key); - if (world == null) throw ExceptionBuilder.createCommandException("World not found: " + inputString + "."); + public World parse(Key data, BitsCommandContext ctx) throws CommandSyntaxException { + World world = Bukkit.getWorld(data); + if (world == null) throw ExceptionBuilder.createCommandException("World not found: " + data + "."); return world; } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { return () -> Bukkit.getWorlds().stream().map(w -> "\"" + w.key() + "\"").toList(); } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java index 7506fd30..4bacdb50 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java @@ -10,10 +10,8 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.kyori.adventure.key.Key; import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import xyz.bitsquidd.bits.mc.command.argument.parser.AbstractArgumentParser; -import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; +import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.paper.wrapper.WorldDefinition; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -25,35 +23,25 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Supplier; import java.util.stream.Stream; +import static xyz.bitsquidd.bits.paper.util.Keys.NSK; -public final class WorldDefinitionArgumentParser extends AbstractArgumentParser { + +public final class WorldDefinitionArgumentParser extends ArgumentParser { private static final Path DIMENSIONS_PATH = Bukkit.getWorldContainer().toPath().resolve("world/dimensions"); public WorldDefinitionArgumentParser() { - super(TypeSignature.of(WorldDefinition.class), "WorldDefinition"); + super(TypeSignature.of(WorldDefinition.class), "WorldDefinition", Key.class); } @Override - public WorldDefinition parse(List inputObjects, BitsCommandContext ctx) throws CommandSyntaxException { - String inputString = singletonInputValidation(inputObjects, String.class); - - NamespacedKey key; - try { - key = NamespacedKey.fromString(inputString); - if (key == null) throw new IllegalArgumentException(); - } catch (IllegalArgumentException e) { - throw ExceptionBuilder.createCommandException("Invalid world key: " + inputString + "."); - } - - WorldDefinition worldDefinition = WorldDefinition.of(key); - return worldDefinition; + public WorldDefinition parse(Key data, BitsCommandContext ctx) throws CommandSyntaxException { + return WorldDefinition.of(NSK(data)); } @Override - public Supplier> getSuggestions() { + public SuggestionSupplier getSuggestions() { Set worldKeys = new HashSet<>(); try (Stream namespaces = Files.list(DIMENSIONS_PATH)) { namespaces.forEach(namespacePath -> { From 38057f19bc80156e1e19f74beb31fd9df31eb339 Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:04:47 +0100 Subject: [PATCH 14/15] :bug: Small command refactor fixes. --- .../mc/command/argument/parser/ArgumentParser.java | 5 +++-- .../argument/parser/SuggestionSupplier.java | 5 +++++ .../argument/parser/impl/LinkArgumentParser.java | 4 ++-- .../parser/impl/abs/EnumArgumentParser.java | 14 +++++++------- .../parser/impl/generic/GenericEnumParser.java | 2 +- .../impl/primitive/BooleanArgumentParser.java | 2 +- .../impl/PlayerCollectionArgumentParser.java | 3 ++- .../parser/impl/PlayerSingleArgumentParser.java | 3 ++- .../argument/parser/impl/WorldArgumentParser.java | 3 ++- .../parser/impl/WorldDefinitionArgumentParser.java | 6 +++--- 10 files changed, 28 insertions(+), 19 deletions(-) diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java index 3d755312..ae56bab9 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** @@ -53,7 +54,7 @@ public abstract class ArgumentParser { private final TypeSignature typeSignature; // The type signature this parser handles private final String argumentName; // The name of the argument, used while displaying suggestions private final Class dataClass; // The class of the input data type - private final Constructor recordConstructor; + private final @Nullable Constructor recordConstructor; /** * @param typeSignature the expected type this parser handles @@ -147,7 +148,7 @@ public final D toData(List inputObjects) throws CommandSyntaxException { if (!dataClass.isRecord()) return dataClass.cast(validated[0]); try { - return recordConstructor.newInstance(validated); + return Objects.requireNonNull(this.recordConstructor).newInstance(validated); } catch (ReflectiveOperationException e) { throw ExceptionBuilder.createCommandException("Failed to construct " + dataClass.getSimpleName() + ": " + e.getMessage()); } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java index bc128270..411eeb82 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/SuggestionSupplier.java @@ -17,4 +17,9 @@ public interface SuggestionSupplier { List getSuggestions(BitsCommandContext ctx); + + static SuggestionSupplier empty() { + return ctx -> List.of(); + } + } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java index 0e4bd58d..60671b53 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/LinkArgumentParser.java @@ -15,7 +15,7 @@ import xyz.bitsquidd.bits.wrapper.Link; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; -import java.util.stream.Stream; +import java.util.List; //TODO add better validation for this, enum for a type? WWW. HTTPS:// etc.? @@ -40,7 +40,7 @@ public Link parse(String data, BitsCommandContext ctx) throws CommandSyntaxEx @Override public SuggestionSupplier getSuggestions() { - return () -> Stream.of("https://", "http://", "www.").toList(); + return ctx -> List.of("https://", "http://", "www."); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java index 27014b53..560cee41 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/abs/EnumArgumentParser.java @@ -35,12 +35,12 @@ * } * } * - * @param the specific enum type + * @param the specific enum type * * @since 0.0.10 */ -public abstract class EnumArgumentParser> extends BasicArgumentParser { - private final Class enumClass; +public abstract class EnumArgumentParser> extends BasicArgumentParser { + private final Class enumClass; /** * @param enumClass the class object representing the enum to parse @@ -48,15 +48,15 @@ public abstract class EnumArgumentParser> extends BasicArgumen * @throws IllegalArgumentException if the provided class is not an enum * @since 0.0.10 */ - public EnumArgumentParser(Class enumClass) { + public EnumArgumentParser(Class enumClass) { super(TypeSignature.of(Enum.class), enumClass.getName()); this.enumClass = enumClass; if (!enumClass.isEnum()) throw new IllegalArgumentException("Provided class " + enumClass.getName() + " is not an enum!"); } @Override - public T parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { - T enumValue; + public E parse(String data, BitsCommandContext ctx) throws CommandSyntaxException { + E enumValue; try { enumValue = Enum.valueOf(enumClass, data); } catch (IllegalArgumentException e) { @@ -68,7 +68,7 @@ public T parse(String data, BitsCommandContext ctx) throws CommandSyntaxExcep @Override public @Nullable SuggestionSupplier getSuggestions() { - return () -> enumClass.isEnum() ? Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList() : List.of(); + return ctx -> enumClass.isEnum() ? Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList() : List.of(); } } \ No newline at end of file diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java index 27daae97..afddfa5e 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/generic/GenericEnumParser.java @@ -49,7 +49,7 @@ public E parse(String data, BitsCommandContext ctx) throws CommandSyntaxExcep @Override public SuggestionSupplier getSuggestions() { - return () -> Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList(); + return ctx -> Stream.of(enumClass.getEnumConstants()).map(Enum::name).toList(); } } diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java index 9b2fb11d..8ed2b8c1 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/primitive/BooleanArgumentParser.java @@ -26,7 +26,7 @@ public BooleanArgumentParser() { @Override public SuggestionSupplier getSuggestions() { - return () -> List.of("true", "false"); + return ctx -> List.of("true", "false"); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java index 373de809..eda97994 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerCollectionArgumentParser.java @@ -16,6 +16,7 @@ import xyz.bitsquidd.bits.mc.command.PaperBitsCommandContext; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -76,7 +77,7 @@ public SuggestionSupplier getSuggestions() { suggestions.add(SelectorType.SELF.selector); suggestions.addAll(Bukkit.getOnlinePlayers().stream().map(Player::getName).toList()); - return () -> suggestions; + return _ -> suggestions; } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java index 98562bb2..0bfd347e 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/PlayerSingleArgumentParser.java @@ -14,6 +14,7 @@ import org.bukkit.entity.Player; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -39,7 +40,7 @@ public Player parse(EntitySelector data, BitsCommandContext ctx) throws Comma @Override public SuggestionSupplier getSuggestions() { - return () -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); + return _ -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList(); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java index ef734053..16852fd9 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldArgumentParser.java @@ -13,6 +13,7 @@ import org.bukkit.World; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.exception.ExceptionBuilder; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -33,7 +34,7 @@ public World parse(Key data, BitsCommandContext ctx) throws CommandSyntaxExce @Override public SuggestionSupplier getSuggestions() { - return () -> Bukkit.getWorlds().stream().map(w -> "\"" + w.key() + "\"").toList(); + return _ -> Bukkit.getWorlds().stream().map(w -> "\"" + w.key() + "\"").toList(); } } diff --git a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java index 4bacdb50..81e647c6 100644 --- a/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java +++ b/minecraft/paper/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/impl/WorldDefinitionArgumentParser.java @@ -12,6 +12,7 @@ import org.bukkit.Bukkit; import xyz.bitsquidd.bits.mc.command.argument.parser.ArgumentParser; +import xyz.bitsquidd.bits.mc.command.argument.parser.SuggestionSupplier; import xyz.bitsquidd.bits.mc.command.util.BitsCommandContext; import xyz.bitsquidd.bits.paper.wrapper.WorldDefinition; import xyz.bitsquidd.bits.wrapper.type.TypeSignature; @@ -21,7 +22,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -58,10 +58,10 @@ public SuggestionSupplier getSuggestions() { }); } catch (IOException e) { // If we can't read the dimensions directory, we just return an empty list of suggestions. - return List::of; + return SuggestionSupplier.empty(); } - return () -> worldKeys.stream().map(k -> "\"" + k + "\"").toList(); + return _ -> worldKeys.stream().map(k -> "\"" + k + "\"").toList(); } } From 7587c73de75ff5ef69124f9e9138a3601a5fa5da Mon Sep 17 00:00:00 2001 From: ImBit <151650938+ImBit@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:18:27 +0100 Subject: [PATCH 15/15] :bug: Fix argument parsing suggestions incorrectly being equalsIgnoreCase. --- .../bits/mc/command/argument/parser/ArgumentParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java index ae56bab9..7625ad05 100644 --- a/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java +++ b/minecraft/module/command/src/main/java/xyz/bitsquidd/bits/mc/command/argument/parser/ArgumentParser.java @@ -176,7 +176,7 @@ public final SuggestionProvider createSuggestionProvider() { String remaining = builder.getRemaining(); for (String suggestion : suggestions) { - if (suggestion.equalsIgnoreCase(remaining)) { + if (suggestion.toLowerCase().startsWith(remaining.toLowerCase())) { builder.suggest(suggestion); } }