new event system

This commit is contained in:
shedaniel
2021-04-15 19:26:07 +08:00
parent bc9367b1b9
commit 569075ccc7
17 changed files with 450 additions and 101 deletions

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
@@ -49,13 +50,13 @@ public interface EntityEvent {
*/
Event<Add> ADD = EventFactory.createInteractionResult();
/**
* Invoked when an entity enters a chunk.
* Invoked when an entity enters a chunk, equivalent to forge's {@code EnteringChunk}
*/
Event<EnterChunk> ENTER_CHUNK = EventFactory.createLoop();
/**
* Invoked when an entity is about to be spawned, equivalent to forge's {@code LivingSpawnEvent.CheckSpawn}
*/
Event<CheckSpawn> CHECK_SPAWN = EventFactory.createInteractionResult();
Event<CheckSpawn> CHECK_SPAWN = EventFactory.createEventResult();
/**
* @deprecated use {@link BlockEvent#PLACE}
@@ -85,6 +86,6 @@ public interface EntityEvent {
}
interface CheckSpawn {
InteractionResult canSpawn(Entity entity, LevelAccessor world, double x, double y, double z, MobSpawnType type, @Nullable BaseSpawner spawner);
EventResult canSpawn(Entity entity, LevelAccessor world, double x, double y, double z, MobSpawnType type, @Nullable BaseSpawner spawner);
}
}

View File

@@ -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;
@@ -42,7 +43,7 @@ public interface InteractionEvent {
/**
* Invoked before a farmland block is trampled by an entity, equivalent to forge's {@code BlockEvent.FarmlandTrampleEvent}
*/
Event<FarmlandTrample> FARMLAND_TRAMPLE = EventFactory.createInteractionResult();
Event<FarmlandTrample> FARMLAND_TRAMPLE = EventFactory.createEventResult();
interface RightClickBlock {
InteractionResult click(Player player, InteractionHand hand, BlockPos pos, Direction face);
@@ -73,6 +74,6 @@ public interface InteractionEvent {
}
interface FarmlandTrample {
InteractionResult trample(Level world, BlockPos pos, BlockState state, float distance, Entity entity);
EventResult trample(Level world, BlockPos pos, BlockState state, float distance, Entity entity);
}
}

View File

@@ -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;
@@ -28,14 +29,12 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
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.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -57,11 +56,11 @@ public interface PlayerEvent {
/**
* 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.createInteractionResultHolder();
Event<FillBucket> FILL_BUCKET = EventFactory.createCompoundEventResult();
/**
* @deprecated use {@link BlockEvent#BREAK}
@@ -127,6 +126,6 @@ public interface PlayerEvent {
}
interface FillBucket {
InteractionResultHolder<ItemStack> fill(Player player, Level level, ItemStack stack, @Nullable HitResult target);
CompoundEventResult<ItemStack> fill(Player player, Level level, ItemStack stack, @Nullable HitResult target);
}
}

View File

@@ -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;
}
}

View File

@@ -19,9 +19,9 @@
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.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BucketItem;
@@ -48,9 +48,9 @@ public class MixinBucketItem {
cancellable = true
)
public void fillBucket(Level level, Player player, InteractionHand hand, CallbackInfoReturnable<InteractionResultHolder<ItemStack>> cir, ItemStack stack, HitResult target) {
InteractionResultHolder<ItemStack> event = PlayerEvent.FILL_BUCKET.invoker().fill(player, level, stack, target);
if (event.getResult() != InteractionResult.PASS) {
cir.setReturnValue(event);
CompoundEventResult<ItemStack> result = PlayerEvent.FILL_BUCKET.invoker().fill(player, level, stack, target);
if (result.interruptsFurtherEvaluation() && result.value() != null) {
cir.setReturnValue(result.asMinecraft());
cir.cancel();
}
}

View File

@@ -16,6 +16,7 @@
* 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;
@@ -38,7 +39,6 @@ import java.util.Random;
@Mixin(CatSpawner.class)
public abstract class MixinCatSpawner {
@Inject(
method = "spawnCat",
at = @At(
@@ -50,7 +50,7 @@ public abstract class MixinCatSpawner {
locals = LocalCapture.CAPTURE_FAILHARD
)
private void checkCatSpawn(BlockPos pos, ServerLevel level, CallbackInfoReturnable<Integer> cir, Cat entity) {
if (EntityEvent.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null) == InteractionResult.FAIL) {
if (EntityEvent.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null).value() == Boolean.FALSE) {
cir.setReturnValue(0);
cir.cancel();
}

View File

@@ -21,33 +21,45 @@ 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<>();
@Shadow
public static void turnToDirt(BlockState blockState, Level level, BlockPos blockPos) {
}
@Redirect(
@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 redirectTurnToDirt(BlockState state, Level level, BlockPos pos, Level l2, BlockPos pos2, Entity entity, float f) {
if (InteractionEvent.FARMLAND_TRAMPLE.invoker().trample(level, pos, state, f, entity) == InteractionResult.PASS) {
turnToDirt(state, level, pos);
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();
}
}
}
}

View File

@@ -16,8 +16,10 @@
* 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;
@@ -49,38 +51,11 @@ import java.util.Random;
@Mixin(NaturalSpawner.class)
public abstract class MixinNaturalSpawner {
@Unique
private static int arch$naturalSpawnOverride = 0;
@Unique
private static boolean arch$skipChunkGenSpawn = false;
@Shadow
private static boolean isValidPositionForMob(ServerLevel serverLevel, Mob mob, double d) {
return false;
}
@Inject(
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
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private static void checkNaturalSpawn(MobCategory cat, ServerLevel level, ChunkAccess ca, BlockPos pos, SpawnPredicate pred, AfterSpawnCallback cb, CallbackInfo ci,
StructureFeatureManager sfm, ChunkGenerator cg, int i, BlockPos.MutableBlockPos mutablePos, int j, int k, int l, int m, int n,
SpawnerData spawnerData, SpawnGroupData sgd, int o, int p, int q, double d, double e, Player player, double f, Mob entity) {
InteractionResult result = EntityEvent.CHECK_SPAWN.invoker()
.canSpawn(entity, level, d, i, e, MobSpawnType.NATURAL, null);
if (result == InteractionResult.FAIL) {
arch$naturalSpawnOverride = -1;
} else if (result == InteractionResult.SUCCESS || result == InteractionResult.CONSUME) {
arch$naturalSpawnOverride = 1;
}
}
@Redirect(
method = "spawnCategoryForPosition",
at = @At(
@@ -90,23 +65,12 @@ public abstract class MixinNaturalSpawner {
)
)
private static boolean overrideNaturalSpawnCondition(ServerLevel level, Mob entity, double f) {
return arch$naturalSpawnOverride != -1 && (arch$naturalSpawnOverride == 1 || isValidPositionForMob(level, entity, f));
}
@Inject(
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
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private static void checkChunkGenSpawn(ServerLevelAccessor level, Biome biome, int cx, int cz, Random dx, CallbackInfo ci,
MobSpawnSettings settings, List<SpawnerData> list, int k, int l, SpawnerData spawnerData, int m, SpawnGroupData sgd,
int n, int o, int p, int q, int r, boolean b, int s, BlockPos pos, double d, double e, Entity entity) {
arch$skipChunkGenSpawn = EntityEvent.CHECK_SPAWN.invoker()
.canSpawn(entity, level, d, pos.getY(), e, MobSpawnType.CHUNK_GENERATION, null) == InteractionResult.FAIL;
EventResult result = EntityEvent.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(
@@ -118,7 +82,12 @@ public abstract class MixinNaturalSpawner {
)
)
private static boolean overrideChunkGenSpawnCondition(Mob mob, LevelAccessor level, MobSpawnType type) {
return !arch$skipChunkGenSpawn && mob.checkSpawnRules(level, type);
EventResult result = EntityEvent.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);
}
}
}

View File

@@ -16,8 +16,10 @@
* 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;
@@ -50,9 +52,9 @@ public abstract class MixinPatrolSpawner {
locals = LocalCapture.CAPTURE_FAILHARD
)
private void checkPatrolSpawn(ServerLevel level, BlockPos pos, Random r, boolean b, CallbackInfoReturnable<Boolean> cir, PatrollingMonster entity) {
if (EntityEvent.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.PATROL, null) == InteractionResult.FAIL) {
cir.setReturnValue(false);
cir.cancel();
EventResult result = EntityEvent.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.PATROL, null);
if (result.value() != null) {
cir.setReturnValue(result.value());
}
}
}

View File

@@ -16,6 +16,7 @@
* 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;
@@ -55,7 +56,7 @@ public abstract class MixinPhantomSpawner {
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.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null) == InteractionResult.FAIL) {
if (EntityEvent.CHECK_SPAWN.invoker().canSpawn(entity, level, pos.getX(), pos.getY(), pos.getZ(), MobSpawnType.NATURAL, null).value() == Boolean.FALSE) {
cir.setReturnValue(0);
cir.cancel();
}

View File

@@ -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;
}
}

View File

@@ -19,6 +19,8 @@
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;
@@ -217,7 +219,7 @@ 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()) == InteractionResult.FAIL) {
if (InteractionEvent.FARMLAND_TRAMPLE.invoker().trample((Level) event.getWorld(), event.getPos(), event.getState(), event.getFallDistance(), event.getEntity()).value() != null) {
event.setCanceled(true);
}
}
@@ -225,18 +227,14 @@ public class EventHandlerImplCommon {
@SubscribeEvent(priority = EventPriority.HIGH)
public static void event(FillBucketEvent event) {
ItemStack oldItem = event.getEmptyBucket();
InteractionResultHolder<ItemStack> result = PlayerEvent.FILL_BUCKET.invoker().fill(event.getPlayer(), event.getWorld(), oldItem, event.getTarget());
switch (result.getResult()) {
case FAIL:
event.setCanceled(true);
break;
case SUCCESS:
case CONSUME:
event.setResult(Event.Result.ALLOW);
event.setFilledBucket(result.getObject());
break;
case PASS:
break;
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);
}
}
}
@@ -247,17 +245,12 @@ public class EventHandlerImplCommon {
@SubscribeEvent(priority = EventPriority.HIGH)
public static void event(LivingSpawnEvent.CheckSpawn event) {
InteractionResult result = EntityEvent.CHECK_SPAWN.invoker().canSpawn(event.getEntity(), event.getWorld(), event.getX(), event.getY(), event.getZ(), event.getSpawnReason(), event.getSpawner());
switch (result) {
case FAIL:
event.setResult(Event.Result.DENY);
break;
case SUCCESS:
case CONSUME:
event.setResult(Event.Result.ALLOW);
break;
case PASS:
break;
EventResult result = EntityEvent.CHECK_SPAWN.invoker().canSpawn(event.getEntity(), 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);
}
}

View File

@@ -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;
@@ -118,10 +120,10 @@ public class DebugEvents {
});
InteractionEvent.FARMLAND_TRAMPLE.register((level, pos, state, distance, entity) -> {
if (entity instanceof Player && ((Player) entity).getItemBySlot(EquipmentSlot.FEET).getItem() == Items.DIAMOND_BOOTS) {
return InteractionResult.FAIL;
return EventResult.interrupt(false);
}
SINK.accept("%s trampled farmland (%s) at %s in %s (Fall height: %f blocks)", entity, state, pos, level, distance);
return InteractionResult.PASS;
return EventResult.pass();
});
LifecycleEvent.SERVER_BEFORE_START.register(instance -> {
SINK.accept("Server ready to start");
@@ -188,7 +190,7 @@ public class DebugEvents {
});
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 InteractionResultHolder.pass(null);
return CompoundEventResult.pass();
}));
LightningEvent.STRIKE.register((bolt, level, pos, toStrike) -> {
SINK.accept(bolt.getScoreboardName() + " struck at " + toShortString(pos) + logSide(level));