diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 3e6ddbdf..9e9fc5bd 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -214,6 +214,11 @@ Here is a list of built-in types, with their return value formats, as well as a * `'players'`: returning a list of valid player name string, logged in or not. If configured with `'single'` returns only one player or `null`. * `'intrange'`: same as `'floatrange'`, but requiring integers. * `'enchantment'`: name of an enchantment + * `'slot'`: provides a list of inventory type and slot. Can be configured with `'restrict'` requiring + `'player'`, `'enderchest'`, `'equipment'`, `'armor'`, `'weapon'`, `'container'`, `'villager'` or `'horse'` restricting selection of + available slots. Scarpet supports all vanilla slots, except for `horse.chest` - chest item, not items themselves. This you would + need to manage yourself via nbt directly. Also, for entities that change their capacity, like llamas, you need to check yourself if + the specified container slot is valid for your entity. * `'item'`: triple of item type, count of 1, and nbt. * `'message'`: text with expanded embedded player names * `'effect'`: string representing a status effect diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java index 82bd5c69..c0e5ebef 100644 --- a/src/main/java/carpet/script/command/CommandArgument.java +++ b/src/main/java/carpet/script/command/CommandArgument.java @@ -47,6 +47,7 @@ import net.minecraft.command.argument.EntitySummonArgumentType; import net.minecraft.command.argument.GameProfileArgumentType; import net.minecraft.command.argument.IdentifierArgumentType; import net.minecraft.command.argument.ItemEnchantmentArgumentType; +import net.minecraft.command.argument.ItemSlotArgumentType; import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.command.argument.MessageArgumentType; import net.minecraft.command.argument.MobEffectArgumentType; @@ -78,11 +79,14 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec2f; import net.minecraft.util.registry.Registry; +import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -158,6 +162,7 @@ public abstract class CommandArgument ), // item_predicate ?? //same as item but accepts tags, not sure right now // slot // item_slot + new SlotArgument(), new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack, (c, p) -> ValueConversions.of(ItemStackArgumentType.getItemStackArgument(c, p).createStack(1, false)), false ), @@ -849,6 +854,100 @@ public abstract class CommandArgument return IntArgument::new; } } + private static class SlotArgument extends CommandArgument + { + private String restrict; + private static final Map, Set>> RESTRICTED_CONTAINERS = new HashMap, Set>>(){{ + int i; + for (String source : Arrays.asList("player", "enderchest", "equipment", "armor", "weapon", "container", "villager", "horse")) + put(source, Pair.of(new HashSet<>(), new HashSet<>())); + for (i = 0; i < 41; i++) get("player").getLeft().add(i); + for(i = 0; i < 41; i++) get("player").getRight().add("container." + i); + for(i = 0; i < 9; i++) get("player").getRight().add("hotbar." + i); + for(i = 0; i < 27; i++) get("player").getRight().add("inventory." + i); + for (String place : Arrays.asList("weapon", "weapon.mainhand", "weapon.offhand")) + { + get("player").getRight().add(place); + get("equipment").getRight().add(place); + get("weapon").getRight().add(place); + } + for (String place : Arrays.asList("armor.feet","armor.legs", "armor.chest","armor.head")) + { + get("player").getRight().add(place); + get("equipment").getRight().add(place); + get("armor").getRight().add(place); + } + + for (i = 0; i < 27; i++) get("enderchest").getLeft().add(200+i); + for(i = 0; i < 27; i++) get("enderchest").getRight().add("enderchest." + i); + + for (i = 0; i < 6; i++) get("equipment").getLeft().add(98+i); + + for (i = 0; i < 4; i++) get("armor").getLeft().add(100+i); + + for (i = 0; i < 2; i++) get("weapon").getLeft().add(98+i); + + for (i = 0; i < 54; i++) get("container").getLeft().add(i); + for(i = 0; i < 41; i++) get("container").getRight().add("container." + i); + + for (i = 0; i < 8; i++) get("villager").getLeft().add(i); + for(i = 0; i < 8; i++) get("villager").getRight().add("villager." + i); + + for (i = 0; i < 15; i++) get("horse").getLeft().add(500+i); + for(i = 0; i < 15; i++) get("horse").getRight().add("horse." + i); + get("horse").getLeft().add(400); + get("horse").getRight().add("horse.saddle"); + get("horse").getLeft().add(401); + get("horse").getRight().add("horse.armor"); + }}; + + protected SlotArgument() + { + super("slot", ItemSlotArgumentType.itemSlot().getExamples(), false); + } + + @Override + protected ArgumentType getArgumentType() throws CommandSyntaxException + { + return ItemSlotArgumentType.itemSlot(); + } + + @Override + protected Value getValueFromContext(CommandContext context, String param) throws CommandSyntaxException + { + int slot = ItemSlotArgumentType.getItemSlot(context, param); + if (restrict != null && !RESTRICTED_CONTAINERS.get(restrict).getLeft().contains(slot)) + { + throw new SimpleCommandExceptionType(new LiteralText("Incorrect slot restricted to "+restrict)).create(); + } + return ValueConversions.ofVanillaSlotResult(slot); + } + + @Override + protected void configure(Map config, CarpetScriptHost host) + { + super.configure(config, host); + if (config.containsKey("restrict")) + { + restrict = config.get("restrict").getString().toLowerCase(Locale.ROOT); + needsMatching = true; + if (!RESTRICTED_CONTAINERS.containsKey(restrict)) + throw new InternalExpressionException("Incorrect slot restriction: "+restrict); + } + } + + @Override + protected Collection getOptions(CommandContext context, CarpetScriptHost host) throws CommandSyntaxException + { + return restrict==null?super.getOptions(context, host):RESTRICTED_CONTAINERS.get(restrict).getRight(); + } + + @Override + protected Supplier factory() + { + return SlotArgument::new; + } + } @FunctionalInterface private interface ValueExtractor diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index cb8d7535..8ffd3c02 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -330,4 +330,49 @@ public class ValueConversions } throw new InternalExpressionException("Unknown property type: "+p.getName()); } + + + private static final Map slotIdsToSlotParams = new HashMap() {{ + int n; + //covers blocks, player hotbar and inventory, and all default inventories + for(n = 0; n < 54; ++n) { + put(n, ListValue.of(Value.NULL, NumericValue.of(n))); + } + for(n = 0; n < 27; ++n) { + put(200+n, ListValue.of(StringValue.of("enderchest"), NumericValue.of(n))); + } + + // villager + for(n = 0; n < 8; ++n) { + put(300+n, ListValue.of(Value.NULL, NumericValue.of(n))); + } + + // horse, llamas, donkeys, etc. + // two first slots are for saddle and armour + for(n = 0; n < 15; ++n) { + put(500+n, ListValue.of(Value.NULL, NumericValue.of(n+2))); + } + Value equipment = StringValue.of("equipment"); + // weapon main hand + put(98, ListValue.of(equipment, NumericValue.of(0))); + // offhand + put(99, ListValue.of(equipment, NumericValue.of(5))); + // feet, legs, chest, head + for(n = 0; n < 4; ++n) { + put(100+n, ListValue.of(equipment, NumericValue.of(n+1))); + } + //horse defaults saddle + put(400, ListValue.of(Value.NULL, NumericValue.of(0))); + // armor + put(401, ListValue.of(Value.NULL, NumericValue.of(1))); + // chest itself on the donkey is wierd - use NBT to alter that. + //hashMap.put("horse.chest", 499); + }}; + + public static Value ofVanillaSlotResult(int itemSlot) + { + Value ret = slotIdsToSlotParams.get(itemSlot); + if (ret == null) return ListValue.of(Value.NULL, NumericValue.of(itemSlot)); + return ret; + } }