diff --git a/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetCrafterScreen.java b/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetCrafterScreen.java new file mode 100644 index 000000000..110d4cfb2 --- /dev/null +++ b/src/main/java/carpet/mixins/ServerGamePacketListenerImpl_scarpetCrafterScreen.java @@ -0,0 +1,29 @@ +package carpet.mixins; + +import net.minecraft.network.protocol.game.ServerboundContainerSlotStateChangedPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.level.block.entity.CrafterBlockEntity; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import carpet.script.value.ScreenValue.ScarpetCrafterMenu; + +@Mixin(ServerGamePacketListenerImpl.class) +public abstract class ServerGamePacketListenerImpl_scarpetCrafterScreen +{ + + @Shadow + public ServerPlayer player; + + @Inject(method = "handleContainerSlotStateChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/inventory/CrafterMenu;getContainer()Lnet/minecraft/world/Container;")) + private void injected(ServerboundContainerSlotStateChangedPacket serverboundContainerSlotStateChangedPacket,CallbackInfo ci) { + if(player.containerMenu instanceof ScarpetCrafterMenu cm && !(cm.getContainer() instanceof CrafterBlockEntity)){ + cm.setSlotState(serverboundContainerSlotStateChangedPacket.slotId(), serverboundContainerSlotStateChangedPacket.newState()); + }; + } +} diff --git a/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java index afba6cde3..675b4ea77 100644 --- a/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayer_scarpetEventMixin.java @@ -27,6 +27,7 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_CHANGES_DIMENSION; import static carpet.script.CarpetEventServer.Event.PLAYER_DIES; import static carpet.script.CarpetEventServer.Event.PLAYER_FINISHED_USING_ITEM; +import static carpet.script.CarpetEventServer.Event.PLAYER_OPEN_SCREEN; import static carpet.script.CarpetEventServer.Event.STATISTICS; @Mixin(ServerPlayer.class) @@ -45,6 +46,19 @@ public ServerPlayer_scarpetEventMixin(Level level, BlockPos blockPos, float f, G @Shadow public boolean wonGame; + @Inject(method = "openMenu", at = @At("RETURN"))//"at return" so that the screen can be get by scarpet. + private void onOpenScreen(CallbackInfoReturnable cir) + { + if (cir.getReturnValue().isPresent()) { + PLAYER_OPEN_SCREEN.onPlayerEvent((ServerPlayer)(Object)this); + }; + } + @Inject(method = "openHorseInventory", at = @At("RETURN"))//"at return" so that the screen can be get by scarpet. + private void onOpenScreen(CallbackInfo ci) + { + PLAYER_OPEN_SCREEN.onPlayerEvent((ServerPlayer)(Object)this); + } + @Redirect(method = "completeUsingItem", at = @At( value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;completeUsingItem()V" diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 24de93ac0..5047aacbb 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -13,6 +13,7 @@ import carpet.script.value.ListValue; import carpet.script.value.NBTSerializableValue; import carpet.script.value.NumericValue; +import carpet.script.value.ScreenValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; @@ -853,6 +854,18 @@ public void onSlotSwitch(ServerPlayer player, int from, int to) ), player::createCommandSourceStack); } }; + public static final Event PLAYER_OPEN_SCREEN = new Event("player_open_screen", 2, false) + { + @Override + public boolean onPlayerEvent(ServerPlayer player) + { + return handler.call(() -> + Arrays.asList( + new EntityValue(player) + , StringValue.of(ScreenValue.playerScreenTypeName(player)) + ), player::createCommandSourceStack); + } + }; public static final Event PLAYER_SWAPS_HANDS = new Event("player_swaps_hands", 1, false) { @Override diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index be4e42a32..aed3a5a2a 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -475,6 +475,27 @@ else if (owner instanceof LivingEntity livingEntity) return new ScreenValue(player, type, name, function, c); }); + expression.addContextFunction("get_current_screen", -1, (c, t, lv) -> + { + if (lv.size() < 1) + { + throw new InternalExpressionException("'get_current_screen' requires at least 1 argument"); + } + Value playerValue = lv.get(0); + ServerPlayer player = EntityValue.getPlayerByValue(((CarpetContext) c).server(), playerValue); + if (player == null) + { + throw new InternalExpressionException("'get_current_screen' requires a valid online player as the first argument."); + } + FunctionValue function = null; + if (lv.size() > 1) + { + function = FunctionArgument.findIn(c, expression.module, lv, 1, true, false).function; + } + + return new ScreenValue(player, function, c); + }); + expression.addContextFunction("close_screen", 1, (c, t, lv) -> { Value value = lv.get(0); diff --git a/src/main/java/carpet/script/value/ScreenValue.java b/src/main/java/carpet/script/value/ScreenValue.java index 087f84540..fcb971e69 100644 --- a/src/main/java/carpet/script/value/ScreenValue.java +++ b/src/main/java/carpet/script/value/ScreenValue.java @@ -9,6 +9,8 @@ import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; import carpet.script.external.Vanilla; +import carpet.script.value.ScreenValue.ScarpetScreenHandlerFactory; +import carpet.script.value.ScreenValue.ScreenHandlerInventory; import java.util.Arrays; import java.util.HashMap; @@ -17,6 +19,7 @@ import java.util.OptionalInt; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -39,11 +42,13 @@ import net.minecraft.world.inventory.ClickType; import net.minecraft.world.inventory.ContainerListener; import net.minecraft.world.inventory.CraftingMenu; +import net.minecraft.world.inventory.CrafterMenu; import net.minecraft.world.inventory.DataSlot; import net.minecraft.world.inventory.EnchantmentMenu; import net.minecraft.world.inventory.FurnaceMenu; import net.minecraft.world.inventory.GrindstoneMenu; import net.minecraft.world.inventory.HopperMenu; +import net.minecraft.world.inventory.HorseInventoryMenu; import net.minecraft.world.inventory.LecternMenu; import net.minecraft.world.inventory.LoomMenu; import net.minecraft.world.inventory.MerchantMenu; @@ -101,9 +106,25 @@ public class ScreenValue extends Value screenHandlerFactories.put("smithing", SmithingMenu::new); screenHandlerFactories.put("smoker", SmokerMenu::new); screenHandlerFactories.put("stonecutter", StonecutterMenu::new); + screenHandlerFactories.put("crafter_3x3", ScarpetCrafterMenu::new); } + public static class ScarpetCrafterMenu extends CrafterMenu{ + private boolean suppress = false; + public ScarpetCrafterMenu(int i, Inventory inventory) { + super(i, inventory); + addSlotListener(this);// this line it to make the result item can be generated in the output slot. + } + public void broadcastChanges(){ + if(!suppress)super.broadcastChanges(); + } + public void setSlotState(int i, boolean bl) { + suppress = true; + super.setSlotState(i, bl); + suppress = false; + } //Do I really need to go to this point to make them similar? + } protected interface ScarpetScreenHandlerFactory { AbstractContainerMenu create(int syncId, Inventory playerInventory); @@ -130,6 +151,40 @@ public ScreenValue(ServerPlayer player, String type, Component name, @Nullable F this.inventory = new ScreenHandlerInventory(this.screenHandler); } + public ScreenValue(ServerPlayer player, @Nullable FunctionValue callback, Context c) + { + this.screenHandler = player.containerMenu; + + this.name = null; //seems that the game forgot that. should i make something like a weak map to remember it? + this.typestring = playerScreenTypeName(player); + + if (callback != null) + { + callback.checkArgs(4); + } + this.callback = callback; + this.hostname = c.host.getName(); + this.scriptServer = (CarpetScriptServer) c.host.scriptServer(); + this.player = player; + + addListenerCallback(screenHandler); + this.inventory = new ScreenHandlerInventory(this.screenHandler); + } + + public static String playerScreenTypeName(ServerPlayer player) { + if (!player.hasContainerOpen()) { + return "inventory"; + } + try { + return ValueConversions.simplify(BuiltInRegistries.MENU.getKey(player.containerMenu.getType())); + } catch (java.lang.UnsupportedOperationException e) { + if (player.containerMenu instanceof HorseInventoryMenu) { + return "horse"; + } + return "unknown"; + } + } + private MenuProvider createScreenHandlerFactory() { if (!screenHandlerFactories.containsKey(this.typestring)) @@ -316,6 +371,17 @@ private DataSlot getProperty(String propertyName) case "enchantment_level_3" -> getPropertyForType(EnchantmentMenu.class, "enchantment", 9, propertyName); case "banner_pattern" -> getPropertyForType(LoomMenu.class, "loom", 0, propertyName); case "stonecutter_recipe" -> getPropertyForType(StonecutterMenu.class, "stonecutter", 0, propertyName); + case "crafter_slot_disable_0" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 0, propertyName); + case "crafter_slot_disable_1" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 1, propertyName); + case "crafter_slot_disable_2" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 2, propertyName); + case "crafter_slot_disable_3" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 3, propertyName); + case "crafter_slot_disable_4" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 4, propertyName); + case "crafter_slot_disable_5" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 5, propertyName); + case "crafter_slot_disable_6" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 6, propertyName); + case "crafter_slot_disable_7" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 7, propertyName); + case "crafter_slot_disable_8" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 8, propertyName); + case "crafter_enable" -> getPropertyForType(CrafterMenu.class, "crafter_3x3", 9, propertyName); + default -> throw new InternalExpressionException("Invalid screen property: " + propertyName); }; diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index a68896207..de4a31484 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -54,6 +54,7 @@ "Villager_aiMixin", "Player_fakePlayersMixin", "ServerGamePacketListenerImpl_coreMixin", + "ServerGamePacketListenerImpl_scarpetCrafterScreen", "MinecraftServer_scarpetMixin", "ExperienceOrb_xpNoCooldownMixin", "Player_xpNoCooldownMixin",