Jelajahi Sumber

Some more (mostly Entity-based) Events (#75)

* Add FarmlandTrample event

* FarmlandTrample debug event

* Add FILL_BUCKET event and testmod

* Add ENTER_CHUNK event and testmod

* Add CHECK_SPAWN on Forge

* CHECK_SPAWN on Fabric part 1 aka: The Concernening

* CHECK_SPAWN on Fabric part 2: I kinda don't hate this as much

* CHECK_SPAWN on Fabric part 3: Patrols

* CHECK_SPAWN on Fabric part 4: catJAM

* CHECK_SPAWN on Fabric part 5: Phantoms

* Fix CHECK_SPAWN for patrols

* Add mod metadata (#73)

* new event system

* Revert patrol spawner behaviour

* Implement CheckSpawn behaviour for spawners and add test (forgot about that)

* Revert "Revert patrol spawner behaviour"

This reverts commit 1da3fb73

* Change MixinPhantomSpawner to SOFT

* Edit forge mods.toml
We support 1.16.2+ on forge

Co-authored-by: shedaniel <daniel@shedaniel.me>
Max 4 tahun lalu
induk
melakukan
ca2a528160
24 mengubah file dengan 1066 tambahan dan 37 penghapusan
  1. 114 0
      common/src/main/java/me/shedaniel/architectury/event/CompoundEventResult.java
  2. 25 0
      common/src/main/java/me/shedaniel/architectury/event/EventActor.java
  3. 98 0
      common/src/main/java/me/shedaniel/architectury/event/EventFactory.java
  4. 85 0
      common/src/main/java/me/shedaniel/architectury/event/EventResult.java
  5. 21 0
      common/src/main/java/me/shedaniel/architectury/event/events/EntityEvent.java
  6. 10 0
      common/src/main/java/me/shedaniel/architectury/event/events/InteractionEvent.java
  7. 14 0
      common/src/main/java/me/shedaniel/architectury/event/events/PlayerEvent.java
  8. 12 0
      fabric/src/main/java/me/shedaniel/architectury/event/fabric/EventFactoryImpl.java
  9. 63 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBaseSpawner.java
  10. 58 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinBucketItem.java
  11. 52 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinCatSpawner.java
  12. 65 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinFarmBlock.java
  13. 51 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinLevelChunk.java
  14. 74 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinNaturalSpawner.java
  15. 58 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPatrolSpawner.java
  16. 63 0
      fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinPhantomSpawner.java
  17. 56 29
      fabric/src/main/resources/architectury.mixins.json
  18. 6 0
      fabric/src/main/resources/fabric.mod.json
  19. TEMPAT SAMPAH
      fabric/src/main/resources/icon.png
  20. 36 1
      forge/src/main/java/me/shedaniel/architectury/event/forge/EventFactoryImpl.java
  21. 44 4
      forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java
  22. 10 2
      forge/src/main/resources/META-INF/mods.toml
  23. TEMPAT SAMPAH
      forge/src/main/resources/icon.png
  24. 51 1
      testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java

+ 114 - 0
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 <T> the type of the extra result
+ * @see #pass()
+ * @see #interrupt(Boolean, Object)
+ * @see CompoundEventResult
+ */
+public class CompoundEventResult<T> {
+    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 <T> the type of the extra result
+     * @return an event that passes the event to other listeners
+     */
+    public static <T> CompoundEventResult<T> pass() {
+        return (CompoundEventResult<T>) 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 <T> CompoundEventResult<T> 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<T> asMinecraft() {
+        if (value() != null) {
+            return value() ? InteractionResultHolder.success(object()) : InteractionResultHolder.fail(object());
+        }
+        return InteractionResultHolder.pass(object());
+    }
+}

+ 25 - 0
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<T> {
+    EventResult act(T t);
+}

+ 98 - 0
common/src/main/java/me/shedaniel/architectury/event/EventFactory.java

@@ -99,6 +99,28 @@ public final class EventFactory {
         }));
     }
     
+    @SafeVarargs
+    public static <T> Event<T> createEventResult(T... typeGetter) {
+        if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!");
+        return createEventResult((Class<T>) typeGetter.getClass().getComponentType());
+    }
+    
+    @SuppressWarnings("UnstableApiUsage")
+    public static <T> Event<T> createEventResult(Class<T> 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 <T> Event<T> 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 <T> Event<T> createCompoundEventResult(T... typeGetter) {
+        if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!");
+        return createCompoundEventResult((Class<T>) typeGetter.getClass().getComponentType());
+    }
+    
+    @SuppressWarnings("UnstableApiUsage")
+    public static <T> Event<T> createCompoundEventResult(Class<T> 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 <T> Event<Consumer<T>> 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 <T> Event<EventActor<T>> createEventActorLoop(T... typeGetter) {
+        if (typeGetter.length != 0) throw new IllegalStateException("array must be empty!");
+        return createEventActorLoop((Class<T>) typeGetter.getClass().getComponentType());
+    }
+    
+    @SuppressWarnings("UnstableApiUsage")
+    public static <T> Event<EventActor<T>> createEventActorLoop(Class<T> clazz) {
+        Event<EventActor<T>> event = of(listeners -> (EventActor<T>) 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<T> 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 <T> Event<Consumer<T>> attachToForge(Event<Consumer<T>> event) {
         throw new AssertionError();
     }
     
     @ExpectPlatform
+    @ApiStatus.Internal
     public static <T> Event<Actor<T>> attachToForgeActor(Event<Actor<T>> event) {
         throw new AssertionError();
     }
     
     @ExpectPlatform
+    @ApiStatus.Internal
     public static <T> Event<Actor<T>> attachToForgeActorCancellable(Event<Actor<T>> event) {
         throw new AssertionError();
     }
     
+    @ExpectPlatform
+    @ApiStatus.Internal
+    public static <T> Event<EventActor<T>> attachToForgeEventActor(Event<EventActor<T>> event) {
+        throw new AssertionError();
+    }
+    
+    @ExpectPlatform
+    @ApiStatus.Internal
+    public static <T> Event<EventActor<T>> attachToForgeEventActorCancellable(Event<EventActor<T>> event) {
+        throw new AssertionError();
+    }
+    
     private static class EventImpl<T> implements Event<T> {
         private final Function<List<T>, T> function;
         private T invoker = null;

+ 85 - 0
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;
+    }
+}

+ 21 - 0
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<LivingAttack> LIVING_ATTACK = EventFactory.createInteractionResult();
+    /**
+     * Invoked when an entity is about to be spawned, equivalent to forge's {@code LivingSpawnEvent.CheckSpawn}
+     */
+    Event<LivingCheckSpawn> LIVING_CHECK_SPAWN = EventFactory.createEventResult();
     /**
      * Invoked before entity is added to a world, equivalent to forge's {@code EntityJoinWorldEvent}.
      */
     Event<Add> ADD = EventFactory.createInteractionResult();
+    /**
+     * Invoked when an entity enters a chunk,  equivalent to forge's {@code EnteringChunk}
+     */
+    Event<EnterChunk> 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);
+    }
+    
 }

+ 10 - 0
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<ClientLeftClickAir> CLIENT_LEFT_CLICK_AIR = EventFactory.createLoop();
     Event<ClientRightClickAir> CLIENT_RIGHT_CLICK_AIR = EventFactory.createLoop();
     Event<InteractEntity> INTERACT_ENTITY = EventFactory.createInteractionResult();
+    /**
+     * Invoked before a farmland block is trampled by an entity, equivalent to forge's {@code BlockEvent.FarmlandTrampleEvent}
+     */
+    Event<FarmlandTrample> 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);
+    }
 }

+ 14 - 0
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<DropItem> DROP_ITEM = EventFactory.createLoop();
     Event<OpenMenu> OPEN_MENU = EventFactory.createLoop();
     Event<CloseMenu> 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.
+     * <p>
+     * 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<FillBucket> 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<ItemStack> fill(Player player, Level level, ItemStack stack, @Nullable HitResult target);
+    }
 }

+ 12 - 0
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 <T> Event<Actor<T>> attachToForgeActorCancellable(Event<Actor<T>> event) {
         return event;
     }
+    
+    @ApiStatus.Internal
+    public static <T> Event<EventActor<T>> attachToForgeEventActor(Event<EventActor<T>> event) {
+        return event;
+    }
+    
+    @ApiStatus.Internal
+    public static <T> Event<EventActor<T>> attachToForgeEventActorCancellable(Event<EventActor<T>> event) {
+        return event;
+    }
 }

+ 63 - 0
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;
+    }
+}

+ 58 - 0
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<InteractionResultHolder<ItemStack>> cir, ItemStack stack, HitResult target) {
+        CompoundEventResult<ItemStack> result = PlayerEvent.FILL_BUCKET.invoker().fill(player, level, stack, target);
+        if (result.interruptsFurtherEvaluation() && result.value() != null) {
+            cir.setReturnValue(result.asMinecraft());
+            cir.cancel();
+        }
+    }
+    
+}

+ 52 - 0
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<Integer> 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();
+        }
+    }
+}

+ 65 - 0
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<Triple<Long, Float, Entity>> 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<Long, Float, Entity> 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();
+            }
+        }
+    }
+}

+ 51 - 0
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);
+    }
+}

+ 74 - 0
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);
+        }
+    }
+    
+}

+ 58 - 0
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<Boolean> 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());
+        }
+    }
+}

+ 63 - 0
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<Integer> cir,
+            Random random, int i, Iterator<ServerPlayer> 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();
+        }
+    }
+}

+ 56 - 29
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
+  }
 }

+ 6 - 0
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"
   },

TEMPAT SAMPAH
fabric/src/main/resources/icon.png


+ 36 - 1
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;
     }
-}
+    
+    @ApiStatus.Internal
+    public static <T> Event<EventActor<T>> attachToForgeEventActor(Event<EventActor<T>> 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 <T> Event<EventActor<T>> attachToForgeEventActorCancellable(Event<EventActor<T>> 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;
+    }
+}

+ 44 - 4
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<ItemStack> 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());

+ 10 - 2
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"
+logoFile="icon.png"
+license = "LGPL-3"
+
+[[dependencies.architectury]]
+modId = "minecraft"
+mandatory = true
+versionRange = "[1.16.2,)"
+ordering = "NONE"
+side = "BOTH"

TEMPAT SAMPAH
forge/src/main/resources/icon.png


+ 51 - 1
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());
     }