From 2e7e9d824d835c473a504448ddb6a401749cd4e3 Mon Sep 17 00:00:00 2001 From: Will BL Date: Mon, 5 Dec 2022 20:30:05 +0000 Subject: [PATCH] Entity Tick Events (#201) * Create entity tick events * Apply licenses to entity tick events * Rename entity tick event Event fields * Fix javadocs / imports * Unfix some entity event javadoc * Change a comment in EntityEventsTestMod Co-authored-by: Ennui Langeweile <85590273+EnnuiL@users.noreply.github.com> * Invert slime tick event test condition Co-authored-by: Ennui Langeweile <85590273+EnnuiL@users.noreply.github.com> --- .../entity_events/api/EntityReviveEvents.java | 6 ++- .../api/ServerEntityTickCallback.java | 48 +++++++++++++++++ .../api/client/ClientEntityTickCallback.java | 52 +++++++++++++++++++ .../entity_events/mixin/ServerWorldMixin.java | 40 ++++++++++++++ .../mixin/client/ClientWorldMixin.java | 40 ++++++++++++++ .../resources/quilt_entity_events.mixins.json | 6 ++- .../test/EntityEventsTestMod.java | 20 ++++++- .../client/EntityEventsTestModClient.java | 13 ++++- 8 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/ServerEntityTickCallback.java create mode 100644 library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/client/ClientEntityTickCallback.java create mode 100644 library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/ServerWorldMixin.java create mode 100644 library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/client/ClientWorldMixin.java diff --git a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/EntityReviveEvents.java b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/EntityReviveEvents.java index ec50d61935..2d17dbcd1c 100644 --- a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/EntityReviveEvents.java +++ b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/EntityReviveEvents.java @@ -71,10 +71,11 @@ public final class EntityReviveEvents { @FunctionalInterface public interface TryReviveBeforeTotem extends EventAwareListener { /** - * {@return {@code true} if the entity which has fatal damage should be revived, or {@code false} otherwise} + * Determines whether an entity should be revived. * * @param entity the entity * @param damageSource the fatal damage source + * @return {@code true} if the entity which has fatal damage should be revived, or {@code false} otherwise */ boolean tryReviveBeforeTotem(LivingEntity entity, DamageSource damageSource); } @@ -82,10 +83,11 @@ public interface TryReviveBeforeTotem extends EventAwareListener { @FunctionalInterface public interface TryReviveAfterTotem extends EventAwareListener { /** - * {@return {@code true} if an entity which has fatal damage and which has not been saved by a totem should be revived, {@code false} otherwise} + * Determines whether an entity should be revived. * * @param entity the entity * @param damageSource the fatal damage source + * @return {@code true} if the entity which has fatal damage should be revived, or {@code false} otherwise */ boolean tryReviveAfterTotem(LivingEntity entity, DamageSource damageSource); } diff --git a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/ServerEntityTickCallback.java b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/ServerEntityTickCallback.java new file mode 100644 index 0000000000..05ba32dcc7 --- /dev/null +++ b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/ServerEntityTickCallback.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.entity_events.api; + +import net.minecraft.entity.Entity; + +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.EventAwareListener; + +/** + * A callback that is invoked when an Entity is ticked on the logical server (nominally every 1/20 of a second). + *

+ * There are two types of entity tick - standalone ({@link Entity#tick()}) and riding ({@link Entity#tickRiding()}). + * This callback takes a parameter which specifies which type of tick it is. + */ +@FunctionalInterface +public interface ServerEntityTickCallback extends EventAwareListener { + /** + * Called when an entity is ticked. + */ + Event EVENT = Event.create(ServerEntityTickCallback.class, callbacks -> (entity, isPassengerTick) -> { + for (ServerEntityTickCallback callback : callbacks) { + callback.onServerEntityTick(entity, isPassengerTick); + } + }); + + /** + * Called when an entity is ticked on the logical server. + * + * @param entity the entity + * @param isPassengerTick whether the entity is being ticked as a passenger of another entity + */ + void onServerEntityTick(Entity entity, boolean isPassengerTick); +} diff --git a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/client/ClientEntityTickCallback.java b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/client/ClientEntityTickCallback.java new file mode 100644 index 0000000000..659fd461cd --- /dev/null +++ b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/api/client/ClientEntityTickCallback.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.entity_events.api.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import net.minecraft.entity.Entity; + +import org.quiltmc.qsl.base.api.event.Event; +import org.quiltmc.qsl.base.api.event.client.ClientEventAwareListener; + +/** + * A callback that is invoked when an Entity is ticked on the logical client (nominally every 1/20 of a second). + *

+ * There are two types of entity tick - standalone ({@link Entity#tick()}) and riding ({@link Entity#tickRiding()}). + * This callback takes a parameter which specifies which type of tick it is. + */ +@Environment(EnvType.CLIENT) +@FunctionalInterface +public interface ClientEntityTickCallback extends ClientEventAwareListener { + /** + * Called when an entity is ticked. + */ + Event EVENT = Event.create(ClientEntityTickCallback.class, callbacks -> (entity, isPassengerTick) -> { + for (ClientEntityTickCallback callback : callbacks) { + callback.onClientEntityTick(entity, isPassengerTick); + } + }); + + /** + * Called when an entity is ticked on the logical client. + * + * @param entity the entity + * @param isPassengerTick whether the entity is being ticked as a passenger of another entity + */ + void onClientEntityTick(Entity entity, boolean isPassengerTick); +} diff --git a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/ServerWorldMixin.java b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/ServerWorldMixin.java new file mode 100644 index 0000000000..2018995095 --- /dev/null +++ b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/ServerWorldMixin.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.entity_events.mixin; + +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.CallbackInfo; + +import net.minecraft.entity.Entity; +import net.minecraft.server.world.ServerWorld; + +import org.quiltmc.qsl.entity_events.api.ServerEntityTickCallback; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin { + @Inject(method = "tickEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tick()V")) + void invokeEntityTickEvent(Entity entity, CallbackInfo ci) { + ServerEntityTickCallback.EVENT.invoker().onServerEntityTick(entity, false); + } + + @Inject(method = "tickPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tickRiding()V")) + void invokePassengerEntityTickEvent(Entity vehicle, Entity passenger, CallbackInfo ci) { + ServerEntityTickCallback.EVENT.invoker().onServerEntityTick(passenger, true); + } +} diff --git a/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/client/ClientWorldMixin.java b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/client/ClientWorldMixin.java new file mode 100644 index 0000000000..7344f94218 --- /dev/null +++ b/library/entity/entity_events/src/main/java/org/quiltmc/qsl/entity_events/mixin/client/ClientWorldMixin.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.qsl.entity_events.mixin.client; + +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.CallbackInfo; + +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; + +import org.quiltmc.qsl.entity_events.api.client.ClientEntityTickCallback; + +@Mixin(ClientWorld.class) +public abstract class ClientWorldMixin { + @Inject(method = "tickEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tick()V")) + void invokeEntityTickEvent(Entity entity, CallbackInfo ci) { + ClientEntityTickCallback.EVENT.invoker().onClientEntityTick(entity, false); + } + + @Inject(method = "tickPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tickRiding()V")) + void invokePassengerEntityTickEvent(Entity vehicle, Entity passenger, CallbackInfo ci) { + ClientEntityTickCallback.EVENT.invoker().onClientEntityTick(passenger, true); + } +} diff --git a/library/entity/entity_events/src/main/resources/quilt_entity_events.mixins.json b/library/entity/entity_events/src/main/resources/quilt_entity_events.mixins.json index 9ff3ec7b9e..f3307a6b1d 100644 --- a/library/entity/entity_events/src/main/resources/quilt_entity_events.mixins.json +++ b/library/entity/entity_events/src/main/resources/quilt_entity_events.mixins.json @@ -3,15 +3,17 @@ "package": "org.quiltmc.qsl.entity_events.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ - "LivingEntityDeathEventMixin", "EntityMixin", + "LivingEntityDeathEventMixin", "LivingEntityMixin", "ServerEntityHandlerMixin", "ServerPlayerEntityMixin", + "ServerWorldMixin", "TeleportCommandMixin" ], "client": [ - "client.ClientEntityHandlerMixin" + "client.ClientEntityHandlerMixin", + "client.ClientWorldMixin" ], "injectors": { "defaultRequire": 1 diff --git a/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/EntityEventsTestMod.java b/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/EntityEventsTestMod.java index 1ebd3c8061..d754d39f3e 100644 --- a/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/EntityEventsTestMod.java +++ b/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/EntityEventsTestMod.java @@ -19,17 +19,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.effect.StatusEffects; import net.minecraft.entity.mob.SkeletonEntity; +import net.minecraft.entity.mob.ZombieEntity; import net.minecraft.entity.passive.ChickenEntity; import net.minecraft.item.Items; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; +import net.minecraft.util.math.Direction; import net.minecraft.util.registry.Registry; import net.minecraft.world.dimension.DimensionTypes; @@ -37,6 +40,7 @@ import org.quiltmc.qsl.entity_events.api.EntityWorldChangeEvents; import org.quiltmc.qsl.entity_events.api.LivingEntityDeathCallback; import org.quiltmc.qsl.entity_events.api.ServerEntityLoadEvents; +import org.quiltmc.qsl.entity_events.api.ServerEntityTickCallback; import org.quiltmc.qsl.entity_events.api.ServerPlayerEntityCopyCallback; public class EntityEventsTestMod implements EntityReviveEvents.TryReviveAfterTotem, @@ -46,7 +50,8 @@ public class EntityEventsTestMod implements EntityReviveEvents.TryReviveAfterTot ServerEntityLoadEvents.AfterUnload, EntityWorldChangeEvents.AfterPlayerWorldChange, EntityWorldChangeEvents.AfterEntityWorldChange, - ServerPlayerEntityCopyCallback { + ServerPlayerEntityCopyCallback, + ServerEntityTickCallback { public static final Logger LOGGER = LoggerFactory.getLogger("quilt_entity_events_testmod"); // When an entity is holding an allium in its main hand at death and nothing else revives it, it will be @@ -122,4 +127,17 @@ public void onPlayerCopy(ServerPlayerEntity newPlayer, ServerPlayerEntity origin newPlayer.giveItemStack(Items.APPLE.getDefaultStack()); } } + + // Zombies will jump higher in a floaty way when it's raining, + // or place raw iron if they're riding something + @Override + public void onServerEntityTick(Entity entity, boolean isPassengerTick) { + if (entity.world.isRaining() && entity instanceof ZombieEntity zombie) { + if (isPassengerTick) { + entity.world.setBlockState(entity.getBlockPos().offset(Direction.UP, 3), Blocks.RAW_IRON_BLOCK.getDefaultState()); + } else { + entity.setVelocity(entity.getVelocity().add(0.0, 0.05, 0.0)); + } + } + } } diff --git a/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/client/EntityEventsTestModClient.java b/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/client/EntityEventsTestModClient.java index 4af210ce46..64f075e63e 100644 --- a/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/client/EntityEventsTestModClient.java +++ b/library/entity/entity_events/src/testmod/java/org/quiltmc/qsl/entity_events/test/client/EntityEventsTestModClient.java @@ -19,12 +19,15 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.mob.SkeletonEntity; +import net.minecraft.entity.mob.SlimeEntity; import net.minecraft.entity.passive.ChickenEntity; +import net.minecraft.particle.ParticleTypes; import org.quiltmc.qsl.entity_events.api.client.ClientEntityLoadEvents; +import org.quiltmc.qsl.entity_events.api.client.ClientEntityTickCallback; import org.quiltmc.qsl.entity_events.test.EntityEventsTestMod; -public class EntityEventsTestModClient implements ClientEntityLoadEvents.AfterLoad, ClientEntityLoadEvents.AfterUnload { +public class EntityEventsTestModClient implements ClientEntityLoadEvents.AfterLoad, ClientEntityLoadEvents.AfterUnload, ClientEntityTickCallback { // Chicken Loading is logged. @Override public void onLoadClient(Entity entity, ClientWorld world) { @@ -40,4 +43,12 @@ public void onUnloadClient(Entity entity, ClientWorld world) { EntityEventsTestMod.LOGGER.info("Skeleton unloaded, client"); } } + + // Slimes will emit explosion particles if they're riding something + @Override + public void onClientEntityTick(Entity entity, boolean isPassengerTick) { + if (entity instanceof SlimeEntity && isPassengerTick) { + entity.world.addParticle(ParticleTypes.EXPLOSION_EMITTER, entity.getX(), entity.getY(), entity.getZ(), 0.0, 0.0, 0.0); + } + } }