Add sleeping events ()

* Add sleeping events

* Fix wake up event triggering on every wakeUp() call

* Make direction modifications stackable

* Simplify by not using Optional in MODIFY_SLEEPING_DIRECTION

* Add two new events

* Let's call it VERIFY_BED

* And it's ALLOW_BED now

* Add the rest of the events

* Clarify docs

* Expand docs, add missing vanillaResult

* WAKE_UP -> STOP_SLEEPING, javadoc

* Make sleepingPos checks more consistent in LivingEntityMixin
This commit is contained in:
Juuxel 2021-08-24 19:44:12 +03:00 committed by GitHub
parent 211ddf95aa
commit 8a9f12e01d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 679 additions and 3 deletions
fabric-entity-events-v1
build.gradle
src
main
testmod
java/net/fabricmc/fabric/test/entity/event
resources

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-entity-events-v1"
version = getSubprojectVersion(project, "1.1.0")
version = getSubprojectVersion(project, "1.2.0")
moduleDependencies(project, [
'fabric-api-base'
@ -7,4 +7,6 @@ moduleDependencies(project, [
dependencies {
testmodImplementation project(path: ':fabric-command-api-v1', configuration: 'dev')
testmodImplementation project(path: ':fabric-networking-api-v1', configuration: 'dev')
testmodImplementation project(path: ':fabric-registry-sync-v0', configuration: 'dev')
}

View file

@ -0,0 +1,323 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.api.entity.event.v1;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events about the sleep of {@linkplain LivingEntity living entities}.
*
* <p>These events can be categorized into three groups:
* <ol>
* <li>Simple listeners: {@link #START_SLEEPING} and {@link #STOP_SLEEPING}</li>
* <li>Predicates: {@link #ALLOW_BED}, {@link #ALLOW_SLEEP_TIME}, {@link #ALLOW_RESETTING_TIME},
* {@link #ALLOW_NEARBY_MONSTERS}, {@link #ALLOW_SETTING_SPAWN} and {@link #ALLOW_SLEEPING}
*
* <p><b>Note:</b> Only the {@link #ALLOW_BED} event applies to non-player entities.</li>
* <li>Modifiers: {@link #MODIFY_SLEEPING_DIRECTION}</li>
* </ol>
*
* <p>Sleep events are useful for making custom bed blocks that do not extend {@link net.minecraft.block.BedBlock}.
* Custom beds generally only need a custom {@link #ALLOW_BED} checker and a {@link #MODIFY_SLEEPING_DIRECTION} callback,
* but the other events might be useful as well.
*/
public final class EntitySleepEvents {
/**
* An event that checks whether a player can start to sleep in a bed-like block.
* This event only applies to sleeping using {@link PlayerEntity#trySleep(BlockPos)}.
*
* <p><b>Note:</b> Please use the more detailed events {@link #ALLOW_SLEEP_TIME} and {@link #ALLOW_NEARBY_MONSTERS}
* if they match your use case! This helps with mod compatibility.
*
* <p>If this event returns a {@link net.minecraft.entity.player.PlayerEntity.SleepFailureReason}, it is used
* as the return value of {@link PlayerEntity#trySleep(BlockPos)} and sleeping fails. A null return value
* means that the player will start sleeping.
*
* <p>When this event is called, all vanilla sleeping checks have already succeeded, i.e. this event
* is used in addition to vanilla checks. The more detailed events {@link #ALLOW_SLEEP_TIME} and {@link #ALLOW_NEARBY_MONSTERS}
* are also checked before this event.
*/
public static final Event<AllowSleeping> ALLOW_SLEEPING = EventFactory.createArrayBacked(AllowSleeping.class, callbacks -> (player, sleepingPos) -> {
for (AllowSleeping callback : callbacks) {
PlayerEntity.SleepFailureReason reason = callback.allowSleep(player, sleepingPos);
if (reason != null) {
return reason;
}
}
return null;
});
/**
* An event that is called when an entity starts to sleep.
*/
public static final Event<StartSleeping> START_SLEEPING = EventFactory.createArrayBacked(StartSleeping.class, callbacks -> (entity, sleepingPos) -> {
for (StartSleeping callback : callbacks) {
callback.onStartSleeping(entity, sleepingPos);
}
});
/**
* An event that is called when an entity stops sleeping and wakes up.
*/
public static final Event<StopSleeping> STOP_SLEEPING = EventFactory.createArrayBacked(StopSleeping.class, callbacks -> (entity, sleepingPos) -> {
for (StopSleeping callback : callbacks) {
callback.onStopSleeping(entity, sleepingPos);
}
});
/**
* An event that is called to check whether a block is valid for sleeping.
*
* <p>Used for checking whether the block at the current sleeping position is a valid bed block.
* If false, the player wakes up.
*
* <p>This event is only checked <i>during</i> sleeping, so an entity can
* {@linkplain LivingEntity#sleep(BlockPos) start sleeping} on any block, but will immediately
* wake up if this check fails.
*
* @see LivingEntity#isSleepingInBed()
*/
public static final Event<AllowBed> ALLOW_BED = EventFactory.createArrayBacked(AllowBed.class, callbacks -> (entity, sleepingPos, state, vanillaResult) -> {
for (AllowBed callback : callbacks) {
ActionResult result = callback.allowBed(entity, sleepingPos, state, vanillaResult);
if (result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
});
/**
* An event that checks whether the current time of day is valid for sleeping.
*
* <p>Note that if sleeping during day time is allowed, the game will still reset the time to 0 if the usual
* conditions are met, unless forbidden with {@link #ALLOW_RESETTING_TIME}.
*/
public static final Event<AllowSleepTime> ALLOW_SLEEP_TIME = EventFactory.createArrayBacked(AllowSleepTime.class, callbacks -> (player, sleepingPos, vanillaResult) -> {
for (AllowSleepTime callback : callbacks) {
ActionResult result = callback.allowSleepTime(player, sleepingPos, vanillaResult);
if (result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
});
/**
* An event that checks whether players can sleep when monsters are nearby.
*
* <p>This event can also be used to force a failing result, meaning it can do custom monster checks.
*/
public static final Event<AllowNearbyMonsters> ALLOW_NEARBY_MONSTERS = EventFactory.createArrayBacked(AllowNearbyMonsters.class, callbacks -> (player, sleepingPos, vanillaResult) -> {
for (AllowNearbyMonsters callback : callbacks) {
ActionResult result = callback.allowNearbyMonsters(player, sleepingPos, vanillaResult);
if (result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
});
/**
* An event that checks whether a sleeping player counts into skipping the current day and resetting the time to 0.
*
* <p>When this event is called, all vanilla time resetting checks have already succeeded, i.e. this event
* is used in addition to vanilla checks.
*/
public static final Event<AllowResettingTime> ALLOW_RESETTING_TIME = EventFactory.createArrayBacked(AllowResettingTime.class, callbacks -> player -> {
for (AllowResettingTime callback : callbacks) {
if (!callback.allowResettingTime(player)) {
return false;
}
}
return true;
});
/**
* An event that can be used to provide the entity's sleep direction if missing.
*
* <p>This is useful for custom bed blocks that need to determine the sleeping direction themselves.
* If the block is not a {@link net.minecraft.block.BedBlock}, you need to provide the sleeping direction manually
* with this event.
*/
public static final Event<ModifySleepingDirection> MODIFY_SLEEPING_DIRECTION = EventFactory.createArrayBacked(ModifySleepingDirection.class, callbacks -> (entity, sleepingPos, sleepingDirection) -> {
for (ModifySleepingDirection callback : callbacks) {
sleepingDirection = callback.modifySleepDirection(entity, sleepingPos, sleepingDirection);
}
return sleepingDirection;
});
/**
* An event that checks whether a player's spawn can be set when sleeping.
*
* <p>Vanilla always allows this operation.
*/
public static final Event<AllowSettingSpawn> ALLOW_SETTING_SPAWN = EventFactory.createArrayBacked(AllowSettingSpawn.class, callbacks -> (player, sleepingPos) -> {
for (AllowSettingSpawn callback : callbacks) {
if (!callback.allowSettingSpawn(player, sleepingPos)) {
return false;
}
}
return true;
});
@FunctionalInterface
public interface AllowSleeping {
/**
* Checks whether a player can start sleeping in a bed-like block.
*
* @param player the sleeping player
* @param sleepingPos the future {@linkplain LivingEntity#getSleepingPosition() sleeping position} of the entity
* @return null if the player can sleep, or a failure reason if they cannot
* @see PlayerEntity#trySleep(BlockPos)
*/
@Nullable
PlayerEntity.SleepFailureReason allowSleep(PlayerEntity player, BlockPos sleepingPos);
}
@FunctionalInterface
public interface StartSleeping {
/**
* Called when an entity starts to sleep.
*
* @param entity the sleeping entity
* @param sleepingPos the {@linkplain LivingEntity#getSleepingPosition() sleeping position} of the entity
*/
void onStartSleeping(LivingEntity entity, BlockPos sleepingPos);
}
@FunctionalInterface
public interface StopSleeping {
/**
* Called when an entity stops sleeping and wakes up.
*
* @param entity the sleeping entity
* @param sleepingPos the {@linkplain LivingEntity#getSleepingPosition() sleeping position} of the entity
*/
void onStopSleeping(LivingEntity entity, BlockPos sleepingPos);
}
@FunctionalInterface
public interface AllowBed {
/**
* Checks whether a block is a valid bed for the entity.
*
* <p>Non-{@linkplain ActionResult#PASS passing} return values cancel further callbacks.
*
* @param entity the sleeping entity
* @param sleepingPos the position of the block
* @param state the block state to check
* @param vanillaResult true if vanilla allows the block, false otherwise
* @return {@link ActionResult#SUCCESS} if the bed is valid, {@link ActionResult#FAIL} if it's not,
* {@link ActionResult#PASS} to fall back to other callbacks
*/
ActionResult allowBed(LivingEntity entity, BlockPos sleepingPos, BlockState state, boolean vanillaResult);
}
@FunctionalInterface
public interface AllowSleepTime {
/**
* Checks whether the current time of day is valid for sleeping.
*
* <p>Non-{@linkplain ActionResult#PASS passing} return values cancel further callbacks.
*
* @param player the sleeping player
* @param sleepingPos the (possibly still unset) {@linkplain LivingEntity#getSleepingPosition() sleeping position} of the player
* @param vanillaResult true if vanilla allows the time, false otherwise
* @return {@link ActionResult#SUCCESS} if the time is valid, {@link ActionResult#FAIL} if it's not,
* {@link ActionResult#PASS} to fall back to other callbacks
*/
ActionResult allowSleepTime(PlayerEntity player, BlockPos sleepingPos, boolean vanillaResult);
}
@FunctionalInterface
public interface AllowNearbyMonsters {
/**
* Checks whether a player can sleep when monsters are nearby.
*
* <p>Non-{@linkplain ActionResult#PASS passing} return values cancel further callbacks.
*
* @param player the sleeping player
* @param sleepingPos the (possibly still unset) {@linkplain LivingEntity#getSleepingPosition() sleeping position} of the player
* @param vanillaResult true if vanilla's monster check succeeded, false otherwise
* @return {@link ActionResult#SUCCESS} to allow sleeping, {@link ActionResult#FAIL} to prevent sleeping,
* {@link ActionResult#PASS} to fall back to other callbacks
*/
ActionResult allowNearbyMonsters(PlayerEntity player, BlockPos sleepingPos, boolean vanillaResult);
}
@FunctionalInterface
public interface AllowResettingTime {
/**
* Checks whether a sleeping player counts into skipping the current day and resetting the time to 0.
*
* @param player the sleeping player
* @return true if allowed, false otherwise
*/
boolean allowResettingTime(PlayerEntity player);
}
@FunctionalInterface
public interface ModifySleepingDirection {
/**
* Modifies or provides a sleeping direction for a block.
*
* @param entity the sleeping entity
* @param sleepingPos the position of the block slept on
* @param sleepingDirection the old sleeping direction, or null if not determined by vanilla logic
* @return the new sleeping direction
*/
@Nullable
Direction modifySleepDirection(LivingEntity entity, BlockPos sleepingPos, @Nullable Direction sleepingDirection);
}
@FunctionalInterface
public interface AllowSettingSpawn {
/**
* Checks whether a player's spawn can be set when sleeping.
*
* @param player the sleeping player
* @param sleepingPos the sleeping position
* @return true if allowed, false otherwise
*/
boolean allowSettingSpawn(PlayerEntity player, BlockPos sleepingPos);
}
private EntitySleepEvents() {
}
}

View file

@ -16,20 +16,29 @@
package net.fabricmc.fabric.mixin.entity.event;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
@ -38,6 +47,9 @@ abstract class LivingEntityMixin extends EntityMixin {
@Shadow
public abstract boolean isDead();
@Shadow
public abstract Optional<BlockPos> getSleepingPosition();
@Inject(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;onKilledOther(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/entity/LivingEntity;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
private void onEntityKilledOther(DamageSource source, CallbackInfo ci, Entity attacker) {
// FIXME: Cannot use shadowed fields from supermixins - needs a fix so people can use fabric api in a dev environment even though this is fine in this repo and prod.
@ -53,4 +65,40 @@ abstract class LivingEntityMixin extends EntityMixin {
return isDead();
}
@Inject(method = "sleep", at = @At("RETURN"))
private void onSleep(BlockPos pos, CallbackInfo info) {
EntitySleepEvents.START_SLEEPING.invoker().onStartSleeping((LivingEntity) (Object) this, pos);
}
@Inject(method = "wakeUp", at = @At("HEAD"))
private void onWakeUp(CallbackInfo info) {
BlockPos sleepingPos = getSleepingPosition().orElse(null);
// If actually asleep - this method is often called with data loading, syncing etc. "just to be sure"
if (sleepingPos != null) {
EntitySleepEvents.STOP_SLEEPING.invoker().onStopSleeping((LivingEntity) (Object) this, sleepingPos);
}
}
@Inject(method = "isSleepingInBed", at = @At("RETURN"), cancellable = true)
private void onIsSleepingInBed(CallbackInfoReturnable<Boolean> info) {
BlockPos sleepingPos = getSleepingPosition().orElse(null);
if (sleepingPos != null) {
BlockState bedState = world.getBlockState(sleepingPos);
ActionResult result = EntitySleepEvents.ALLOW_BED.invoker().allowBed((LivingEntity) (Object) this, sleepingPos, bedState, info.getReturnValueZ());
if (result != ActionResult.PASS) {
info.setReturnValue(result.isAccepted());
}
}
}
@Inject(method = "getSleepingDirection", at = @At("RETURN"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
private void onGetSleepingDirection(CallbackInfoReturnable<Direction> info, @Nullable BlockPos sleepingPos) {
if (sleepingPos != null) {
info.setReturnValue(EntitySleepEvents.MODIFY_SLEEPING_DIRECTION.invoker().modifySleepDirection((LivingEntity) (Object) this, sleepingPos, info.getReturnValue()));
}
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.mixin.entity.event;
import com.mojang.datafixers.util.Either;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
@Mixin(PlayerEntity.class)
abstract class PlayerEntityMixin extends LivingEntityMixin {
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
private void onTrySleep(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> info) {
PlayerEntity.SleepFailureReason failureReason = EntitySleepEvents.ALLOW_SLEEPING.invoker().allowSleep((PlayerEntity) (Object) this, pos);
if (failureReason != null) {
info.setReturnValue(Either.left(failureReason));
}
}
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;isDay()Z"))
private boolean redirectDaySleepCheck(World world) {
boolean day = world.isDay();
if (getSleepingPosition().isPresent()) {
BlockPos pos = getSleepingPosition().get();
ActionResult result = EntitySleepEvents.ALLOW_SLEEP_TIME.invoker().allowSleepTime((PlayerEntity) (Object) this, pos, !day);
if (result != ActionResult.PASS) {
return !result.isAccepted(); // true from the event = night-like conditions, so we have to invert
}
}
return day;
}
@Inject(method = "isSleepingLongEnough", at = @At("RETURN"), cancellable = true)
private void onIsSleepingLongEnough(CallbackInfoReturnable<Boolean> info) {
if (info.getReturnValueZ()) {
info.setReturnValue(EntitySleepEvents.ALLOW_RESETTING_TIME.invoker().allowResettingTime((PlayerEntity) (Object) this));
}
}
}

View file

@ -16,17 +16,37 @@
package net.fabricmc.fabric.mixin.entity.event;
import java.util.List;
import com.mojang.datafixers.util.Either;
import org.jetbrains.annotations.Nullable;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.BedBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.property.Property;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
@ -65,4 +85,44 @@ abstract class ServerPlayerEntityMixin extends LivingEntityMixin {
private void onCopyFrom(ServerPlayerEntity oldPlayer, boolean alive, CallbackInfo ci) {
ServerPlayerEvents.COPY_FROM.invoker().copyFromPlayer(oldPlayer, (ServerPlayerEntity) (Object) this, alive);
}
@Redirect(method = "trySleep", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;get(Lnet/minecraft/state/property/Property;)Ljava/lang/Comparable;"))
private Comparable<?> redirectSleepDirection(BlockState state, Property<?> property, BlockPos pos) {
Direction initial = state.getBlock() instanceof BedBlock ? (Direction) state.get(property) : null;
return EntitySleepEvents.MODIFY_SLEEPING_DIRECTION.invoker().modifySleepDirection((LivingEntity) (Object) this, pos, initial);
}
@Inject(method = "trySleep", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;get(Lnet/minecraft/state/property/Property;)Ljava/lang/Comparable;", shift = At.Shift.BY, by = 3), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
private void onTrySleepDirectionCheck(BlockPos pos, CallbackInfoReturnable<Either<PlayerEntity.SleepFailureReason, Unit>> info, @Nullable Direction sleepingDirection) {
// This checks the result from the event call above.
if (sleepingDirection == null) {
info.setReturnValue(Either.left(PlayerEntity.SleepFailureReason.NOT_POSSIBLE_HERE));
}
}
@Redirect(method = "trySleep", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;setSpawnPoint(Lnet/minecraft/util/registry/RegistryKey;Lnet/minecraft/util/math/BlockPos;FZZ)V"))
private void onSetSpawnPoint(ServerPlayerEntity player, RegistryKey<World> dimension, BlockPos pos, float angle, boolean spawnPointSet, boolean sendMessage) {
if (EntitySleepEvents.ALLOW_SETTING_SPAWN.invoker().allowSettingSpawn(player, pos)) {
player.setSpawnPoint(dimension, pos, angle, spawnPointSet, sendMessage);
}
}
@Redirect(method = "trySleep", at = @At(value = "INVOKE", target = "Ljava/util/List;isEmpty()Z", remap = false))
private boolean hasNoMonstersNearby(List<HostileEntity> monsters, BlockPos pos) {
boolean vanillaResult = monsters.isEmpty();
ActionResult result = EntitySleepEvents.ALLOW_NEARBY_MONSTERS.invoker().allowNearbyMonsters((PlayerEntity) (Object) this, pos, vanillaResult);
return result != ActionResult.PASS ? result.isAccepted() : vanillaResult;
}
@Redirect(method = "trySleep", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;isDay()Z"))
private boolean redirectDaySleepCheck(World world, BlockPos pos) {
boolean day = world.isDay();
ActionResult result = EntitySleepEvents.ALLOW_SLEEP_TIME.invoker().allowSleepTime((PlayerEntity) (Object) this, pos, !day);
if (result != ActionResult.PASS) {
return !result.isAccepted(); // true from the event = night-like conditions, so we have to invert
}
return day;
}
}

View file

@ -5,11 +5,13 @@
"mixins": [
"EntityMixin",
"LivingEntityMixin",
"PlayerEntityMixin",
"PlayerManagerMixin",
"ServerPlayerEntityMixin",
"TeleportCommandMixin"
],
"injectors": {
"defaultRequire": 1
"defaultRequire": 1,
"maxShiftBy": 3
}
}

View file

@ -19,19 +19,40 @@ package net.fabricmc.fabric.test.entity.event;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Material;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.server.command.CommandManager;
import net.minecraft.text.LiteralText;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
public final class EntityEventTests implements ModInitializer {
private static final Logger LOGGER = LogManager.getLogger(EntityEventTests.class);
public static final Block TEST_BED = new TestBedBlock(AbstractBlock.Settings.of(Material.WOOL).strength(1, 1));
@Override
public void onInitialize() {
Registry.register(Registry.BLOCK, new Identifier("fabric-entity-events-v1-testmod", "test_bed"), TEST_BED);
Registry.register(Registry.ITEM, new Identifier("fabric-entity-events-v1-testmod", "test_bed"), new BlockItem(TEST_BED, new Item.Settings().group(ItemGroup.DECORATIONS)));
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.register((world, entity, killed) -> {
LOGGER.info("Entity Killed: {}", killed);
});
@ -62,5 +83,85 @@ public final class EntityEventTests implements ModInitializer {
return true;
});
EntitySleepEvents.ALLOW_SLEEPING.register((player, sleepingPos) -> {
// Can't sleep if holds blue wool
if (player.getStackInHand(Hand.MAIN_HAND).isOf(Items.BLUE_WOOL)) {
return PlayerEntity.SleepFailureReason.OTHER_PROBLEM;
}
return null;
});
EntitySleepEvents.START_SLEEPING.register((entity, sleepingPos) -> {
LOGGER.info("Entity {} sleeping at {}", entity, sleepingPos);
});
EntitySleepEvents.STOP_SLEEPING.register((entity, sleepingPos) -> {
LOGGER.info("Entity {} woke up at {}", entity, sleepingPos);
});
EntitySleepEvents.ALLOW_BED.register((entity, sleepingPos, state, vanillaResult) -> {
return state.isOf(TEST_BED) ? ActionResult.SUCCESS : ActionResult.PASS;
});
EntitySleepEvents.MODIFY_SLEEPING_DIRECTION.register((entity, sleepingPos, sleepingDirection) -> {
return entity.world.getBlockState(sleepingPos).isOf(TEST_BED) ? Direction.NORTH : sleepingDirection;
});
EntitySleepEvents.ALLOW_SLEEP_TIME.register((player, sleepingPos, vanillaResult) -> {
// Yellow wool allows to sleep during the day
if (player.world.isDay() && player.getStackInHand(Hand.MAIN_HAND).isOf(Items.YELLOW_WOOL)) {
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
});
EntitySleepEvents.ALLOW_NEARBY_MONSTERS.register((player, sleepingPos, vanillaResult) -> {
// Green wool allows monsters and red wool always "detects" monsters
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
if (stack.isOf(Items.GREEN_WOOL)) {
return ActionResult.SUCCESS;
} else if (stack.isOf(Items.RED_WOOL)) {
return ActionResult.FAIL;
}
return ActionResult.PASS;
});
EntitySleepEvents.ALLOW_SETTING_SPAWN.register((player, sleepingPos) -> {
// Don't set spawn if holding white wool
return !player.getStackInHand(Hand.MAIN_HAND).isOf(Items.WHITE_WOOL);
});
EntitySleepEvents.ALLOW_RESETTING_TIME.register(player -> {
// Don't allow resetting time if holding black wool
return !player.getStackInHand(Hand.MAIN_HAND).isOf(Items.BLACK_WOOL);
});
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
dispatcher.register(CommandManager.literal("addsleeptestwools").executes(context -> {
addSleepWools(context.getSource().getPlayer());
return 0;
}));
});
}
private static void addSleepWools(PlayerEntity player) {
PlayerInventory inventory = player.getInventory();
inventory.offerOrDrop(createNamedItem(Items.BLUE_WOOL, "Can't start sleeping"));
inventory.offerOrDrop(createNamedItem(Items.YELLOW_WOOL, "Sleep whenever"));
inventory.offerOrDrop(createNamedItem(Items.GREEN_WOOL, "Allow nearby monsters"));
inventory.offerOrDrop(createNamedItem(Items.RED_WOOL, "Detect nearby monsters"));
inventory.offerOrDrop(createNamedItem(Items.WHITE_WOOL, "Don't set spawn"));
inventory.offerOrDrop(createNamedItem(Items.BLACK_WOOL, "Don't reset time"));
}
private static ItemStack createNamedItem(Item item, String name) {
ItemStack stack = new ItemStack(item);
stack.setCustomName(new LiteralText(name));
return stack;
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* 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 net.fabricmc.fabric.test.entity.event;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
public class TestBedBlock extends Block {
private static final VoxelShape SHAPE = createCuboidShape(0, 0, 0, 16, 8, 16);
public static final BooleanProperty OCCUPIED = Properties.OCCUPIED;
public TestBedBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(OCCUPIED, false));
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SHAPE;
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!state.get(OCCUPIED) && world.getDimension().isBedWorking()) {
if (!world.isClient) {
player.trySleep(pos).ifLeft(sleepFailureReason -> {
Text message = sleepFailureReason.toText();
if (message != null) {
player.sendMessage(message, true);
}
});
}
return ActionResult.CONSUME;
}
return ActionResult.PASS;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(OCCUPIED);
}
}

View file

@ -7,7 +7,8 @@
"license": "Apache-2.0",
"depends": {
"fabric-entity-events-v1": "*",
"fabric-command-api-v1":"*"
"fabric-command-api-v1": "*",
"fabric-registry-sync-v0": "*"
},
"entrypoints": {
"main": [