diff --git a/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/api/entity/event/v1/ServerEntityWorldChangeEvents.java b/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/api/entity/event/v1/ServerEntityWorldChangeEvents.java index b1fa89d736..7088594adf 100644 --- a/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/api/entity/event/v1/ServerEntityWorldChangeEvents.java +++ b/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/api/entity/event/v1/ServerEntityWorldChangeEvents.java @@ -45,6 +45,19 @@ public final class ServerEntityWorldChangeEvents { } }); + /** + * Called before a player is being moved to a different world. + */ + public static final Event ALLOW_PLAYER_CHANGE_WORLD = EventFactory.createArrayBacked(AllowPlayerChange.class, callbacks -> (player, origin, destination) -> { + for (AllowPlayerChange callback : callbacks) { + if (!callback.allowChangeWorld(player, origin, destination)) { + return false; + } + } + + return true; + }); + /** * An event which is called after a player has been moved to a different world. * @@ -75,6 +88,19 @@ public interface AfterEntityChange { void afterChangeWorld(Entity originalEntity, Entity newEntity, ServerWorld origin, ServerWorld destination); } + @FunctionalInterface + public interface AllowPlayerChange { + /** + * Called before a player is being moved to a different world. + * + * @param player the player + * @param origin the original world the player is in + * @param destination the new world the player is moving to + * @return true if the player is allowed to change worlds, false otherwise + */ + boolean allowChangeWorld(ServerPlayerEntity player, ServerWorld origin, ServerWorld destination); + } + @FunctionalInterface public interface AfterPlayerChange { /** diff --git a/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/mixin/entity/event/ServerPlayerEntityMixin.java b/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/mixin/entity/event/ServerPlayerEntityMixin.java index 2c13abe408..43e4e4b4e3 100644 --- a/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/mixin/entity/event/ServerPlayerEntityMixin.java +++ b/fabric-entity-events-v1/src/main/java/net/fabricmc/fabric/mixin/entity/event/ServerPlayerEntityMixin.java @@ -20,6 +20,7 @@ import com.mojang.datafixers.util.Either; import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -43,6 +44,7 @@ import net.minecraft.util.Unit; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.world.TeleportTarget; import net.minecraft.world.World; import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents; @@ -77,6 +79,16 @@ private void notifyDeath(DamageSource source, CallbackInfo ci) { ServerLivingEntityEvents.AFTER_DEATH.invoker().afterDeath((ServerPlayerEntity) (Object) this, source); } + @Inject(method = "teleportTo", at = @At(value = "FIELD", target = "Lnet/minecraft/server/network/ServerPlayerEntity;inTeleportationState:Z", opcode = Opcodes.PUTFIELD), cancellable = true) + private void beforeWorldChanged(TeleportTarget teleportTarget, CallbackInfoReturnable cir) { + ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; + boolean allowed = ServerEntityWorldChangeEvents.ALLOW_PLAYER_CHANGE_WORLD.invoker().allowChangeWorld(player, player.getServerWorld(), teleportTarget.world()); + + if (!allowed) { + cir.setReturnValue(null); + } + } + /** * This is called by {@code teleportTo}. */ diff --git a/fabric-entity-events-v1/src/testmod/java/net/fabricmc/fabric/test/entity/event/EntityEventTests.java b/fabric-entity-events-v1/src/testmod/java/net/fabricmc/fabric/test/entity/event/EntityEventTests.java index f6e1779ed5..5071efa365 100644 --- a/fabric-entity-events-v1/src/testmod/java/net/fabricmc/fabric/test/entity/event/EntityEventTests.java +++ b/fabric-entity-events-v1/src/testmod/java/net/fabricmc/fabric/test/entity/event/EntityEventTests.java @@ -64,6 +64,16 @@ public void onInitialize() { LOGGER.info("Entity {} Killed: {}", entity, killed); }); + ServerEntityWorldChangeEvents.ALLOW_PLAYER_CHANGE_WORLD.register((player, origin, destination) -> { + if (player.getMainHandStack().isOf(Items.END_ROD)) { + LOGGER.info("Player {} failed to change world because of handing an end rod", player.getGameProfile().getName()); + return false; + } + + LOGGER.info("Allow Moving player {}: [{} -> {}]", player, origin.getRegistryKey().getValue(), destination.getRegistryKey().getValue()); + return true; + }); + ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) -> { LOGGER.info("Moved player {}: [{} -> {}]", player, origin.getRegistryKey().getValue(), destination.getRegistryKey().getValue()); });