diff --git a/common/src/main/java/me/shedaniel/architectury/event/CompoundEventResult.java b/common/src/main/java/me/shedaniel/architectury/event/CompoundEventResult.java new file mode 100644 index 00000000..368982fe --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/CompoundEventResult.java @@ -0,0 +1,114 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.event; + +import net.minecraft.world.InteractionResultHolder; + +/** + * A result from an event, determines if the event should continue to other listeners, + * determines the outcome of the event, and provides extra result for the outcome. + * + * @param the type of the extra result + * @see #pass() + * @see #interrupt(Boolean, Object) + * @see CompoundEventResult + */ +public class CompoundEventResult { + private static final CompoundEventResult PASS = new CompoundEventResult<>(EventResult.pass(), null); + private final EventResult result; + private final T object; + + /** + * Passes the event to other listeners, and does not set an outcome of the event. + * + * @param the type of the extra result + * @return an event that passes the event to other listeners + */ + public static CompoundEventResult pass() { + return (CompoundEventResult) PASS; + } + + /** + * Interrupts the event and stops it from being passed on to other listeners, + * may or may not set an outcome and extra data of the event. + * + * @param value the outcome of the event, passing {@code null} here means the default outcome, + * which often means falling back to vanilla logic + * @param object the extra data of the result, this usually is the returning value of the event + * @return an event that interrupts the event + */ + public static CompoundEventResult interrupt(Boolean value, T object) { + return new CompoundEventResult<>(EventResult.interrupt(value), object); + } + + private CompoundEventResult(EventResult result, T object) { + this.result = result; + this.object = object; + } + + /** + * Returns whether this result interrupts the evaluation of other listeners. + * + * @return whether this result interrupts the evaluation of other listeners + */ + public boolean interruptsFurtherEvaluation() { + return result.interruptsFurtherEvaluation(); + } + + /** + * Returns the outcome of the result, an passing result will never have an outcome. + * + * @return the outcome of the result, returns {@code null} if fallback + */ + public Boolean value() { + return result.value(); + } + + /** + * Returns the {@link EventResult} view of the result, this returns the same values as + * {@link #interruptsFurtherEvaluation()} and {@link #value()}. + * + * @return the {@link EventResult} view of the result. + */ + public EventResult result() { + return result; + } + + /** + * Returns the extra data of the result, an passing result will never contain any extra data. + * + * @return the extra data of the result, returns {@code null} if passing + */ + public T object() { + return object; + } + + /** + * Returns the Minecraft-facing result, however ignores {@link #interruptsFurtherEvaluation()}. + * + * @return the Minecraft-facing result + */ + public InteractionResultHolder asMinecraft() { + if (value() != null) { + return value() ? InteractionResultHolder.success(object()) : InteractionResultHolder.fail(object()); + } + return InteractionResultHolder.pass(object()); + } +} diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventActor.java b/common/src/main/java/me/shedaniel/architectury/event/EventActor.java new file mode 100644 index 00000000..bb3c7f95 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/EventActor.java @@ -0,0 +1,25 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.event; + +@FunctionalInterface +public interface EventActor { + EventResult act(T t); +} diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java b/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java index f6bce4b3..ba678f4b 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java +++ b/common/src/main/java/me/shedaniel/architectury/event/EventFactory.java @@ -99,6 +99,28 @@ public final class EventFactory { })); } + @SafeVarargs + public static Event createEventResult(T... typeGetter) { + if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); + return createEventResult((Class) typeGetter.getClass().getComponentType()); + } + + @SuppressWarnings("UnstableApiUsage") + public static Event createEventResult(Class clazz) { + return of(listeners -> (T) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{clazz}, new AbstractInvocationHandler() { + @Override + protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { + for (T listener : listeners) { + EventResult result = (EventResult) Objects.requireNonNull(method.invoke(listener, args)); + if (result.interruptsFurtherEvaluation()) { + return result; + } + } + return EventResult.pass(); + } + })); + } + @SafeVarargs public static Event createInteractionResultHolder(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); @@ -121,6 +143,28 @@ public final class EventFactory { })); } + @SafeVarargs + public static Event createCompoundEventResult(T... typeGetter) { + if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); + return createCompoundEventResult((Class) typeGetter.getClass().getComponentType()); + } + + @SuppressWarnings("UnstableApiUsage") + public static Event createCompoundEventResult(Class clazz) { + return of(listeners -> (T) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{clazz}, new AbstractInvocationHandler() { + @Override + protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { + for (T listener : listeners) { + CompoundEventResult result = (CompoundEventResult) Objects.requireNonNull(method.invoke(listener, args)); + if (result.interruptsFurtherEvaluation()) { + return result; + } + } + return CompoundEventResult.pass(); + } + })); + } + @SafeVarargs public static Event> createConsumerLoop(T... typeGetter) { if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); @@ -187,21 +231,75 @@ public final class EventFactory { return event; } + @SafeVarargs + public static Event> createEventActorLoop(T... typeGetter) { + if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!"); + return createEventActorLoop((Class) typeGetter.getClass().getComponentType()); + } + + @SuppressWarnings("UnstableApiUsage") + public static Event> createEventActorLoop(Class clazz) { + Event> event = of(listeners -> (EventActor) Proxy.newProxyInstance(EventFactory.class.getClassLoader(), new Class[]{EventActor.class}, new AbstractInvocationHandler() { + @Override + protected Object handleInvocation(@NotNull Object proxy, @NotNull Method method, Object @NotNull [] args) throws Throwable { + for (EventActor listener : listeners) { + EventResult result = (EventResult) method.invoke(listener, args); + if (result.interruptsFurtherEvaluation()) { + return result; + } + } + return EventResult.pass(); + } + })); + Class superClass = clazz; + do { + + if (superClass.isAnnotationPresent(ForgeEventCancellable.class)) { + return attachToForgeEventActorCancellable(event); + } + superClass = superClass.getSuperclass(); + } while (superClass != null); + superClass = clazz; + do { + + if (superClass.isAnnotationPresent(ForgeEvent.class)) { + return attachToForgeEventActor(event); + } + superClass = superClass.getSuperclass(); + } while (superClass != null); + return event; + } + @ExpectPlatform + @ApiStatus.Internal public static Event> attachToForge(Event> event) { throw new AssertionError(); } @ExpectPlatform + @ApiStatus.Internal public static Event> attachToForgeActor(Event> event) { throw new AssertionError(); } @ExpectPlatform + @ApiStatus.Internal public static Event> attachToForgeActorCancellable(Event> event) { throw new AssertionError(); } + @ExpectPlatform + @ApiStatus.Internal + public static Event> attachToForgeEventActor(Event> event) { + throw new AssertionError(); + } + + @ExpectPlatform + @ApiStatus.Internal + public static Event> attachToForgeEventActorCancellable(Event> event) { + throw new AssertionError(); + } + private static class EventImpl implements Event { private final Function, T> function; private T invoker = null; diff --git a/common/src/main/java/me/shedaniel/architectury/event/EventResult.java b/common/src/main/java/me/shedaniel/architectury/event/EventResult.java new file mode 100644 index 00000000..39ca7bc3 --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/EventResult.java @@ -0,0 +1,85 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.event; + +/** + * A result from an event, determines if the event should continue to other listeners, + * and determines the outcome of the event. + * + * @see #pass() + * @see #interrupt(Boolean) + * @see CompoundEventResult + */ +public final class EventResult { + private static final EventResult TRUE = new EventResult(true, true); + private static final EventResult STOP = new EventResult(true, null); + private static final EventResult PASS = new EventResult(false, null); + private static final EventResult FALSE = new EventResult(true, false); + + /** + * Passes the event to other listeners, and does not set an outcome of the event. + * + * @return an event that passes the event to other listeners + */ + public static EventResult pass() { + return PASS; + } + + /** + * Interrupts the event and stops it from being passed on to other listeners, + * may or may not set an outcome of the event. + * + * @param value the outcome of the event, passing {@code null} here means the default outcome, + * which often means falling back to vanilla logic + * @return an event that interrupts the event + */ + public static EventResult interrupt(Boolean value) { + if (value == null) return STOP; + if (value) return TRUE; + return FALSE; + } + + private final boolean interruptsFurtherEvaluation; + + private final Boolean value; + + EventResult(boolean interruptsFurtherEvaluation, Boolean value) { + this.interruptsFurtherEvaluation = interruptsFurtherEvaluation; + this.value = value; + } + + /** + * Returns whether this result interrupts the evaluation of other listeners. + * + * @return whether this result interrupts the evaluation of other listeners + */ + public boolean interruptsFurtherEvaluation() { + return interruptsFurtherEvaluation; + } + + /** + * Returns the outcome of the result, an passing result will never have an outcome. + * + * @return the outcome of the result, returns {@code null} if fallback + */ + public Boolean value() { + return value; + } +} diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java index 27c91ccd..e1976465 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java +++ b/common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java @@ -21,12 +21,16 @@ package me.shedaniel.architectury.event.events; import me.shedaniel.architectury.event.Event; import me.shedaniel.architectury.event.EventFactory; +import me.shedaniel.architectury.event.EventResult; import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.BaseSpawner; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -40,10 +44,18 @@ public interface EntityEvent { * Invoked before LivingEntity#hurt, equivalent to forge's {@code LivingAttackEvent}. */ Event LIVING_ATTACK = EventFactory.createInteractionResult(); + /** + * Invoked when an entity is about to be spawned, equivalent to forge's {@code LivingSpawnEvent.CheckSpawn} + */ + Event LIVING_CHECK_SPAWN = EventFactory.createEventResult(); /** * Invoked before entity is added to a world, equivalent to forge's {@code EntityJoinWorldEvent}. */ Event ADD = EventFactory.createInteractionResult(); + /** + * Invoked when an entity enters a chunk, equivalent to forge's {@code EnteringChunk} + */ + Event ENTER_CHUNK = EventFactory.createLoop(); /** * @deprecated use {@link BlockEvent#PLACE} @@ -60,6 +72,10 @@ public interface EntityEvent { InteractionResult attack(LivingEntity entity, DamageSource source, float amount); } + interface LivingCheckSpawn { + EventResult canSpawn(LivingEntity entity, LevelAccessor world, double x, double y, double z, MobSpawnType type, @Nullable BaseSpawner spawner); + } + interface Add { InteractionResult add(Entity entity, Level world); } @@ -67,4 +83,9 @@ public interface EntityEvent { interface PlaceBlock { InteractionResult placeBlock(Level world, BlockPos pos, BlockState state, @Nullable Entity placer); } + + interface EnterChunk { + void enterChunk(Entity entity, int chunkX, int chunkZ, int prevX, int prevZ); + } + } diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java index 2c6ee6a9..8e62f002 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java +++ b/common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java @@ -21,6 +21,7 @@ package me.shedaniel.architectury.event.events; import me.shedaniel.architectury.event.Event; import me.shedaniel.architectury.event.EventFactory; +import me.shedaniel.architectury.event.EventResult; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.InteractionHand; @@ -29,6 +30,7 @@ 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.Level; import net.minecraft.world.level.block.state.BlockState; public interface InteractionEvent { @@ -38,6 +40,10 @@ public interface InteractionEvent { Event CLIENT_LEFT_CLICK_AIR = EventFactory.createLoop(); Event CLIENT_RIGHT_CLICK_AIR = EventFactory.createLoop(); Event INTERACT_ENTITY = EventFactory.createInteractionResult(); + /** + * Invoked before a farmland block is trampled by an entity, equivalent to forge's {@code BlockEvent.FarmlandTrampleEvent} + */ + Event FARMLAND_TRAMPLE = EventFactory.createEventResult(); interface RightClickBlock { InteractionResult click(Player player, InteractionHand hand, BlockPos pos, Direction face); @@ -66,4 +72,8 @@ public interface InteractionEvent { interface BlockBreak { InteractionResult breakBlock(Player player, BlockPos pos, BlockState state); } + + interface FarmlandTrample { + EventResult trample(Level world, BlockPos pos, BlockState state, float distance, Entity entity); + } } diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java index 5c821043..caa121a6 100644 --- a/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java +++ b/common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java @@ -19,6 +19,7 @@ package me.shedaniel.architectury.event.events; +import me.shedaniel.architectury.event.CompoundEventResult; import me.shedaniel.architectury.event.Event; import me.shedaniel.architectury.event.EventFactory; import me.shedaniel.architectury.utils.IntValue; @@ -34,6 +35,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.HitResult; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -51,6 +53,14 @@ public interface PlayerEvent { Event DROP_ITEM = EventFactory.createLoop(); Event OPEN_MENU = EventFactory.createLoop(); Event CLOSE_MENU = EventFactory.createLoop(); + /** + * Invoked when a player attempts to fill a bucket using right-click. + * You can return a non-PASS interaction result to cancel further processing by other mods. + *

+ * On Forge, FAIL cancels the event, and SUCCESS sets the event as handled. + * On Fabric, any non-PASS result is returned directly and immediately. + */ + Event FILL_BUCKET = EventFactory.createCompoundEventResult(); /** * @deprecated use {@link BlockEvent#BREAK} @@ -114,4 +124,8 @@ public interface PlayerEvent { interface CloseMenu { void close(Player player, AbstractContainerMenu menu); } + + interface FillBucket { + CompoundEventResult fill(Player player, Level level, ItemStack stack, @Nullable HitResult target); + } } diff --git a/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java b/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java index 96261c60..f2f18400 100644 --- a/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java +++ b/fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java @@ -21,6 +21,8 @@ package me.shedaniel.architectury.event.fabric; import me.shedaniel.architectury.event.Actor; import me.shedaniel.architectury.event.Event; +import me.shedaniel.architectury.event.EventActor; +import org.jetbrains.annotations.ApiStatus; import java.util.function.Consumer; @@ -36,4 +38,14 @@ public class EventFactoryImpl { public static Event> attachToForgeActorCancellable(Event> event) { return event; } + + @ApiStatus.Internal + public static Event> attachToForgeEventActor(Event> event) { + return event; + } + + @ApiStatus.Internal + public static Event> attachToForgeEventActorCancellable(Event> event) { + return event; + } } diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBaseSpawner.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBaseSpawner.java new file mode 100644 index 00000000..9dbe6640 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBaseSpawner.java @@ -0,0 +1,63 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.EventResult; +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.BaseSpawner; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(BaseSpawner.class) +public abstract class MixinBaseSpawner { + @Redirect( + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/Mob;checkSpawnRules(Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/world/entity/MobSpawnType;)Z", + ordinal = 0 + ) + ) + private boolean checkSpawnerSpawn(Mob mob, LevelAccessor level, MobSpawnType type) { + EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker() + .canSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), type, (BaseSpawner) (Object) this); + if (result.value() != null) { + return result.value(); + } + return mob.checkSpawnRules(level, type) && mob.checkSpawnObstruction(level); + } + + @Redirect( + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/Mob;checkSpawnObstruction(Lnet/minecraft/world/level/LevelReader;)Z", + ordinal = 0 + ) + ) + private boolean skipDoubleObstruction(Mob mob, LevelReader levelReader) { + return true; + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBucketItem.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBucketItem.java new file mode 100644 index 00000000..da207df4 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBucketItem.java @@ -0,0 +1,58 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.CompoundEventResult; +import me.shedaniel.architectury.event.events.PlayerEvent; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.HitResult; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(BucketItem.class) +public class MixinBucketItem { + + @Inject( + method = "use", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/phys/HitResult;getType()Lnet/minecraft/world/phys/HitResult$Type;", + ordinal = 0 + ), + locals = LocalCapture.CAPTURE_FAILHARD, + cancellable = true + ) + public void fillBucket(Level level, Player player, InteractionHand hand, CallbackInfoReturnable> cir, ItemStack stack, HitResult target) { + CompoundEventResult result = PlayerEvent.FILL_BUCKET.invoker().fill(player, level, stack, target); + if (result.interruptsFurtherEvaluation() && result.value() != null) { + cir.setReturnValue(result.asMinecraft()); + cir.cancel(); + } + } + +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCatSpawner.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCatSpawner.java new file mode 100644 index 00000000..debdd5dd --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCatSpawner.java @@ -0,0 +1,52 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.animal.Cat; +import net.minecraft.world.entity.npc.CatSpawner; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(CatSpawner.class) +public abstract class MixinCatSpawner { + @Inject( + method = "spawnCat", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/animal/Cat;finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/entity/SpawnGroupData;", + ordinal = 0 + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void checkCatSpawn(BlockPos pos, ServerLevel level, CallbackInfoReturnable cir, Cat entity) { + if (EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null).value() == Boolean.FALSE) { + cir.setReturnValue(0); + cir.cancel(); + } + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinFarmBlock.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinFarmBlock.java new file mode 100644 index 00000000..43a491e9 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinFarmBlock.java @@ -0,0 +1,65 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.InteractionEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.FarmBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.apache.commons.lang3.tuple.Triple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FarmBlock.class) +public abstract class MixinFarmBlock { + @Unique + private static ThreadLocal> turnToDirtLocal = new ThreadLocal<>(); + + @Inject( + method = "fallOn", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/FarmBlock;turnToDirt(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V" + ) + ) + private void fallOn(Level level, BlockPos blockPos, Entity entity, float f, CallbackInfo ci) { + turnToDirtLocal.set(Triple.of(blockPos.asLong(), f, entity)); + } + + @Inject(method = "turnToDirt", at = @At("HEAD"), cancellable = true) + private static void turnToDirt(BlockState state, Level level, BlockPos pos, CallbackInfo ci) { + Triple triple = turnToDirtLocal.get(); + turnToDirtLocal.remove(); + if (triple != null && triple.getLeft() == pos.asLong()) { + if (InteractionEvent.FARMLAND_TRAMPLE.invoker().trample(level, pos, state, triple.getMiddle(), triple.getRight()).value() != null) { + ci.cancel(); + } + } + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinLevelChunk.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinLevelChunk.java new file mode 100644 index 00000000..957d6917 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinLevelChunk.java @@ -0,0 +1,51 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +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; + +@Mixin(LevelChunk.class) +public class MixinLevelChunk { + @Shadow @Final private ChunkPos chunkPos; + + @Inject( + method = "addEntity", + at = @At( + value = "FIELD", + opcode = Opcodes.PUTFIELD, + target = "Lnet/minecraft/world/entity/Entity;inChunk:Z", + shift = At.Shift.BY, + by = -1 + ) + ) + public void enterChunk(Entity entity, CallbackInfo ci) { + EntityEvent.ENTER_CHUNK.invoker().enterChunk(entity, this.chunkPos.x, this.chunkPos.z, entity.xChunk, entity.zChunk); + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinNaturalSpawner.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinNaturalSpawner.java new file mode 100644 index 00000000..5827b953 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinNaturalSpawner.java @@ -0,0 +1,74 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.EventResult; +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.*; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.NaturalSpawner; +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.Redirect; + +@Mixin(NaturalSpawner.class) +public abstract class MixinNaturalSpawner { + @Shadow + private static boolean isValidPositionForMob(ServerLevel serverLevel, Mob mob, double d) { + return false; + } + + @Redirect( + method = "spawnCategoryForPosition", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/NaturalSpawner;isValidPositionForMob(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Mob;D)Z", + ordinal = 0 + ) + ) + private static boolean overrideNaturalSpawnCondition(ServerLevel level, Mob entity, double f) { + EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(entity, level, entity.xOld, entity.yOld, entity.zOld, MobSpawnType.NATURAL, null); + if (result.value() != null) { + return result.value(); + } else { + return isValidPositionForMob(level, entity, f); + } + } + + @Redirect( + method = "spawnMobsForChunkGeneration", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/Mob;checkSpawnRules(Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/world/entity/MobSpawnType;)Z", + ordinal = 0 + ) + ) + private static boolean overrideChunkGenSpawnCondition(Mob mob, LevelAccessor level, MobSpawnType type) { + EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(mob, level, mob.xOld, mob.yOld, mob.zOld, MobSpawnType.CHUNK_GENERATION, null); + if (result.value() != null) { + return result.value(); + } else { + return mob.checkSpawnRules(level, type); + } + } + +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPatrolSpawner.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPatrolSpawner.java new file mode 100644 index 00000000..9abee30b --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPatrolSpawner.java @@ -0,0 +1,58 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.EventResult; +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.monster.PatrollingMonster; +import net.minecraft.world.level.levelgen.PatrolSpawner; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Random; + +@Mixin(PatrolSpawner.class) +public abstract class MixinPatrolSpawner { + + @Inject( + method = "spawnPatrolMember", + at = @At( + value = "INVOKE_ASSIGN", + target = "Lnet/minecraft/world/entity/EntityType;create(Lnet/minecraft/world/level/Level;)Lnet/minecraft/world/entity/Entity;", + ordinal = 0, + shift = At.Shift.BY, + by = 2 + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void checkPatrolSpawn(ServerLevel level, BlockPos pos, Random r, boolean b, CallbackInfoReturnable cir, PatrollingMonster entity) { + EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.PATROL, null); + if (result.value() != null) { + cir.setReturnValue(result.value()); + } + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPhantomSpawner.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPhantomSpawner.java new file mode 100644 index 00000000..322b79e6 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPhantomSpawner.java @@ -0,0 +1,63 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.EntityEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.SpawnGroupData; +import net.minecraft.world.entity.monster.Phantom; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.levelgen.PhantomSpawner; +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; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Iterator; +import java.util.Random; + +@Mixin(PhantomSpawner.class) +public abstract class MixinPhantomSpawner { + + @Inject( + method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/monster/Phantom;finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/entity/SpawnGroupData;", + ordinal = 0, + shift = At.Shift.BEFORE + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILSOFT // SOFT, because this will break in 2 seconds + ) + private void checkPhantomSpawn(ServerLevel level, boolean bl, boolean bl2, CallbackInfoReturnable cir, + Random random, int i, Iterator it, Player player, BlockPos pos, DifficultyInstance diff, BlockPos pos2, + SpawnGroupData sgd, int l, int m, Phantom entity) { + if (EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null).value() == Boolean.FALSE) { + cir.setReturnValue(0); + cir.cancel(); + } + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index 1807f492..3859d93a 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -1,31 +1,58 @@ { - "required": true, - "package": "me.shedaniel.architectury.mixin.fabric", - "plugin": "me.shedaniel.architectury.plugin.fabric.ArchitecturyMixinPlugin", - "compatibilityLevel": "JAVA_8", - "minVersion": "0.7.11", - "client": [ - "client.MixinClientLevel", - "client.MixinClientPacketListener", - "client.MixinDebugScreenOverlay", - "client.MixinEffectInstance", - "client.MixinGameRenderer", - "client.MixinIntegratedServer", - "client.MixinKeyboardHandler", - "client.MixinMinecraft", - "client.MixinMouseHandler", - "client.MixinMultiPlayerGameMode", - "client.MixinScreen", - "client.MixinTextureAtlas" - ], - "mixins": [ - "ExplosionPreInvoker", "LivingDeathInvoker", "MixinBlockEntityExtension", "MixinBlockItem", "MixinCollisionContext", "MixinCommands", - "MixinDedicatedServer", "MixinEntityCollisionContext", "MixinExplosion", "MixinFurnaceResultSlot", "MixinInventory", "MixinItemEntity", - "MixinLivingEntity", "MixinMob", "MixinPlayer", "MixinPlayerAdvancements", "MixinPlayerList", "MixinResultSlot", "MixinServerGamePacketListenerImpl", - "MixinServerLevel", "MixinServerPlayer", "MixinServerPlayerGameMode", "PlayerAttackInvoker" - ], - "injectors": { - "maxShiftBy": 5, - "defaultRequire": 1 - } + "required": true, + "package": "me.shedaniel.architectury.mixin.fabric", + "plugin": "me.shedaniel.architectury.plugin.fabric.ArchitecturyMixinPlugin", + "compatibilityLevel": "JAVA_8", + "minVersion": "0.7.11", + "client": [ + "client.MixinClientLevel", + "client.MixinClientPacketListener", + "client.MixinDebugScreenOverlay", + "client.MixinEffectInstance", + "client.MixinGameRenderer", + "client.MixinIntegratedServer", + "client.MixinKeyboardHandler", + "client.MixinMinecraft", + "client.MixinMouseHandler", + "client.MixinMultiPlayerGameMode", + "client.MixinScreen", + "client.MixinTextureAtlas" + ], + "mixins": [ + "ExplosionPreInvoker", + "LivingDeathInvoker", + "MixinBaseSpawner", + "MixinBlockEntityExtension", + "MixinBlockItem", + "MixinBucketItem", + "MixinCatSpawner", + "MixinCollisionContext", + "MixinCommands", + "MixinDedicatedServer", + "MixinEntityCollisionContext", + "MixinExplosion", + "MixinFarmBlock", + "MixinFurnaceResultSlot", + "MixinInventory", + "MixinItemEntity", + "MixinLevelChunk", + "MixinLivingEntity", + "MixinMob", + "MixinNaturalSpawner", + "MixinPatrolSpawner", + "MixinPhantomSpawner", + "MixinPlayer", + "MixinPlayerAdvancements", + "MixinPlayerList", + "MixinResultSlot", + "MixinServerGamePacketListenerImpl", + "MixinServerLevel", + "MixinServerPlayer", + "MixinServerPlayerGameMode", + "PlayerAttackInvoker" + ], + "injectors": { + "maxShiftBy": 5, + "defaultRequire": 1 + } } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 13ff4d36..7970a749 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -7,6 +7,11 @@ "authors": [ "shedaniel" ], + "contact": { + "issues": "https://github.com/architectury/architectury-api/issues", + "sources": "https://github.com/architectury/architectury-api", + "homepage": "https://architectury.github.io/architectury-documentations/" + }, "license": "LGPL-3", "environment": "*", "mixins": [ @@ -25,6 +30,7 @@ ] }, "accessWidener": "architectury.accessWidener", + "icon": "icon.png", "depends": { "minecraft": ">=1.16.4" }, diff --git a/fabric/src/main/resources/icon.png b/fabric/src/main/resources/icon.png new file mode 100644 index 00000000..812d0b57 Binary files /dev/null and b/fabric/src/main/resources/icon.png differ diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java index 69ab8929..5e397894 100644 --- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java +++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java @@ -21,8 +21,11 @@ package me.shedaniel.architectury.event.forge; import me.shedaniel.architectury.event.Actor; import me.shedaniel.architectury.event.Event; +import me.shedaniel.architectury.event.EventActor; +import me.shedaniel.architectury.event.EventResult; import net.minecraft.world.InteractionResult; import net.minecraftforge.common.MinecraftForge; +import org.jetbrains.annotations.ApiStatus; import java.util.function.Consumer; @@ -63,4 +66,36 @@ public class EventFactoryImpl { }); return event; } -} \ No newline at end of file + + @ApiStatus.Internal + public static Event> attachToForgeEventActor(Event> event) { + event.register(eventObj -> { + if (!(eventObj instanceof net.minecraftforge.eventbus.api.Event)) { + throw new ClassCastException(eventObj.getClass() + " is not an instance of forge Event!"); + } + if (!((net.minecraftforge.eventbus.api.Event) eventObj).isCancelable()) { + throw new ClassCastException(eventObj.getClass() + " is not cancellable Event!"); + } + MinecraftForge.EVENT_BUS.post((net.minecraftforge.eventbus.api.Event) eventObj); + return EventResult.pass(); + }); + return event; + } + + @ApiStatus.Internal + public static Event> attachToForgeEventActorCancellable(Event> event) { + event.register(eventObj -> { + if (!(eventObj instanceof net.minecraftforge.eventbus.api.Event)) { + throw new ClassCastException(eventObj.getClass() + " is not an instance of forge Event!"); + } + if (!((net.minecraftforge.eventbus.api.Event) eventObj).isCancelable()) { + throw new ClassCastException(eventObj.getClass() + " is not cancellable Event!"); + } + if (MinecraftForge.EVENT_BUS.post((net.minecraftforge.eventbus.api.Event) eventObj)) { + return EventResult.interrupt(false); + } + return EventResult.pass(); + }); + return event; + } +} diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java index 26d2338c..057476b6 100644 --- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java +++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java @@ -19,6 +19,9 @@ package me.shedaniel.architectury.event.forge; +import me.shedaniel.architectury.event.CompoundEventResult; +import me.shedaniel.architectury.event.EventResult; +import me.shedaniel.architectury.event.events.PlayerEvent; import me.shedaniel.architectury.event.events.*; import me.shedaniel.architectury.utils.IntValue; import net.minecraft.network.chat.Component; @@ -35,17 +38,17 @@ import net.minecraftforge.event.TickEvent.Phase; import net.minecraftforge.event.TickEvent.PlayerTickEvent; import net.minecraftforge.event.TickEvent.ServerTickEvent; import net.minecraftforge.event.TickEvent.WorldTickEvent; +import net.minecraftforge.event.entity.EntityEvent.EnteringChunk; 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.PlayerContainerEvent; +import net.minecraftforge.event.entity.living.LivingSpawnEvent; +import net.minecraftforge.event.entity.player.*; import net.minecraftforge.event.entity.player.PlayerEvent.*; -import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.world.BlockEvent.BreakEvent; import net.minecraftforge.event.world.BlockEvent.EntityPlaceEvent; +import net.minecraftforge.event.world.BlockEvent.FarmlandTrampleEvent; import net.minecraftforge.event.world.ExplosionEvent.Detonate; import net.minecraftforge.event.world.ExplosionEvent.Start; import net.minecraftforge.event.world.WorldEvent; @@ -214,6 +217,43 @@ public class EventHandlerImplCommon { } } + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(FarmlandTrampleEvent event) { + if (InteractionEvent.FARMLAND_TRAMPLE.invoker().trample((Level) event.getWorld(), event.getPos(), event.getState(), event.getFallDistance(), event.getEntity()).value() != null) { + event.setCanceled(true); + } + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(FillBucketEvent event) { + ItemStack oldItem = event.getEmptyBucket(); + CompoundEventResult result = PlayerEvent.FILL_BUCKET.invoker().fill(event.getPlayer(), event.getWorld(), oldItem, event.getTarget()); + if (result.interruptsFurtherEvaluation()) { + event.setCanceled(true); + event.setFilledBucket(result.object()); + + if (result.value() != null) { + event.setResult(result.value() ? Event.Result.ALLOW : Event.Result.DENY); + } + } + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(EnteringChunk event) { + EntityEvent.ENTER_CHUNK.invoker().enterChunk(event.getEntity(), event.getNewChunkX(), event.getNewChunkZ(), event.getOldChunkX(), event.getOldChunkZ()); + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(LivingSpawnEvent.CheckSpawn event) { + EventResult result = EntityEvent.LIVING_CHECK_SPAWN.invoker().canSpawn(event.getEntityLiving(), event.getWorld(), event.getX(), event.getY(), event.getZ(), event.getSpawnReason(), event.getSpawner()); + if (result.interruptsFurtherEvaluation()) { + if (result.value() != null) { + event.setResult(result.value() == Boolean.TRUE ? Event.Result.ALLOW : Event.Result.DENY); + } + event.setCanceled(true); + } + } + @SubscribeEvent(priority = EventPriority.HIGH) public static void event(ItemCraftedEvent event) { PlayerEvent.CRAFT_ITEM.invoker().craft(event.getPlayer(), event.getCrafting(), event.getInventory()); diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml index 3d9b282d..09091f4b 100644 --- a/forge/src/main/resources/META-INF/mods.toml +++ b/forge/src/main/resources/META-INF/mods.toml @@ -1,7 +1,7 @@ modLoader = "javafml" loaderVersion = "[33,)" issueTrackerURL = "https://github.com/shedaniel/architectury/issues" -license = "LGPL-3" +license = "GNU LGPLv3" [[mods]] modId = "architectury" @@ -11,4 +11,12 @@ authors = "shedaniel" description = ''' A intermediary api aimed to ease developing multiplatform mods. ''' -license = "LGPL-3" \ No newline at end of file +logoFile="icon.png" +license = "LGPL-3" + +[[dependencies.architectury]] +modId = "minecraft" +mandatory = true +versionRange = "[1.16.2,)" +ordering = "NONE" +side = "BOTH" diff --git a/forge/src/main/resources/icon.png b/forge/src/main/resources/icon.png new file mode 100644 index 00000000..812d0b57 Binary files /dev/null and b/forge/src/main/resources/icon.png differ diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java index 4633f812..748d8b55 100644 --- a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java +++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java @@ -20,6 +20,8 @@ package me.shedaniel.architectury.test.events; import com.mojang.blaze3d.platform.InputConstants; +import me.shedaniel.architectury.event.CompoundEventResult; +import me.shedaniel.architectury.event.EventResult; import me.shedaniel.architectury.event.events.*; import me.shedaniel.architectury.event.events.client.*; import me.shedaniel.architectury.hooks.ExplosionHooks; @@ -28,15 +30,20 @@ import me.shedaniel.architectury.utils.Env; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.screens.ChatScreen; +import net.minecraft.client.gui.screens.inventory.AnvilScreen; import net.minecraft.core.Position; import net.minecraft.core.Vec3i; +import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TranslatableComponent; 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.EquipmentSlot; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import java.util.Optional; @@ -87,6 +94,34 @@ public class DebugEvents { } return InteractionResult.PASS; }); + EntityEvent.ENTER_CHUNK.register(((entity, nx, nz, ox, oz) -> { + if (entity instanceof Player && entity.inChunk) { + Player player = (Player) entity; + SINK.accept("%s switched chunks: %s => %s", entity.getScoreboardName(), chunkPos(ox, oz), chunkPos(nx, nz)); + player.displayClientMessage(new TextComponent("Entering chunk: " + chunkPos(nx, nz)), true); + } + })); + EntityEvent.LIVING_CHECK_SPAWN.register(((entity, level, x, y, z, type, spawner) -> { + StringBuilder sb = new StringBuilder(); + sb.append(entity.getType()); + sb.append(" is trying to spawn"); + sb.append(" at "); + sb.append(toShortString(new Vec3(x, y, z))); + if (level instanceof Level) { + sb.append(" in world "); + sb.append(((Level) level).dimension().location()); + } + sb.append(" from cause "); + sb.append(type.name()); + if (spawner != null) { + sb.append(" ("); + sb.append(spawner); + sb.append(") "); + } + + SINK.accept(sb.toString()); + return EventResult.pass(); + })); ExplosionEvent.DETONATE.register((world, explosion, affectedEntities) -> { SINK.accept(world.dimension().location() + " explodes at " + toShortString(ExplosionHooks.getPosition(explosion)) + logSide(world)); }); @@ -106,6 +141,13 @@ public class DebugEvents { SINK.accept(player.getScoreboardName() + " interacts with " + entity.getScoreboardName() + " using " + (hand == InteractionHand.MAIN_HAND ? "main hand" : "off hand") + logSide(player.level)); return InteractionResult.PASS; }); + InteractionEvent.FARMLAND_TRAMPLE.register((level, pos, state, distance, entity) -> { + if (entity instanceof Player && ((Player) entity).getItemBySlot(EquipmentSlot.FEET).getItem() == Items.DIAMOND_BOOTS) { + return EventResult.interrupt(false); + } + SINK.accept("%s trampled farmland (%s) at %s in %s (Fall height: %f blocks)", entity, state, pos, level, distance); + return EventResult.pass(); + }); LifecycleEvent.SERVER_BEFORE_START.register(instance -> { SINK.accept("Server ready to start"); }); @@ -169,6 +211,10 @@ public class DebugEvents { PlayerEvent.CHANGE_DIMENSION.register((player, oldLevel, newLevel) -> { SINK.accept(player.getScoreboardName() + " switched from " + oldLevel.location() + " to " + newLevel.location() + logSide(player.level)); }); + PlayerEvent.FILL_BUCKET.register(((player, level, stack, target) -> { + SINK.accept("%s used a bucket (%s) in %s%s while looking at %s", player.getScoreboardName(), stack, level.dimension().location(), logSide(level), target == null ? "nothing" : target.getLocation()); + return CompoundEventResult.pass(); + })); LightningEvent.STRIKE.register((bolt, level, pos, toStrike) -> { SINK.accept(bolt.getScoreboardName() + " struck at " + toShortString(pos) + logSide(level)); }); @@ -269,7 +315,7 @@ public class DebugEvents { return InteractionResult.PASS; }); GuiEvent.SET_SCREEN.register(screen -> { - if (screen instanceof ChatScreen) { + if (screen instanceof AnvilScreen) { return InteractionResultHolder.fail(screen); } @@ -278,6 +324,10 @@ public class DebugEvents { }); } + private static String chunkPos(int x, int z) { + return "[" + x + ", " + z + "]"; + } + private static String toSimpleName(Object o) { return o == null ? "null" : o.getClass().getSimpleName() + "@" + Integer.toHexString(o.hashCode()); }