mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-21 03:10:54 -04:00
Add sleeping events (#1633)
* 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:
parent
211ddf95aa
commit
8a9f12e01d
9 changed files with 679 additions and 3 deletions
fabric-entity-events-v1
build.gradle
src
main
java/net/fabricmc/fabric
api/entity/event/v1
mixin/entity/event
resources
testmod
java/net/fabricmc/fabric/test/entity/event
resources
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
"mixins": [
|
||||
"EntityMixin",
|
||||
"LivingEntityMixin",
|
||||
"PlayerEntityMixin",
|
||||
"PlayerManagerMixin",
|
||||
"ServerPlayerEntityMixin",
|
||||
"TeleportCommandMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
"defaultRequire": 1,
|
||||
"maxShiftBy": 3
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue