1010import com .mojang .brigadier .suggestion .Suggestions ;
1111import com .mojang .brigadier .suggestion .SuggestionsBuilder ;
1212import net .earthcomputer .clientcommands .util .MultiVersionCompat ;
13+ import net .minecraft .advancements .critereon .MinMaxBounds ;
1314import net .minecraft .commands .SharedSuggestionProvider ;
1415import net .minecraft .commands .arguments .ResourceArgument ;
1516import net .minecraft .core .component .DataComponents ;
2324import net .minecraft .world .item .enchantment .Enchantment ;
2425import net .minecraft .world .item .enchantment .EnchantmentInstance ;
2526import net .minecraft .world .item .enchantment .ItemEnchantments ;
27+ import org .jetbrains .annotations .Nullable ;
2628
2729import java .util .ArrayList ;
2830import java .util .Arrays ;
2931import java .util .Collection ;
32+ import java .util .Collections ;
3033import java .util .List ;
3134import java .util .concurrent .CompletableFuture ;
3235import java .util .function .BiPredicate ;
@@ -37,7 +40,6 @@ public class ItemAndEnchantmentsPredicateArgument implements ArgumentType<ItemAn
3740
3841 private static final Collection <String > EXAMPLES = Arrays .asList ("stick with sharpness 4 without sweeping *" , "minecraft:diamond_sword with sharpness *" );
3942
40- private static final SimpleCommandExceptionType EXPECTED_WITH_WITHOUT_EXACTLY_EXCEPTION = new SimpleCommandExceptionType (Component .translatable ("commands.cenchant.expectedWithWithoutExactly" ));
4143 private static final SimpleCommandExceptionType INCOMPATIBLE_ENCHANTMENT_EXCEPTION = new SimpleCommandExceptionType (Component .translatable ("commands.cenchant.incompatible" ));
4244 private static final DynamicCommandExceptionType ID_INVALID_EXCEPTION = new DynamicCommandExceptionType (id -> Component .translatable ("argument.item.id.invalid" , id ));
4345
@@ -80,24 +82,38 @@ public ItemAndEnchantmentsPredicate parse(StringReader reader) throws CommandSyn
8082 if (parser .exact && (parser .with .size () != enchantments .size ())) {
8183 return false ;
8284 }
83- for (EnchantmentInstance with : parser .with ) {
84- boolean found = false ;
85- for (EnchantmentInstance ench : enchantments ) {
86- if (with .enchantment == ench .enchantment && (with .level == -1 || with .level == ench .level )) {
87- found = true ;
88- break ;
85+ if (parser .ordered ) {
86+ int enchIndex = 0 ;
87+ for (EnchantmentInstancePredicate with : parser .with ) {
88+ while (enchIndex < enchantments .size () && !with .test (enchantments .get (enchIndex ))) {
89+ enchIndex ++;
8990 }
91+ if (enchIndex >= enchantments .size ()) {
92+ return false ;
93+ }
94+ // we're matching, increment index
95+ enchIndex ++;
9096 }
91- if (!found ) {
92- return false ;
97+ } else {
98+ for (EnchantmentInstancePredicate with : parser .with ) {
99+ boolean found = false ;
100+ for (EnchantmentInstance ench : enchantments ) {
101+ if (with .test (ench )) {
102+ found = true ;
103+ break ;
104+ }
105+ }
106+ if (!found ) {
107+ return false ;
108+ }
93109 }
94110 }
95111 if (parser .exact ) {
96112 return true ;
97113 }
98- for (EnchantmentInstance without : parser .without ) {
114+ for (EnchantmentInstancePredicate without : parser .without ) {
99115 for (EnchantmentInstance ench : enchantments ) {
100- if (without .enchantment == ench . enchantment && ( without . level == - 1 || without . level == ench . level )) {
116+ if (without .test ( ench )) {
101117 return false ;
102118 }
103119 }
@@ -149,10 +165,11 @@ private class Parser {
149165 private Consumer <SuggestionsBuilder > suggestor ;
150166
151167 private Item item ;
152- private final List <EnchantmentInstance > with = new ArrayList <>();
153- private final List <EnchantmentInstance > without = new ArrayList <>();
168+ private final List <EnchantmentInstancePredicate > with = new ArrayList <>();
169+ private final List <EnchantmentInstancePredicate > without = new ArrayList <>();
154170
155171 private boolean exact = false ;
172+ private boolean ordered = false ;
156173
157174 public Parser (StringReader reader ) {
158175 this .reader = reader ;
@@ -163,9 +180,8 @@ public void parse() throws CommandSyntaxException {
163180
164181 while (reader .canRead ()) {
165182 parseSpace ();
166- parseInfoEnchantment ();
167- if (exact ) {
168- return ;
183+ if (!parseEnchantmentInstancePredicate ()) {
184+ break ;
169185 }
170186 }
171187 }
@@ -188,46 +204,64 @@ private Item parseItem() throws CommandSyntaxException {
188204 return item ;
189205 }
190206
191- private void parseInfoEnchantment () throws CommandSyntaxException {
207+ private boolean parseEnchantmentInstancePredicate () throws CommandSyntaxException {
192208 ItemStack stack = new ItemStack (item );
193209
194- Option option = parseWithWithoutExactly ();
210+ int start = reader .getCursor ();
211+ Option option = parseOption ();
212+ if (option == null ) {
213+ reader .setCursor (start );
214+ return false ;
215+ }
216+
195217 boolean suggest = reader .canRead ();
196- parseSpace ();
197218 if (option == Option .EXACT ) {
198219 exact = true ;
199- return ;
220+ return true ;
221+ }
222+ if (option == Option .ORDERED ) {
223+ ordered = true ;
224+ return true ;
200225 }
226+
227+ if (exact || ordered ) {
228+ reader .setCursor (start );
229+ return false ;
230+ }
231+
232+ parseSpace ();
233+
201234 Enchantment enchantment = parseEnchantment (suggest , option , stack );
202235 suggest = reader .canRead ();
203236 parseSpace ();
204- int level = parseEnchantmentLevel (suggest , option , stack , enchantment );
237+ MinMaxBounds . Ints level = parseEnchantmentLevel (suggest , option , stack , enchantment );
205238
206239 if (option == Option .WITH ) {
207- with .add (new EnchantmentInstance (enchantment , level ));
240+ with .add (new EnchantmentInstancePredicate (enchantment , level ));
208241 } else {
209- without .add (new EnchantmentInstance (enchantment , level ));
242+ without .add (new EnchantmentInstancePredicate (enchantment , level ));
210243 }
244+
245+ return true ;
211246 }
212247
213248 private enum Option {
214249 WITH ,
215250 WITHOUT ,
216- EXACT
251+ EXACT ,
252+ ORDERED ,
217253 }
218254
219- private Option parseWithWithoutExactly () throws CommandSyntaxException {
220- suggestWithWithoutExactly ();
221- int start = reader . getCursor ();
255+ @ Nullable
256+ private Option parseOption () {
257+ suggestOption ();
222258 String option = reader .readUnquotedString ();
223259 return switch (option ) {
224260 case "with" -> Option .WITH ;
225261 case "without" -> Option .WITHOUT ;
226- case "exactly" -> Option .EXACT ;
227- default -> {
228- reader .setCursor (start );
229- throw EXPECTED_WITH_WITHOUT_EXACTLY_EXCEPTION .createWithContext (reader );
230- }
262+ case "exactly" -> exact ? null : Option .EXACT ;
263+ case "ordered" -> ordered ? null : Option .ORDERED ;
264+ default -> null ;
231265 };
232266 }
233267
@@ -239,24 +273,24 @@ private Enchantment parseEnchantment(boolean suggest, Option option, ItemStack s
239273 continue ;
240274 }
241275 if (option == Option .WITH ) {
242- for (EnchantmentInstance ench2 : with ) {
276+ for (EnchantmentInstancePredicate ench2 : with ) {
243277 if (ench2 .enchantment == ench || !ench2 .enchantment .isCompatibleWith (ench )) {
244278 continue nextEnchantment ;
245279 }
246280 }
247- for (EnchantmentInstance ench2 : without ) {
248- if (ench2 .enchantment == ench && ench2 .level == - 1 ) {
281+ for (EnchantmentInstancePredicate ench2 : without ) {
282+ if (ench2 .enchantment == ench && ench2 .level . isAny () ) {
249283 continue nextEnchantment ;
250284 }
251285 }
252286 } else {
253- for (EnchantmentInstance ench2 : with ) {
254- if (ench2 .enchantment == ench && ench2 .level == - 1 ) {
287+ for (EnchantmentInstancePredicate ench2 : with ) {
288+ if (ench2 .enchantment == ench && ench2 .level . isAny () ) {
255289 continue nextEnchantment ;
256290 }
257291 }
258- for (EnchantmentInstance ench2 : without ) {
259- if (ench2 .enchantment == ench && ench2 .level == - 1 ) {
292+ for (EnchantmentInstancePredicate ench2 : without ) {
293+ if (ench2 .enchantment == ench && ench2 .level . isAny () ) {
260294 continue nextEnchantment ;
261295 }
262296 }
@@ -287,7 +321,7 @@ private Enchantment parseEnchantment(boolean suggest, Option option, ItemStack s
287321 return enchantment ;
288322 }
289323
290- private int parseEnchantmentLevel (boolean suggest , Option option , ItemStack stack , Enchantment enchantment ) throws CommandSyntaxException {
324+ private MinMaxBounds . Ints parseEnchantmentLevel (boolean suggest , Option option , ItemStack stack , Enchantment enchantment ) throws CommandSyntaxException {
291325 int maxLevel ;
292326 if (constrainMaxLevel ) {
293327 int enchantability = stack .getItem ().getEnchantmentValue ();
@@ -309,19 +343,19 @@ private int parseEnchantmentLevel(boolean suggest, Option option, ItemStack stac
309343 }
310344
311345 if (option == Option .WITH ) {
312- for (EnchantmentInstance ench : without ) {
313- if (ench .enchantment == enchantment && (level == -1 || level == ench .level )) {
346+ for (EnchantmentInstancePredicate ench : without ) {
347+ if (ench .enchantment == enchantment && (level == -1 || ench .level . matches ( level ) )) {
314348 continue nextLevel ;
315349 }
316350 }
317351 } else {
318- for (EnchantmentInstance ench : with ) {
319- if (ench .enchantment == enchantment && (level == -1 || level == ench .level )) {
352+ for (EnchantmentInstancePredicate ench : with ) {
353+ if (ench .enchantment == enchantment && (level == -1 || ench .level . matches ( level ) )) {
320354 continue nextLevel ;
321355 }
322356 }
323- for (EnchantmentInstance ench : without ) {
324- if (ench .enchantment == enchantment && (level == -1 || level == ench .level )) {
357+ for (EnchantmentInstancePredicate ench : without ) {
358+ if (ench .enchantment == enchantment && (level == -1 || ench .level . matches ( level ) )) {
325359 continue nextLevel ;
326360 }
327361 }
@@ -352,15 +386,17 @@ private int parseEnchantmentLevel(boolean suggest, Option option, ItemStack stac
352386
353387 if (reader .peek () == '*' ) {
354388 reader .skip ();
355- return - 1 ;
389+ return MinMaxBounds . Ints . ANY ;
356390 }
357391
358- int level = reader .readInt ();
359- if (level == -1 || !allowedLevels .contains (level )) {
360- reader .setCursor (start );
361- throw CommandSyntaxException .BUILT_IN_EXCEPTIONS .readerInvalidInt ().createWithContext (reader , level );
392+ int levelStart = reader .getCursor ();
393+ MinMaxBounds .Ints result = MinMaxBounds .Ints .fromReader (reader );
394+ if (allowedLevels .stream ().noneMatch (result ::matches )) {
395+ int levelEnd = reader .getCursor ();
396+ reader .setCursor (levelStart );
397+ throw CommandSyntaxException .BUILT_IN_EXCEPTIONS .readerInvalidInt ().createWithContext (reader , reader .getString ().substring (levelStart , levelEnd ));
362398 }
363- return level ;
399+ return result ;
364400 }
365401
366402 private void parseSpace () throws CommandSyntaxException {
@@ -393,13 +429,30 @@ private void suggestEnchantableItem() {
393429 };
394430 }
395431
396- private void suggestWithWithoutExactly () {
432+ private void suggestOption () {
397433 int start = reader .getCursor ();
398434 suggestor = suggestions -> {
399435 SuggestionsBuilder builder = suggestions .createOffset (start );
400- SharedSuggestionProvider .suggest (new String []{"with" , "without" , "exactly" }, builder );
436+ List <String > validOptions = new ArrayList <>(4 );
437+ if (!exact && !ordered ) {
438+ Collections .addAll (validOptions , "with" , "without" );
439+ }
440+ if (!exact ) {
441+ validOptions .add ("exactly" );
442+ }
443+ if (!ordered ) {
444+ validOptions .add ("ordered" );
445+ }
446+ SharedSuggestionProvider .suggest (validOptions , builder );
401447 suggestions .add (builder );
402448 };
403449 }
404450 }
451+
452+ private record EnchantmentInstancePredicate (Enchantment enchantment , MinMaxBounds .Ints level ) implements Predicate <EnchantmentInstance > {
453+ @ Override
454+ public boolean test (EnchantmentInstance enchInstance ) {
455+ return enchantment == enchInstance .enchantment && level .matches (enchInstance .level );
456+ }
457+ }
405458}
0 commit comments