diff --git a/build.gradle b/build.gradle
index 2dc45583..c9cfacd7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,6 +11,7 @@ version = project.minecraft_version+'-'+project.mod_version
group = project.maven_group
minecraft {
+ accessWidener "src/main/resources/carpet.accesswidener"
}
dependencies {
diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index cbe8ee52..47c5f610 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -2940,6 +2940,50 @@ item_category('ender_pearl') => misc
item_category('stone') => building_blocks
+### `recipe_data(item, type?)`, `recipe_data(recipe, type?)`
+
+returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items
+that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its
+direct name can be different.
+
+Recipe type can take one of the following options:
+ * `'crafting'` - default, crafting table recipe
+ * `'smelting'` - furnace recipe
+ * `'blasting'` - blast furnace recipe
+ * `'smoking'` - smoker recipe
+ * `'campfire_cooking'` - campfire recipe
+ * `'stonecutting'` - stonecutter recipe
+
+ The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of
+ an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the
+ ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left
+ empty, and recipe specification as another list. Possible recipe specs is:
+ * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3.
+ * `['shapeless']` - shapeless crafting
+ * `['smelting', duration, xp]` - smelting/cooking recipes
+ * `['cutting']` - stonecutter recipe
+ * `['special']` - special crafting recipe, typically not present in the crafting menu
+ * `['custom']` - other recipe types
+
+Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one
+of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it
+can accept items of any tags.
+
+Also note that some recipes leave some products in the crafting window, and these can be determined using
+ `crafting_remaining_item()` function
+
+ Examples:
+
+ recipe_data('iron_ingot_from_nuggets')
+ recipe_data('iron_ingot')
+ recipe_data('glass', 'smelting')
+
+
+### `crafting_remaining_item(item)`
+
+returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an
+item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles.
+
### `inventory_size(inventory)`
Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't
@@ -3055,6 +3099,8 @@ __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) /
__on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block
__on_player_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand)
+__on_player_chooses_recipe(player, recipe, full_stack)
+__on_player_switches_slot(player, from, to)
__on_player_attacks_entity(player, entity)
__on_player_takes_damage(player, amount, source, source_entity)
__on_player_deals_damage(player, amount, entity)
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index 6a616f30..05b8c4cd 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -32,6 +32,8 @@ __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) /
__on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block
__on_player_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand)
+__on_player_chooses_recipe(player, recipe, full_stack)
+__on_player_switches_slot(player, from, to)
__on_player_attacks_entity(player, entity)
__on_player_takes_damage(player, amount, source, source_entity)
__on_player_deals_damage(player, amount, entity)
diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md
index 5ef96c7a..fcc1fd53 100644
--- a/docs/scarpet/api/Inventories.md
+++ b/docs/scarpet/api/Inventories.md
@@ -38,6 +38,50 @@ item_category('ender_pearl') => misc
item_category('stone') => building_blocks
+### `recipe_data(item, type?)`, `recipe_data(recipe, type?)`
+
+returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items
+that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its
+direct name can be different.
+
+Recipe type can take one of the following options:
+ * `'crafting'` - default, crafting table recipe
+ * `'smelting'` - furnace recipe
+ * `'blasting'` - blast furnace recipe
+ * `'smoking'` - smoker recipe
+ * `'campfire_cooking'` - campfire recipe
+ * `'stonecutting'` - stonecutter recipe
+
+ The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of
+ an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the
+ ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left
+ empty, and recipe specification as another list. Possible recipe specs is:
+ * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3.
+ * `['shapeless']` - shapeless crafting
+ * `['smelting', duration, xp]` - smelting/cooking recipes
+ * `['cutting']` - stonecutter recipe
+ * `['special']` - special crafting recipe, typically not present in the crafting menu
+ * `['custom']` - other recipe types
+
+Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one
+of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it
+can accept items of any tags.
+
+Also note that some recipes leave some products in the crafting window, and these can be determined using
+ `crafting_remaining_item()` function
+
+ Examples:
+
+ recipe_data('iron_ingot_from_nuggets')
+ recipe_data('iron_ingot')
+ recipe_data('glass', 'smelting')
+
+
+### `crafting_remaining_item(item)`
+
+returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an
+item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles.
+
### `inventory_size(inventory)`
Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 7e2a8d8a..2d2496c5 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -12,6 +12,7 @@ blocks_daylight
blocks_movement
brightness
chunk_tickets
+crafting_remaining_item
create_marker
current_dimension
day_time
@@ -67,6 +68,7 @@ power
property
query
random_tick
+recipe_data
rect
reload_chunk
remove_all_markers
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index faf87463..1f659f99 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -6,6 +6,7 @@ __on_player_attacks_entity
__on_player_breaks_block
__on_player_clicks_block
__on_player_changes_dimension
+__on_player_chooses_recipe
__on_player_deploys_elytra
__on_player_drops_item
__on_player_drops_stack
@@ -21,6 +22,7 @@ __on_player_starts_sneaking
__on_player_starts_sprinting
__on_player_stops_sneaking
__on_player_stops_sprinting
+__on_player_switches_slot
__on_player_uses_item
__on_player_wakes_up
__on_statistic
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 33416a05..ffb50e58 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -62,7 +62,9 @@
player spawn entity_id entity_list entity_area entity_selector query modify entity_event
- stack_limit item_category inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
+ stack_limit item_category recipe_data crafting_remaining_item
+
+ inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
scan volume neighbours rect diamond
@@ -87,6 +89,7 @@
__on_player_breaks_block
__on_player_clicks_block
__on_player_changes_dimension
+ __on_player_chooses_recipe
__on_player_deploys_elytra
__on_player_drops_item
__on_player_drops_stack
@@ -102,6 +105,7 @@
__on_player_starts_sprinting
__on_player_stops_sneaking
__on_player_stops_sprinting
+ __on_player_switches_slot
__on_player_uses_item
__on_player_wakes_up
__on_statistic
diff --git a/src/main/java/carpet/fakes/IngredientInterface.java b/src/main/java/carpet/fakes/IngredientInterface.java
new file mode 100644
index 00000000..d910883e
--- /dev/null
+++ b/src/main/java/carpet/fakes/IngredientInterface.java
@@ -0,0 +1,11 @@
+package carpet.fakes;
+
+import net.minecraft.item.ItemStack;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface IngredientInterface
+{
+ List> getRecipeStacks();
+}
diff --git a/src/main/java/carpet/fakes/RecipeManagerInterface.java b/src/main/java/carpet/fakes/RecipeManagerInterface.java
new file mode 100644
index 00000000..77f45d21
--- /dev/null
+++ b/src/main/java/carpet/fakes/RecipeManagerInterface.java
@@ -0,0 +1,12 @@
+package carpet.fakes;
+
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+
+public interface RecipeManagerInterface
+{
+ List> getAllMatching(RecipeType type, Identifier output);
+}
diff --git a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
index 16a41708..94bd965b 100644
--- a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
+++ b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
@@ -14,6 +14,11 @@ public class ClientPlayerEntity_clientCommandMixin
@Inject(method = "sendChatMessage", at = @At("HEAD"))
private void inspectMessage(String string, CallbackInfo ci)
{
+ if (string.startsWith("/call"))
+ {
+ String command = string.substring(6);
+ CarpetClient.sendClientCommand(command);
+ }
if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet())
{
ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this;
diff --git a/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java b/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java
new file mode 100644
index 00000000..f4b68577
--- /dev/null
+++ b/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java
@@ -0,0 +1,26 @@
+package carpet.mixins;
+
+import carpet.fakes.IngredientInterface;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Ingredient;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Mixin(Ingredient.class)
+public class Ingredient_scarpetMixin implements IngredientInterface
+{
+ @Shadow @Final private Ingredient.Entry[] entries;
+
+ @Override
+ public List> getRecipeStacks()
+ {
+ List> res = Arrays.stream(entries).map(Ingredient.Entry::getStacks).collect(Collectors.toList());
+ return res;
+ }
+}
diff --git a/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java b/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java
new file mode 100644
index 00000000..c31d4648
--- /dev/null
+++ b/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java
@@ -0,0 +1,32 @@
+package carpet.mixins;
+
+import carpet.fakes.RecipeManagerInterface;
+import com.google.common.collect.Lists;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeManager;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Mixin(RecipeManager.class)
+public class RecipeManager_scarpetMixin implements RecipeManagerInterface
+{
+
+ @Shadow private Map, Map>> recipes;
+
+ @Override
+ public List> getAllMatching(RecipeType type, Identifier output)
+ {
+ Map> typeRecipes = recipes.get(type);
+ if (typeRecipes.containsKey(output)) return Collections.singletonList(typeRecipes.get(output));
+ return Lists.newArrayList(typeRecipes.values().stream().filter(
+ r -> Registry.ITEM.getId(r.getOutput().getItem()).equals(output)).collect(Collectors.toList()));
+ }
+}
diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
index 76b8aa71..63534015 100644
--- a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
+++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
@@ -1,6 +1,9 @@
package carpet.mixins;
import net.minecraft.item.ItemStack;
+import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket;
+import net.minecraft.network.packet.c2s.play.CraftRequestC2SPacket;
+import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
@@ -29,10 +32,12 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_RELEASED_ITEM;
import static carpet.script.CarpetEventServer.Event.PLAYER_RIDES;
import static carpet.script.CarpetEventServer.Event.PLAYER_JUMPS;
import static carpet.script.CarpetEventServer.Event.PLAYER_RIGHT_CLICKS_BLOCK;
+import static carpet.script.CarpetEventServer.Event.PLAYER_CHOOSES_RECIPE;
import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SNEAKING;
import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SPRINTING;
import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SNEAKING;
import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SPRINTING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_SWITCHES_SLOT;
import static carpet.script.CarpetEventServer.Event.PLAYER_USES_ITEM;
import static carpet.script.CarpetEventServer.Event.PLAYER_WAKES_UP;
@@ -219,4 +224,28 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
//todo add hit and hand in the future
PLAYER_ATTACKS_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), null);
}
+
+ @Inject(method = "onButtonClick", at = @At("HEAD"))
+ private void onItemBeingPickedFromInventory(ButtonClickC2SPacket packet, CallbackInfo ci)
+ {
+ // crafts not int the crafting window
+ //CarpetSettings.LOG.error("Player clicks button "+packet.getButtonId());
+ }
+ @Inject(method = "onCraftRequest", at = @At("HEAD"))
+ private void onRecipeSelectedInRecipeManager(CraftRequestC2SPacket packet, CallbackInfo ci)
+ {
+ if (PLAYER_CHOOSES_RECIPE.isNeeded())
+ {
+ PLAYER_CHOOSES_RECIPE.onRecipeSelected(player, packet.getRecipe(), packet.shouldCraftAll());
+ }
+ }
+
+ @Inject(method = "onUpdateSelectedSlot", at = @At("HEAD"))
+ private void onUpdatedSelectedSLot(UpdateSelectedSlotC2SPacket packet, CallbackInfo ci)
+ {
+ if (PLAYER_SWITCHES_SLOT.isNeeded())
+ {
+ PLAYER_SWITCHES_SLOT.onSlotSwitch(player, player.inventory.selectedSlot, packet.getSelectedSlot());
+ }
+ }
}
diff --git a/src/main/java/carpet/network/CarpetClient.java b/src/main/java/carpet/network/CarpetClient.java
index 7010eb31..47f74fd4 100644
--- a/src/main/java/carpet/network/CarpetClient.java
+++ b/src/main/java/carpet/network/CarpetClient.java
@@ -1,8 +1,13 @@
package carpet.network;
import carpet.CarpetServer;
+import carpet.CarpetSettings;
import carpet.script.utils.ShapesRenderer;
import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.Tag;
+import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public class CarpetClient
@@ -51,4 +56,26 @@ public class CarpetClient
{
return isServerCarpet;
}
+
+ public static boolean sendClientCommand(String command)
+ {
+ if (!isServerCarpet && CarpetServer.minecraft_server == null) return false;
+ ClientNetworkHandler.clientCommand(command);
+ return true;
+ }
+
+ public static void onClientCommand(Tag t)
+ {
+ CarpetSettings.LOG.info("Server Response:");
+ CompoundTag tag = (CompoundTag)t;
+ CarpetSettings.LOG.info(" - id: "+tag.getString("id"));
+ CarpetSettings.LOG.info(" - code: "+tag.getInt("code"));
+ if (tag.contains("error")) CarpetSettings.LOG.warn(" - error: "+tag.getString("error"));
+ if (tag.contains("output"))
+ {
+ ListTag outputTag = (ListTag) tag.get("output");
+ for (int i = 0; i < outputTag.size(); i++)
+ CarpetSettings.LOG.info(" - response: " + Text.Serializer.fromJson(outputTag.getString(i)).getString());
+ }
+ }
}
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index 8807217d..8d77dc5b 100644
--- a/src/main/java/carpet/network/ClientNetworkHandler.java
+++ b/src/main/java/carpet/network/ClientNetworkHandler.java
@@ -44,6 +44,9 @@ public class ClientNetworkHandler
if (CarpetClient.shapes != null)
CarpetClient.shapes.addShapes((ListTag) t);
});
+ put("clientCommand", (p, t) -> {
+ CarpetClient.onClientCommand(t);
+ });
}};
@@ -99,4 +102,17 @@ public class ClientNetworkHandler
CarpetSettings.LOG.error("Unknown carpet data: "+key);
}
}
+
+ public static void clientCommand(String command)
+ {
+ CompoundTag tag = new CompoundTag();
+ tag.putString("id", command);
+ tag.putString("command", command);
+ CompoundTag outer = new CompoundTag();
+ outer.put("clientCommand", tag);
+ CarpetClient.getPlayer().networkHandler.sendPacket(new CustomPayloadC2SPacket(
+ CarpetClient.CARPET_CHANNEL,
+ (new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.DATA).writeCompoundTag(outer)
+ ));
+ }
}
diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java
index 71e8e35c..79d0513a 100644
--- a/src/main/java/carpet/network/ServerNetworkHandler.java
+++ b/src/main/java/carpet/network/ServerNetworkHandler.java
@@ -6,22 +6,37 @@ import carpet.helpers.TickSpeed;
import carpet.settings.ParsedRule;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
+import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.text.Text;
+import net.minecraft.util.PacketByteBuf;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
public class ServerNetworkHandler
{
private static Map remoteCarpetPlayers = new HashMap<>();
private static Set validCarpetPlayers = new HashSet<>();
+ private static Map> dataHandlers = new HashMap>(){{
+ put("clientCommand", (p, t) -> {
+ handleClientCommand(p, (CompoundTag)t);
+ });
+ }};
+
public static void handleData(PacketByteBuf data, ServerPlayerEntity player)
{
if (data != null)
@@ -29,8 +44,13 @@ public class ServerNetworkHandler
int id = data.readVarInt();
if (id == CarpetClient.HELLO)
onHello(player, data);
+ if (id == CarpetClient.DATA)
+ onClientData(player, data);
}
}
+
+
+
public static void onPlayerJoin(ServerPlayerEntity playerEntity)
{
if (!playerEntity.networkHandler.connection.isLocal())
@@ -61,7 +81,68 @@ public class ServerNetworkHandler
CarpetServer.settingsManager.getRules().forEach(data::withRule);
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() ));
}
-
+
+ private static void handleClientCommand(ServerPlayerEntity player, CompoundTag commandData)
+ {
+ String command = commandData.getString("command");
+ String id = commandData.getString("id");
+ List output = new ArrayList<>();
+ String[] error = {null};
+ int resultCode = -1;
+ if (player.getServer() == null)
+ {
+ error[0] = "No Server";
+ }
+ else
+ {
+ resultCode = player.getServer().getCommandManager().execute(
+ new ServerCommandSource(player, player.getPos(), player.getRotationClient(),
+ player.world instanceof ServerWorld ? (ServerWorld) player.world : null,
+ player.server.getPermissionLevel(player.getGameProfile()), player.getName().getString(), player.getDisplayName(),
+ player.world.getServer(), player)
+ {
+ @Override
+ public void sendError(Text message)
+ {
+ error[0] = message.getString();
+ }
+ @Override
+ public void sendFeedback(Text message, boolean broadcastToOps)
+ {
+ output.add(message);
+ }
+ },
+ command
+ );
+ };
+ CompoundTag result = new CompoundTag();
+ result.putString("id", id);
+ result.putInt("code", resultCode);
+ if (error[0] != null) result.putString("error", error[0]);
+ ListTag outputResult = new ListTag();
+ for (Text line: output) outputResult.add(StringTag.of(Text.Serializer.toJson(line)));
+ if (!output.isEmpty()) result.put("output", outputResult);
+ player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
+ CarpetClient.CARPET_CHANNEL,
+ DataBuilder.create().withCustomNbt("clientCommand", result).build()
+ ));
+ // run command plug to command output,
+ }
+
+
+ private static void onClientData(ServerPlayerEntity player, PacketByteBuf data)
+ {
+ CompoundTag compound = data.readCompoundTag();
+ if (compound == null) return;
+ for (String key: compound.getKeys())
+ {
+ if (dataHandlers.containsKey(key))
+ dataHandlers.get(key).accept(player, compound.get(key));
+ else
+ CarpetSettings.LOG.warn("Unknown carpet client data: "+key);
+ }
+ }
+
public static void updateRuleWithConnectedClients(ParsedRule> rule)
{
if (CarpetSettings.superSecretSetting) return;
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 6541144b..bb2e8289 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -494,6 +494,37 @@ public class CarpetEventServer
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
}
},
+ PLAYER_CHOOSES_RECIPE("player_chooses_recipe", 3, false)
+ {
+ @Override
+ public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack)
+ {
+ handler.call( () ->
+ {
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new StringValue(NBTSerializableValue.nameFromRegistryId(recipe))),
+ ((c, t) -> new NumericValue(fullStack))
+ );
+ }, player::getCommandSource);
+ }
+ },
+ PLAYER_SWITCHES_SLOT("player_switches_slot", 3, false)
+ {
+ @Override
+ public void onSlotSwitch(ServerPlayerEntity player, int from, int to)
+ {
+ if (from == to) return; // initial slot update
+ handler.call( () ->
+ {
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new NumericValue(from)),
+ ((c, t) -> new NumericValue(to))
+ );
+ }, player::getCommandSource);
+ }
+ },
PLAYER_TAKES_DAMAGE("player_takes_damage", 4, false)
{
@Override
@@ -653,6 +684,8 @@ public class CarpetEventServer
public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, RegistryKey fromDim, RegistryKey dimTo) {}
public void onDamage(Entity target, float amount, DamageSource source) { }
+ public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {}
+ public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
public void onWorldEvent(ServerWorld world, BlockPos pos) { }
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 58d185d2..3100d815 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -3,7 +3,10 @@ package carpet.script;
import carpet.CarpetServer;
import carpet.fakes.BiomeArrayInterface;
import carpet.fakes.ChunkTicketManagerInterface;
+import carpet.fakes.IngredientInterface;
import carpet.fakes.MinecraftServerInterface;
+import carpet.fakes.BiomeArrayInterface;
+import carpet.fakes.RecipeManagerInterface;
import carpet.fakes.ServerChunkManagerInterface;
import carpet.fakes.SpawnHelperInnerInterface;
import carpet.fakes.StatTypeInterface;
@@ -83,6 +86,13 @@ import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.predicate.entity.EntityPredicates;
+import net.minecraft.recipe.AbstractCookingRecipe;
+import net.minecraft.recipe.CuttingRecipe;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.recipe.ShapedRecipe;
+import net.minecraft.recipe.ShapelessRecipe;
+import net.minecraft.recipe.SpecialCraftingRecipe;
import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ScoreboardCriterion;
import net.minecraft.scoreboard.ScoreboardObjective;
@@ -135,6 +145,7 @@ 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.Comparator;
import java.util.HashMap;
@@ -1493,6 +1504,121 @@ public class CarpetExpression
return (_c, _t) -> res;
});
+ this.expr.addLazyFunction("recipe_data", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext)c;
+ if (lv.size() < 1) throw new InternalExpressionException("'recipe_data' requires at least one argument");
+ String recipeName = lv.get(0).evalValue(c).getString();
+ RecipeType type = RecipeType.CRAFTING;
+ if (lv.size() > 1)
+ {
+ String recipeType = lv.get(1).evalValue(c).getString();
+ try
+ {
+ type = Registry.RECIPE_TYPE.get(new Identifier(recipeType));
+ }
+ catch (InvalidIdentifierException ignored)
+ {
+ throw new InternalExpressionException("Unknown crafting category: " + recipeType);
+ }
+ }
+ List> recipes;
+ try
+ {
+ recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
+ }
+ catch (InvalidIdentifierException ignored)
+ {
+ return LazyValue.NULL;
+ }
+ if (recipes.isEmpty())
+ return LazyValue.NULL;
+ List recipesOutput = new ArrayList<>();
+ for (Recipe> recipe: recipes)
+ {
+ ItemStack result = recipe.getOutput();
+ List ingredientValue = new ArrayList<>();
+ CarpetSettings.LOG.error("input size: "+recipe.getPreviewInputs().size());
+ recipe.getPreviewInputs().forEach(
+ ingredient ->
+ {
+ // I am flattening ingredient lists per slot.
+ // consider recipe_data('wooden_sword','crafting') and ('iron_nugget', 'blasting') and notice difference
+ // in depths of lists.
+ List> stacks = ((IngredientInterface) (Object) ingredient).getRecipeStacks();
+ if (stacks.isEmpty())
+ {
+ ingredientValue.add(Value.NULL);
+ }
+ else
+ {
+ List alternatives = new ArrayList<>();
+ stacks.forEach(col -> col.stream().map(ListValue::fromItemStack).forEach(alternatives::add));
+ ingredientValue.add(ListValue.wrap(alternatives));
+ }
+ }
+ );
+ Value recipeSpec;
+ if (recipe instanceof ShapedRecipe)
+ {
+ recipeSpec = ListValue.of(
+ new StringValue("shaped"),
+ new NumericValue(((ShapedRecipe) recipe).getWidth()),
+ new NumericValue(((ShapedRecipe) recipe).getHeight())
+ );
+ }
+ else if (recipe instanceof ShapelessRecipe)
+ {
+ recipeSpec = ListValue.of(new StringValue("shapeless"));
+ }
+ else if (recipe instanceof AbstractCookingRecipe)
+ {
+ recipeSpec = ListValue.of(
+ new StringValue("smelting"),
+ new NumericValue(((AbstractCookingRecipe) recipe).getCookTime()),
+ new NumericValue(((AbstractCookingRecipe) recipe).getExperience())
+ );
+ }
+ else if (recipe instanceof CuttingRecipe)
+ {
+ recipeSpec = ListValue.of(new StringValue("cutting"));
+ }
+ else if (recipe instanceof SpecialCraftingRecipe)
+ {
+ recipeSpec = ListValue.of(new StringValue("special"));
+ }
+ else
+ {
+ recipeSpec = ListValue.of(new StringValue("custom"));
+ }
+
+ recipesOutput.add(ListValue.of(ListValue.fromItemStack(result), ListValue.wrap(ingredientValue), recipeSpec));
+ }
+ Value ret = ListValue.wrap(recipesOutput);
+ return (_c, _t) -> ret;
+ });
+
+ this.expr.addLazyFunction("crafting_remaining_item", 1, (c, t, lv) ->
+ {
+ String itemStr = lv.get(0).evalValue(c).getString();
+ Item item;
+ try
+ {
+ Identifier id = new Identifier(itemStr);
+ item = Registry.ITEM.get(id);
+ if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air"))
+ throw new InvalidIdentifierException("boo");
+ }
+ catch (InvalidIdentifierException ignored)
+ {
+ throw new InternalExpressionException("Incorrect item: "+itemStr);
+ }
+ if (!item.hasRecipeRemainder()) return LazyValue.NULL;
+ Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder())));
+ return (_c, _t ) -> ret;
+ });
+
+
this.expr.addLazyFunction("inventory_size", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext) c;
@@ -2960,7 +3086,7 @@ public class CarpetExpression
this.expr.addLazyFunction("seed", -1, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
- Value ret = new NumericValue(s.getWorld().getSeed());
+ Value ret = new StringValue(Long.toString(s.getWorld().getSeed()));
return (cc, tt) -> ret;
});
diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc
index 1659d565..c6934911 100644
--- a/src/main/resources/assets/carpet/scripts/event_test.sc
+++ b/src/main/resources/assets/carpet/scripts/event_test.sc
@@ -244,3 +244,23 @@ __on_player_disconnects(player, reason) ->
print('__on_player_disconnects(player)');
print('Player '+player+' disconnects because: '+reason)
);
+
+__on_player_chooses_recipe(player, recipe, full_stack) ->
+(
+ print('');
+ print('__on_player_chooses_recipe(player, recipe, full_stack)');
+ print('player chooses recipe:');
+ print(' - player: '+player);
+ print(' - recipe: '+recipe);
+ print(' - full stack: '+ full_stack);
+);
+
+__on_player_switches_slot(player, from, to) ->
+(
+ print('');
+ print('__on_player_switches_slot(player, from, to)');
+ print('player switches inventory slot:');
+ print(' - player: '+player);
+ print(' - from: '+ from);
+ print(' - to: '+ to);
+);
diff --git a/src/main/resources/carpet.accesswidener b/src/main/resources/carpet.accesswidener
new file mode 100644
index 00000000..c03c46bb
--- /dev/null
+++ b/src/main/resources/carpet.accesswidener
@@ -0,0 +1,2 @@
+accessWidener v1 named
+Accessible class net/minecraft/recipe/Ingredient$Entry
\ No newline at end of file
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index f3587c63..513b4937 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -160,6 +160,9 @@
"ServerWorld_scarpetEventMixin",
"HungerManagerMixin_scarpetEventsMixin",
+ "RecipeManager_scarpetMixin",
+ "Ingredient_scarpetMixin",
+
"PistonBlock_movableTEMixin",
"PistonBlockEntity_movableTEMixin",
"World_movableTEMixin",
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 79bb312f..d733e8c0 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -24,6 +24,7 @@
"mixins": [
"carpet.mixins.json"
],
+ "accessWidener" : "carpet.accesswidener",
"depends": {
"minecraft": "1.16.x",