diff --git a/Minecraft.Client/ServerPlayer.cpp b/Minecraft.Client/ServerPlayer.cpp index 2ae8431ce..daa52d114 100644 --- a/Minecraft.Client/ServerPlayer.cpp +++ b/Minecraft.Client/ServerPlayer.cpp @@ -1046,6 +1046,10 @@ void ServerPlayer::take(shared_ptr e, int orgCount) Player::BedSleepingResult ServerPlayer::startSleepInBed(int x, int y, int z, bool bTestUse) { +#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) + if (!bTestUse && FourKitBridge::FireBedEnter(entityId, dimension, x, y, z)) + return OTHER_PROBLEM; +#endif BedSleepingResult result = Player::startSleepInBed(x, y, z, bTestUse); if (result == OK) { @@ -1059,12 +1063,22 @@ Player::BedSleepingResult ServerPlayer::startSleepInBed(int x, int y, int z, boo void ServerPlayer::stopSleepInBed(bool forcefulWakeUp, bool updateLevelList, bool saveRespawnPoint) { +#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) + int bedX = bedPosition ? bedPosition->x : 0; + int bedY = bedPosition ? bedPosition->y : 0; + int bedZ = bedPosition ? bedPosition->z : 0; + bool wasSleeping = isSleeping(); +#endif if (isSleeping()) { getLevel()->getTracker()->broadcastAndSend(shared_from_this(), std::make_shared(shared_from_this(), AnimatePacket::WAKE_UP)); } Player::stopSleepInBed(forcefulWakeUp, updateLevelList, saveRespawnPoint); if (connection != nullptr) connection->teleport(x, y, z, yRot, xRot); +#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD) + if (wasSleeping) + FourKitBridge::FireBedLeave(entityId, dimension, bedX, bedY, bedZ); +#endif } void ServerPlayer::ride(shared_ptr e) diff --git a/Minecraft.Server.FourKit/Entity/HumanEntity.cs b/Minecraft.Server.FourKit/Entity/HumanEntity.cs index 0ebfe99bb..497ce7bca 100644 --- a/Minecraft.Server.FourKit/Entity/HumanEntity.cs +++ b/Minecraft.Server.FourKit/Entity/HumanEntity.cs @@ -13,6 +13,8 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder internal PlayerInventory _playerInventory = new(); internal Inventory _enderChestInventory = new("Ender Chest", InventoryType.ENDER_CHEST, 27); private ItemStack? _cursorItem; + private bool _sleeping; + private int _sleepTicks; /// /// Gets this human's current . @@ -154,4 +156,20 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder internal void SetGameModeInternal(GameMode mode) => _gameMode = mode; internal void SetNameInternal(string name) => _name = name; + + internal void SetSleepingInternal(bool sleeping) => _sleeping = sleeping; + + internal void SetSleepTicksInternal(int ticks) => _sleepTicks = ticks; + + /// + /// Returns whether this player is slumbering. + /// + /// slumber state + public bool isSleeping() => _sleeping; + + /// + /// Get the sleep ticks of the player. This value may be capped. + /// + /// slumber ticks + public int getSleepTicks() => _sleepTicks; } diff --git a/Minecraft.Server.FourKit/Event/Player/PlayerBedEnterEvent.cs b/Minecraft.Server.FourKit/Event/Player/PlayerBedEnterEvent.cs new file mode 100644 index 000000000..5647da28c --- /dev/null +++ b/Minecraft.Server.FourKit/Event/Player/PlayerBedEnterEvent.cs @@ -0,0 +1,33 @@ +namespace Minecraft.Server.FourKit.Event.Player; + +using Minecraft.Server.FourKit.Entity; +using Minecraft.Server.FourKit.Block; + +/// +/// This event is fired when the player is almost about to enter the bed. +/// +public class PlayerBedEnterEvent : PlayerEvent, Cancellable +{ + private readonly Block _bed; + private bool _cancelled; + + internal PlayerBedEnterEvent(Player player, Block bed) : base(player) + { + _bed = bed; + } + + /// + /// Returns the bed block involved in this event. + /// + /// the bed block involved in this event + public Block getBed() => _bed; + + /// + public bool isCancelled() => _cancelled; + + /// + public void setCancelled(bool cancel) + { + _cancelled = cancel; + } +} diff --git a/Minecraft.Server.FourKit/Event/Player/PlayerBedLeaveEvent.cs b/Minecraft.Server.FourKit/Event/Player/PlayerBedLeaveEvent.cs new file mode 100644 index 000000000..aae1a50bf --- /dev/null +++ b/Minecraft.Server.FourKit/Event/Player/PlayerBedLeaveEvent.cs @@ -0,0 +1,23 @@ +namespace Minecraft.Server.FourKit.Event.Player; + +using Minecraft.Server.FourKit.Entity; +using Minecraft.Server.FourKit.Block; + +/// +/// This event is fired when the player is leaving a bed. +/// +public class PlayerBedLeaveEvent : PlayerEvent +{ + private readonly Block _bed; + + internal PlayerBedLeaveEvent(Player player, Block bed) : base(player) + { + _bed = bed; + } + + /// + /// Returns the bed block involved in this event. + /// + /// the bed block involved in this event + public Block getBed() => _bed; +} diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs index 8293af672..2e3190e74 100644 --- a/Minecraft.Server.FourKit/FourKitHost.cs +++ b/Minecraft.Server.FourKit/FourKitHost.cs @@ -948,12 +948,12 @@ public static class FourKitHost }; } - // double[11] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension } + // double[13] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer } private static void SyncPlayerFromNative(Player player) { if (NativeBridge.GetPlayerSnapshot == null) return; - double[] buf = new double[11]; + double[] buf = new double[13]; var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); try { @@ -972,6 +972,8 @@ public static class FourKitHost player.SetFallDistanceInternal((float)buf[5]); player.SetGameModeInternal((GameMode)(int)buf[6]); player.SetWalkSpeedInternal((float)buf[7]); + player.SetSleepingInternal(buf[11] != 0.0); + player.SetSleepTicksInternal((int)buf[12]); } private static void BroadcastNativeMessage(string message) @@ -1180,6 +1182,51 @@ public static class FourKitHost } } + [UnmanagedCallersOnly] + public static int FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return 0; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var bed = new Block.Block(world, bedX, bedY, bedZ); + var evt = new Event.Player.PlayerBedEnterEvent(player, bed); + FourKit.FireEvent(evt); + + return evt.isCancelled() ? 1 : 0; + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBedEnter error: {ex}"); + return 0; + } + } + + [UnmanagedCallersOnly] + public static void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ) + { + try + { + var player = FourKit.GetPlayerByEntityId(entityId); + if (player == null) return; + + SyncPlayerFromNative(player); + + var world = FourKit.getWorld(dimId); + var bed = new Block.Block(world, bedX, bedY, bedZ); + var evt = new Event.Player.PlayerBedLeaveEvent(player, bed); + FourKit.FireEvent(evt); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"FireBedLeave error: {ex}"); + } + } + private static ClickType MapNativeClickType(int nativeClickType, int button) { return nativeClickType switch diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp index 10ad4b067..dddecdd0d 100644 --- a/Minecraft.Server/FourKitBridge.cpp +++ b/Minecraft.Server/FourKitBridge.cpp @@ -170,8 +170,10 @@ typedef int(__stdcall *fn_fire_player_portal)(int entityId, double toX, double toY, double toZ, int toDimId, int cause, double *outCoords); typedef int(__stdcall *fn_fire_inventory_click)(int entityId, - int slot, int button, int clickType, int nativeContainerType, int containerSize, - const char *titleUtf8, int titleByteLen); + int slot, int button, int clickType, int nativeContainerType, int containerSize, + const char *titleUtf8, int titleByteLen); +typedef int(__stdcall *fn_fire_bed_enter)(int entityId, int dimId, int bedX, int bedY, int bedZ); +typedef void(__stdcall *fn_fire_bed_leave)(int entityId, int dimId, int bedX, int bedY, int bedZ); struct OpenContainerInfo { @@ -210,6 +212,8 @@ static fn_get_plugin_command_help s_managedGetPluginCommandHelp = nullptr; static fn_fire_player_teleport s_managedFirePlayerTeleport = nullptr; static fn_fire_player_portal s_managedFirePlayerPortal = nullptr; static fn_fire_inventory_click s_managedFireInventoryClick = nullptr; +static fn_fire_bed_enter s_managedFireBedEnter = nullptr; +static fn_fire_bed_leave s_managedFireBedLeave = nullptr; static bool s_initialized = false; @@ -321,13 +325,13 @@ static void __cdecl NativeSetFallDistance(int entityId, float distance) } } -// double[11] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension } +// double[13] = { x, y, z, health, maxHealth, fallDistance, gameMode, walkSpeed, yaw, pitch, dimension, isSleeping, sleepTimer } static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData) { auto player = FindPlayer(entityId); if (!player) { - memset(outData, 0, 11 * sizeof(double)); + memset(outData, 0, 13 * sizeof(double)); outData[3] = 20.0; outData[4] = 20.0; outData[7] = 0.1; @@ -345,6 +349,8 @@ static void __cdecl NativeGetPlayerSnapshot(int entityId, double *outData) outData[8] = (double)player->yRot; outData[9] = (double)player->xRot; outData[10] = (double)player->dimension; + outData[11] = player->isSleeping() ? 1.0 : 0.0; + outData[12] = (double)player->getSleepTimer(); } static void __cdecl NativeBroadcastMessage(const char *utf8, int len) @@ -1303,6 +1309,8 @@ void Initialize() ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerTeleport", (void **)&s_managedFirePlayerTeleport); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FirePlayerPortal", (void **)&s_managedFirePlayerPortal); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireInventoryClick", (void **)&s_managedFireInventoryClick); + ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedEnter", (void **)&s_managedFireBedEnter); + ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"FireBedLeave", (void **)&s_managedFireBedLeave); if (!ok) { @@ -2110,4 +2118,18 @@ int FireInventoryClick(int entityId, int slot, int button, int clickType) return s_managedFireInventoryClick(entityId, slot, button, clickType, nativeContainerType, containerSize, titleUtf8.empty() ? nullptr : titleUtf8.data(), (int)titleUtf8.size()); } + +bool FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ) +{ + if (!s_initialized || !s_managedFireBedEnter) + return false; + return s_managedFireBedEnter(entityId, dimId, bedX, bedY, bedZ) != 0; +} + +void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ) +{ + if (!s_initialized || !s_managedFireBedLeave) + return; + s_managedFireBedLeave(entityId, dimId, bedX, bedY, bedZ); +} } // namespace FourKitBridge diff --git a/Minecraft.Server/FourKitBridge.h b/Minecraft.Server/FourKitBridge.h index 8fd62737d..3025c4130 100644 --- a/Minecraft.Server/FourKitBridge.h +++ b/Minecraft.Server/FourKitBridge.h @@ -78,4 +78,6 @@ namespace FourKitBridge int cause, double *outToX, double *outToY, double *outToZ); int FireInventoryClick(int entityId, int slot, int button, int clickType); + bool FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ); + void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ); }