Pārlūkot izejas kodu

A few more events

shedaniel 4 gadi atpakaļ
vecāks
revīzija
1a2d921ad0

+ 66 - 0
common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.event.events;
+
+import me.shedaniel.architectury.event.Event;
+import me.shedaniel.architectury.event.EventFactory;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.InteractionResultHolder;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.block.state.BlockState;
+
+public interface InteractionEvent {
+    Event<LeftClickBlock> LEFT_CLICK_BLOCK = EventFactory.createInteractionResult(LeftClickBlock.class);
+    Event<RightClickBlock> RIGHT_CLICK_BLOCK = EventFactory.createInteractionResult(RightClickBlock.class);
+    Event<RightClickItem> RIGHT_CLICK_ITEM = EventFactory.createInteractionResultHolder(RightClickItem.class);
+    Event<ClientLeftClickAir> CLIENT_LEFT_CLICK_AIR = EventFactory.createLoop(ClientLeftClickAir.class);
+    Event<ClientRightClickAir> CLIENT_RIGHT_CLICK_AIR = EventFactory.createLoop(ClientRightClickAir.class);
+    Event<InteractEntity> INTERACT_ENTITY = EventFactory.createInteractionResult(InteractEntity.class);
+    
+    interface RightClickBlock {
+        InteractionResult click(Player player, InteractionHand hand, BlockPos pos, Direction face);
+    }
+    
+    interface LeftClickBlock {
+        InteractionResult click(Player player, InteractionHand hand, BlockPos pos, Direction face);
+    }
+    
+    interface RightClickItem {
+        InteractionResultHolder<ItemStack> click(Player player, InteractionHand hand);
+    }
+    
+    interface ClientRightClickAir {
+        void click(Player player, InteractionHand hand);
+    }
+    
+    interface ClientLeftClickAir {
+        void click(Player player, InteractionHand hand);
+    }
+    
+    interface InteractEntity {
+        InteractionResult interact(Player player, Entity entity, InteractionHand hand);
+    }
+    
+    interface BlockBreak {
+        InteractionResult breakBlock(Player player, BlockPos pos, BlockState state);
+    }
+}

+ 186 - 0
common/src/main/java/me/shedaniel/architectury/fluid/FluidStack.java

@@ -0,0 +1,186 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.fluid;
+
+import me.shedaniel.architectury.hooks.FluidStackHooks;
+import me.shedaniel.architectury.utils.Fraction;
+import me.shedaniel.architectury.utils.NbtType;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.Fluids;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+public final class FluidStack {
+    private Fraction amount;
+    @Nullable
+    private CompoundTag tag;
+    private Supplier<Fluid> fluid;
+    
+    private FluidStack(Supplier<Fluid> fluid, Fraction amount, CompoundTag tag) {
+        this.fluid = Objects.requireNonNull(fluid);
+        this.amount = Objects.requireNonNull(amount);
+        this.tag = tag == null ? null : tag.copy();
+    }
+    
+    public static FluidStack create(Supplier<Fluid> fluid, Fraction amount, @Nullable CompoundTag tag) {
+        return new FluidStack(fluid, amount, tag);
+    }
+    
+    public static FluidStack create(Supplier<Fluid> fluid, Fraction amount) {
+        return create(fluid, amount, null);
+    }
+    
+    public static FluidStack create(FluidStack stack, Fraction amount) {
+        return create(stack.getRawFluidSupplier(), amount, stack.getTag());
+    }
+    
+    public static Fraction bucketAmount() {
+        return FluidStackHooks.bucketAmount();
+    }
+    
+    public final Fluid getFluid() {
+        return isEmpty() ? Fluids.EMPTY : getRawFluid();
+    }
+    
+    @Nullable
+    public final Fluid getRawFluid() {
+        return fluid.get();
+    }
+    
+    public final Supplier<Fluid> getRawFluidSupplier() {
+        return fluid;
+    }
+    
+    public boolean isEmpty() {
+        return getRawFluid() == Fluids.EMPTY || !amount.isGreaterThan(Fraction.zero());
+    }
+    
+    public Fraction getAmount() {
+        return isEmpty() ? Fraction.zero() : amount;
+    }
+    
+    public void setAmount(Fraction amount) {
+        this.amount = Objects.requireNonNull(amount);
+    }
+    
+    public void grow(Fraction amount) {
+        setAmount(this.amount.add(amount));
+    }
+    
+    public void shrink(Fraction amount) {
+        setAmount(this.amount.minus(amount));
+    }
+    
+    public boolean hasTag() {
+        return tag != null;
+    }
+    
+    @Nullable
+    public CompoundTag getTag() {
+        return tag;
+    }
+    
+    public void setTag(@Nullable CompoundTag tag) {
+        this.tag = tag;
+    }
+    
+    public CompoundTag getOrCreateTag() {
+        if (tag == null)
+            setTag(new CompoundTag());
+        return tag;
+    }
+    
+    @Nullable
+    public CompoundTag getChildTag(String childName) {
+        if (tag == null)
+            return null;
+        return tag.getCompound(childName);
+    }
+    
+    public CompoundTag getOrCreateChildTag(String childName) {
+        getOrCreateTag();
+        CompoundTag child = tag.getCompound(childName);
+        if (!tag.contains(childName, NbtType.COMPOUND)) {
+            tag.put(childName, child);
+        }
+        return child;
+    }
+    
+    public void removeChildTag(String childName) {
+        if (tag != null)
+            tag.remove(childName);
+    }
+    
+    public Component getName() {
+        return FluidStackHooks.getName(this);
+    }
+    
+    public String getTranslationKey() {
+        return FluidStackHooks.getTranslationKey(this);
+    }
+    
+    public FluidStack copy() {
+        return new FluidStack(fluid, amount, tag);
+    }
+    
+    @Override
+    public final int hashCode() {
+        int code = 1;
+        code = 31 * code + getFluid().hashCode();
+        code = 31 * code + amount.hashCode();
+        if (tag != null)
+            code = 31 * code + tag.hashCode();
+        return code;
+    }
+    
+    @Override
+    public final boolean equals(Object o) {
+        if (!(o instanceof FluidStack)) {
+            return false;
+        }
+        return isFluidStackEqual((FluidStack) o);
+    }
+    
+    public boolean isFluidStackEqual(FluidStack other) {
+        return getFluid() == other.getFluid() && getAmount().equals(other.getAmount()) && isTagEqual(other);
+    }
+    
+    private boolean isTagEqual(FluidStack other) {
+        return tag == null ? other.tag == null : other.tag != null && tag.equals(other.tag);
+    }
+    
+    public static FluidStack read(FriendlyByteBuf buf) {
+        return FluidStackHooks.read(buf);
+    }
+    
+    public static FluidStack read(CompoundTag tag) {
+        return FluidStackHooks.read(tag);
+    }
+    
+    public void write(FriendlyByteBuf buf) {
+        FluidStackHooks.write(this, buf);
+    }
+    
+    public CompoundTag write(CompoundTag tag) {
+        return FluidStackHooks.write(this, tag);
+    }
+}

+ 97 - 0
common/src/main/java/me/shedaniel/architectury/hooks/FluidStackHooks.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.hooks;
+
+import me.shedaniel.architectury.ArchitecturyPopulator;
+import me.shedaniel.architectury.Populatable;
+import me.shedaniel.architectury.fluid.FluidStack;
+import me.shedaniel.architectury.utils.Fraction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.chat.Component;
+
+public class FluidStackHooks {
+    private FluidStackHooks() {}
+    
+    @Populatable
+    private static final Impl IMPL = null;
+    
+    public static Component getName(FluidStack stack) {
+        return IMPL.getName(stack);
+    }
+    
+    public static String getTranslationKey(FluidStack stack) {
+        return IMPL.getTranslationKey(stack);
+    }
+    
+    /**
+     * Platform-specific FluidStack read.
+     */
+    public static FluidStack read(FriendlyByteBuf buf) {
+        return IMPL.read(buf);
+    }
+    
+    /**
+     * Platform-specific FluidStack write.
+     */
+    public static void write(FluidStack stack, FriendlyByteBuf buf) {
+        IMPL.write(stack, buf);
+    }
+    
+    /**
+     * Platform-specific FluidStack read.
+     */
+    public static FluidStack read(CompoundTag tag) {
+        return IMPL.read(tag);
+    }
+    
+    /**
+     * Platform-specific FluidStack write.
+     */
+    public static CompoundTag write(FluidStack stack, CompoundTag tag) {
+        return IMPL.write(stack, tag);
+    }
+    
+    /**
+     * Platform-specific bucket amount.
+     * Forge: 1000
+     * Fabric: 1
+     */
+    public static Fraction bucketAmount() {
+        return IMPL.bucketAmount();
+    }
+    
+    public interface Impl {
+        Fraction bucketAmount();
+        
+        Component getName(FluidStack stack);
+        
+        String getTranslationKey(FluidStack stack);
+        
+        FluidStack read(FriendlyByteBuf buf);
+        
+        void write(FluidStack stack, FriendlyByteBuf buf);
+        
+        FluidStack read(CompoundTag tag);
+        
+        CompoundTag write(FluidStack stack, CompoundTag tag);
+    }
+    
+    static {
+        ArchitecturyPopulator.populate();
+    }
+}

+ 177 - 0
common/src/main/java/me/shedaniel/architectury/utils/Fraction.java

@@ -0,0 +1,177 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.utils;
+
+import com.google.common.math.LongMath;
+import org.jetbrains.annotations.NotNull;
+
+import java.text.DecimalFormat;
+
+public final class Fraction extends Number implements Comparable<Fraction> {
+    private static final Fraction ZERO = ofWhole(0);
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("###.###");
+    private final long numerator;
+    private final long denominator;
+    private boolean simplified;
+    
+    private Fraction(long numerator, long denominator) {
+        if (denominator > 0) {
+            this.numerator = numerator;
+            this.denominator = denominator;
+        } else if (denominator < 0) {
+            this.numerator = -numerator;
+            this.denominator = -denominator;
+        } else {
+            throw new ArithmeticException("/ by zero");
+        }
+        this.simplified = (this.numerator >= -1 && this.numerator <= 1) || this.denominator == 1;
+    }
+    
+    public static Fraction zero() {
+        return ZERO;
+    }
+    
+    public static Fraction ofWhole(long whole) {
+        return new Fraction(whole, 1);
+    }
+    
+    public static Fraction of(long numerator, long denominator) {
+        return new Fraction(numerator, denominator);
+    }
+    
+    public static Fraction of(long whole, long numerator, long denominator) {
+        return of(numerator + whole * denominator, denominator);
+    }
+    
+    public static Fraction from(double value) {
+        int whole = (int) value;
+        double part = value - whole;
+        int i = 1;
+        
+        while (true) {
+            double tem = part / (1D / i);
+            long numerator = Math.round(tem);
+            if (Math.abs(tem - numerator) < 0.00001) {
+                return of(whole, numerator, i);
+            }
+            i++;
+        }
+    }
+    
+    public long getNumerator() {
+        return numerator;
+    }
+    
+    public long getDenominator() {
+        return denominator;
+    }
+    
+    public Fraction add(Fraction other) {
+        if (other.numerator == 0) return this;
+        return of(numerator * other.denominator + other.numerator * denominator, denominator * other.denominator);
+    }
+    
+    public Fraction minus(Fraction other) {
+        if (other.numerator == 0) return this;
+        return of(numerator * other.denominator - other.numerator * denominator, denominator * other.denominator);
+    }
+    
+    public Fraction multiply(Fraction other) {
+        if (other.numerator == other.denominator) return this;
+        return of(numerator * other.numerator, denominator * other.denominator);
+    }
+    
+    public Fraction divide(Fraction other) {
+        if (other.numerator == other.denominator) return this;
+        return of(numerator * other.denominator, denominator * other.numerator);
+    }
+    
+    public Fraction inverse() {
+        if (numerator == denominator)
+            return this;
+        Fraction fraction = of(denominator, numerator);
+        fraction.simplified = fraction.simplified && this.simplified;
+        return fraction;
+    }
+    
+    public Fraction simplify() {
+        if (simplified)
+            return this;
+        if (numerator == 0)
+            return ofWhole(0);
+        long gcd = LongMath.gcd(Math.abs(numerator), denominator);
+        Fraction fraction = of(numerator / gcd, denominator / gcd);
+        fraction.simplified = true;
+        return fraction;
+    }
+    
+    public boolean isGreaterThan(Fraction fraction) {
+        return compareTo(fraction) > 0;
+    }
+    
+    public boolean isLessThan(Fraction fraction) {
+        return compareTo(fraction) < 0;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Fraction fraction = (Fraction) o;
+        return numerator * fraction.denominator == denominator * fraction.numerator;
+    }
+    
+    @Override
+    public int hashCode() {
+        return Double.hashCode(doubleValue());
+    }
+    
+    @Override
+    public int compareTo(@NotNull Fraction fraction) {
+        return Long.compare(numerator * fraction.denominator, denominator * fraction.numerator);
+    }
+    
+    @Override
+    public int intValue() {
+        return (int) longValue();
+    }
+    
+    @Override
+    public long longValue() {
+        return numerator / denominator;
+    }
+    
+    @Override
+    public float floatValue() {
+        return (float) numerator / denominator;
+    }
+    
+    @Override
+    public double doubleValue() {
+        return (double) numerator / denominator;
+    }
+    
+    public String toDecimalString() {
+        return DECIMAL_FORMAT.format(doubleValue());
+    }
+    
+    @Override
+    public String toString() {
+        if (intValue() == doubleValue()) return toDecimalString();
+        return String.format("%s (%d/%d)", toDecimalString(), numerator, denominator);
+    }
+}

+ 7 - 0
fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventHandlerImpl.java

@@ -26,6 +26,9 @@ import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
+import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseItemCallback;
 import net.minecraft.commands.Commands;
 
 public class EventHandlerImpl implements EventHandler.Impl {
@@ -59,6 +62,10 @@ public class EventHandlerImpl implements EventHandler.Impl {
         ServerWorldEvents.UNLOAD.register((server, world) -> LifecycleEvent.SERVER_WORLD_UNLOAD.invoker().act(world));
         
         CommandRegistrationCallback.EVENT.register((commandDispatcher, b) -> CommandRegistrationEvent.EVENT.invoker().register(commandDispatcher, b ? Commands.CommandSelection.DEDICATED : Commands.CommandSelection.INTEGRATED));
+        
+        UseItemCallback.EVENT.register((player, world, hand) -> InteractionEvent.RIGHT_CLICK_ITEM.invoker().click(player, hand));
+        UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> InteractionEvent.RIGHT_CLICK_BLOCK.invoker().click(player, hand, hitResult.getBlockPos(), hitResult.getDirection()));
+        AttackBlockCallback.EVENT.register((player, world, hand, pos, face) -> InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(player, hand, pos, face));
     }
     
     @Override

+ 14 - 0
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPlayer.java

@@ -16,9 +16,12 @@
 
 package me.shedaniel.architectury.mixin.fabric;
 
+import me.shedaniel.architectury.event.events.InteractionEvent;
 import me.shedaniel.architectury.event.events.PlayerEvent;
 import me.shedaniel.architectury.event.events.TickEvent;
+import net.minecraft.world.InteractionHand;
 import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.item.ItemEntity;
 import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.ItemStack;
@@ -46,4 +49,15 @@ public class MixinPlayer {
             cir.setReturnValue(null);
         }
     }
+    
+    @Inject(method = "interactOn", at = @At(value = "INVOKE",
+                                            target = "Lnet/minecraft/world/entity/player/Player;getItemInHand(Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/item/ItemStack;",
+                                            ordinal = 0),
+            cancellable = true)
+    private void entityInteract(Entity entity, InteractionHand interactionHand, CallbackInfoReturnable<InteractionResult> cir) {
+        InteractionResult result = InteractionEvent.INTERACT_ENTITY.invoker().interact((Player) (Object) this, entity, interactionHand);
+        if (result != InteractionResult.PASS) {
+            cir.setReturnValue(result);
+        }
+    }
 }

+ 16 - 0
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinResultSlot.java

@@ -1,3 +1,19 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package me.shedaniel.architectury.mixin.fabric;
 
 import me.shedaniel.architectury.event.events.PlayerEvent;

+ 20 - 0
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMinecraft.java

@@ -16,24 +16,44 @@
 
 package me.shedaniel.architectury.mixin.fabric.client;
 
+import me.shedaniel.architectury.event.events.InteractionEvent;
 import me.shedaniel.architectury.event.events.PlayerEvent;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.screens.Screen;
 import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.phys.HitResult;
 import org.jetbrains.annotations.Nullable;
 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 org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
 @Mixin(Minecraft.class)
 public class MixinMinecraft {
     @Shadow @Nullable public LocalPlayer player;
     
+    @Shadow @Nullable public HitResult hitResult;
+    
     @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V",
             at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/chat/NarratorChatListener;clear()V"))
     private void handleLogin(Screen screen, CallbackInfo ci) {
         PlayerEvent.CLIENT_PLAYER_QUIT.invoker().quit(player);
     }
+    
+    @Inject(method = "startUseItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;isEmpty()Z", ordinal = 1),
+            locals = LocalCapture.CAPTURE_FAILHARD)
+    private void rightClickAir(CallbackInfo ci, InteractionHand var1[], int var2, int var3, InteractionHand interactionHand, ItemStack itemStack) {
+        if (itemStack.isEmpty() && (this.hitResult == null || this.hitResult.getType() == HitResult.Type.MISS)) {
+            InteractionEvent.CLIENT_RIGHT_CLICK_AIR.invoker().click(player, interactionHand);
+        }
+    }
+    
+    @Inject(method = "startAttack", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;resetAttackStrengthTicker()V", ordinal = 0))
+    private void leftClickAir(CallbackInfo ci) {
+        InteractionEvent.CLIENT_LEFT_CLICK_AIR.invoker().click(player, InteractionHand.MAIN_HAND);
+    }
 }

+ 42 - 0
fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/client/MixinMultiPlayerGameMode.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2020 shedaniel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.shedaniel.architectury.mixin.fabric.client;
+
+import me.shedaniel.architectury.event.events.InteractionEvent;
+import net.minecraft.client.multiplayer.MultiPlayerGameMode;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.player.Player;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(MultiPlayerGameMode.class)
+public class MixinMultiPlayerGameMode {
+    @Inject(method = "interact",
+            at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V",
+                     shift = At.Shift.AFTER),
+            cancellable = true)
+    private void entityInteract(Player player, Entity entity, InteractionHand interactionHand, CallbackInfoReturnable<InteractionResult> cir) {
+        InteractionResult result = InteractionEvent.INTERACT_ENTITY.invoker().interact(player, entity, interactionHand);
+        if (result != InteractionResult.PASS) {
+            cir.setReturnValue(result);
+        }
+    }
+}

+ 1 - 0
fabric/src/main/resources/architectury.mixins.json

@@ -9,6 +9,7 @@
     "client.MixinDebugScreenOverlay",
     "client.MixinGameRenderer",
     "client.MixinMinecraft",
+    "client.MixinMultiPlayerGameMode",
     "client.MixinScreen"
   ],
   "mixins": [

+ 56 - 6
forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImpl.java

@@ -17,11 +17,13 @@
 package me.shedaniel.architectury.event.forge;
 
 import me.shedaniel.architectury.event.EventHandler;
+import me.shedaniel.architectury.event.events.PlayerEvent;
 import me.shedaniel.architectury.event.events.*;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.IGuiEventListener;
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.entity.player.ServerPlayerEntity;
+import net.minecraft.item.ItemStack;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.ActionResultType;
 import net.minecraft.util.text.ITextComponent;
@@ -38,14 +40,12 @@ import net.minecraftforge.event.entity.EntityJoinWorldEvent;
 import net.minecraftforge.event.entity.item.ItemTossEvent;
 import net.minecraftforge.event.entity.living.LivingAttackEvent;
 import net.minecraftforge.event.entity.living.LivingDeathEvent;
-import net.minecraftforge.event.entity.player.AdvancementEvent;
-import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
-import net.minecraftforge.event.entity.player.ItemTooltipEvent;
-import net.minecraftforge.event.entity.player.PlayerContainerEvent;
+import net.minecraftforge.event.entity.player.*;
 import net.minecraftforge.event.entity.player.PlayerEvent.*;
 import net.minecraftforge.event.world.ExplosionEvent.Detonate;
 import net.minecraftforge.event.world.ExplosionEvent.Start;
 import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.eventbus.api.Event;
 import net.minecraftforge.eventbus.api.SubscribeEvent;
 import net.minecraftforge.fml.LogicalSide;
 import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
@@ -163,6 +163,16 @@ public class EventHandlerImpl implements EventHandler.Impl {
         public static void event(GuiScreenEvent.DrawScreenEvent.Post event) {
             GuiEvent.RENDER_POST.invoker().render(event.getGui(), event.getMatrixStack(), event.getMouseX(), event.getMouseY(), event.getRenderPartialTicks());
         }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.RightClickEmpty event) {
+            InteractionEvent.CLIENT_RIGHT_CLICK_AIR.invoker().click(event.getPlayer(), event.getHand());
+        }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.LeftClickEmpty event) {
+            InteractionEvent.CLIENT_LEFT_CLICK_AIR.invoker().click(event.getPlayer(), event.getHand());
+        }
     }
     
     public static class Common {
@@ -347,16 +357,56 @@ public class EventHandlerImpl implements EventHandler.Impl {
         public static void event(ItemTossEvent event) {
             PlayerEvent.DROP_ITEM.invoker().drop(event.getPlayer(), event.getEntityItem());
         }
-    
+        
         @SubscribeEvent
         public static void event(PlayerContainerEvent.Open event) {
             PlayerEvent.OPEN_MENU.invoker().open(event.getPlayer(), event.getContainer());
         }
-    
+        
         @SubscribeEvent
         public static void event(PlayerContainerEvent.Close event) {
             PlayerEvent.CLOSE_MENU.invoker().close(event.getPlayer(), event.getContainer());
         }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.RightClickItem event) {
+            ActionResult<ItemStack> result = InteractionEvent.RIGHT_CLICK_ITEM.invoker().click(event.getPlayer(), event.getHand());
+            if (result.getResult() != ActionResultType.PASS) {
+                event.setCanceled(true);
+                event.setCancellationResult(result.getResult());
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.RightClickBlock event) {
+            ActionResultType result = InteractionEvent.RIGHT_CLICK_BLOCK.invoker().click(event.getPlayer(), event.getHand(), event.getPos(), event.getFace());
+            if (result != ActionResultType.PASS) {
+                event.setCanceled(true);
+                event.setCancellationResult(result);
+                event.setUseBlock(Event.Result.DENY);
+                event.setUseItem(Event.Result.DENY);
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.EntityInteract event) {
+            ActionResultType result = InteractionEvent.INTERACT_ENTITY.invoker().interact(event.getPlayer(), event.getTarget(), event.getHand());
+            if (result != ActionResultType.PASS) {
+                event.setCanceled(true);
+                event.setCancellationResult(result);
+            }
+        }
+        
+        @SubscribeEvent
+        public static void event(PlayerInteractEvent.LeftClickBlock event) {
+            ActionResultType result = InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(event.getPlayer(), event.getHand(), event.getPos(), event.getFace());
+            if (result != ActionResultType.PASS) {
+                event.setCanceled(true);
+                event.setCancellationResult(result);
+                event.setUseBlock(Event.Result.DENY);
+                event.setUseItem(Event.Result.DENY);
+            }
+        }
     }
     
     @OnlyIn(Dist.DEDICATED_SERVER)