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>
This commit is contained in:
Max
2021-04-15 17:26:48 +02:00
committed by GitHub
parent ac94c33e07
commit ca2a528160
24 changed files with 1066 additions and 37 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,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);
}
}

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

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