namespace Minecraft.Server.FourKit.Entity; using System.Runtime.InteropServices; using Minecraft.Server.FourKit.Command; using Minecraft.Server.FourKit.Inventory; using Minecraft.Server.FourKit.Net; /// /// Represents a player connected to the server. /// public class Player : HumanEntity, OfflinePlayer, CommandSender { private float _saturation = 5.0f; private float _walkSpeed = 0.2f; private float _exhaustion; private int _foodLevel = 20; private int _level; private float _exp; private int _totalExperience; private Guid _playerUniqueId; private string? _displayName; private bool _sneaking; private bool _sprinting; private bool _allowFlight; private bool _sleepingIgnored; internal bool IsOnline { get; set; } internal Player(int entityId, string name) { SetEntityIdInternal(entityId); SetEntityTypeInternal(EntityType.PLAYER); SetNameInternal(name); IsOnline = true; _playerInventory._holder = this; } /// public override EntityType getType() => EntityType.PLAYER; /// public override EntityType GetType() => EntityType.PLAYER; /// public override bool teleport(Location location) { NativeBridge.TeleportPlayer?.Invoke(getEntityId(), location.X, location.Y, location.Z); SetLocation(location); return true; } /// public Player? getPlayer() => IsOnline ? this : null; /// /// Gets the "friendly" name to display of this player. /// This may include color. If no custom display name has been set, /// this returns the player's . /// /// The display name. public string getDisplayName() => _displayName ?? getName(); /// /// Sets the "friendly" name to display of this player. /// /// The display name, or null to reset to . public void setDisplayName(string? name) { _displayName = name; } /// public bool isOnline() => IsOnline; /// /// Returns the UUID that uniquely identifies this player across sessions. /// This is the player-specific UUID, not the entity UUID. /// /// The player's unique identifier. public new Guid getUniqueId() => _playerUniqueId; /// /// Gets the player's current saturation level. /// Saturation acts as a buffer before hunger begins to deplete. /// /// The current saturation level. public float getSaturation() => _saturation; /// /// Gets the current allowed speed that a client can walk. /// The default value is 0.2. /// /// The current walk speed. public float getWalkSpeed() => _walkSpeed; /// /// Sets the speed at which a client will walk. /// This calls into the native server to apply the change. /// /// The new walk speed. public void setWalkSpeed(float value) { _walkSpeed = value; NativeBridge.SetWalkSpeed?.Invoke(getEntityId(), value); } /// /// Returns if the player is in sneak mode. /// /// True if player is in sneak mode. public bool isSneaking() => _sneaking; /// /// Gets whether the player is sprinting or not. /// /// True if player is sprinting. public bool isSprinting() => _sprinting; /// /// Sets whether the player is ignored as not sleeping. If everyone is /// either sleeping or has this flag set, then time will advance to the /// next day. If everyone has this flag set but no one is actually in /// bed, then nothing will happen. /// /// Whether to ignore. public void setSleepingIgnored(bool isSleeping) { _sleepingIgnored = isSleeping; NativeBridge.SetSleepingIgnored?.Invoke(getEntityId(), isSleeping ? 1 : 0); } /// /// Returns whether the player is sleeping ignored. /// /// Whether player is ignoring sleep. public bool isSleepingIgnored() => _sleepingIgnored; /// /// Play a sound for a player at the location. /// This function will fail silently if Location or Sound are null. /// /// The location to play the sound. /// The sound to play. /// The volume of the sound. /// The pitch of the sound. public void playSound(Location location, Sound sound, float volume, float pitch) { if (location == null) return; NativeBridge.PlaySound?.Invoke(getEntityId(), (int)sound, location.X, location.Y, location.Z, volume, pitch); } /// /// Determines if the Player is allowed to fly via jump key double-tap /// like in creative mode. /// /// True if the player is allowed to fly. public bool getAllowFlight() => _allowFlight; /// /// Sets if the Player is allowed to fly via jump key double-tap like /// in creative mode. /// /// If flight should be allowed. public void setAllowFlight(bool flight) { _allowFlight = flight; NativeBridge.SetAllowFlight?.Invoke(getEntityId(), flight ? 1 : 0); } /// public void sendMessage(string message) { if (string.IsNullOrEmpty(message) || NativeBridge.SendMessage == null) return; if (message.Length > FourKit.MAX_CHAT_LENGTH) message = message[..FourKit.MAX_CHAT_LENGTH]; IntPtr ptr = Marshal.StringToCoTaskMemUTF8(message); try { NativeBridge.SendMessage(getEntityId(), ptr, System.Text.Encoding.UTF8.GetByteCount(message)); } finally { Marshal.FreeCoTaskMem(ptr); } } /// public void sendMessage(string[] messages) { foreach (var msg in messages) sendMessage(msg); } /// /// Kicks player with the default reason. /// public void kickPlayer() { NativeBridge.KickPlayer?.Invoke(getEntityId(), (int)DisconnectReason.KICKED); } /// /// Bans the player by UID with the specified reason and disconnects them. /// /// The ban reason. /// true if the ban was applied successfully. public bool banPlayer(string reason) { if (NativeBridge.BanPlayer == null) return false; IntPtr ptr = Marshal.StringToCoTaskMemUTF8(reason ?? string.Empty); try { int byteLen = System.Text.Encoding.UTF8.GetByteCount(reason ?? string.Empty); return NativeBridge.BanPlayer(getEntityId(), ptr, byteLen) != 0; } finally { Marshal.FreeCoTaskMem(ptr); } } /// /// Bans the player's IP address with the specified reason. /// /// The ban reason. /// true if the IP ban was applied successfully. public bool banPlayerIp(string reason) { if (NativeBridge.BanPlayerIp == null) return false; IntPtr ptr = Marshal.StringToCoTaskMemUTF8(reason ?? string.Empty); try { int byteLen = System.Text.Encoding.UTF8.GetByteCount(reason ?? string.Empty); return NativeBridge.BanPlayerIp(getEntityId(), ptr, byteLen) != 0; } finally { Marshal.FreeCoTaskMem(ptr); } } /// /// Gets the socket address of this player. /// /// The player's socket address, or null if the address could not be determined. public InetSocketAddress? getAddress() { if (NativeBridge.GetPlayerAddress == null) return null; const int ipBufSize = 64; IntPtr ipBuf = Marshal.AllocCoTaskMem(ipBufSize); IntPtr portBuf = Marshal.AllocCoTaskMem(sizeof(int)); try { int result = NativeBridge.GetPlayerAddress(getEntityId(), ipBuf, ipBufSize, portBuf); if (result == 0) return null; string? ip = Marshal.PtrToStringAnsi(ipBuf); int port = Marshal.ReadInt32(portBuf); if (string.IsNullOrEmpty(ip)) return null; return new InetSocketAddress(new InetAddress(ip), port); } finally { Marshal.FreeCoTaskMem(ipBuf); Marshal.FreeCoTaskMem(portBuf); } } /// /// Gets the players current experience level. /// /// Current experience level. public int getLevel() => _level; /// /// Sets the players current experience level. /// /// New experience level. public void setLevel(int level) { _level = level; NativeBridge.SetLevel?.Invoke(getEntityId(), level); } /// /// Gets the players current experience points towards the next level. /// This is a percentage value. 0 is "no progress" and 1 is "next level". /// /// Current experience points. public float getExp() => _exp; /// /// Sets the players current experience points towards the next level. /// This is a percentage value. 0 is "no progress" and 1 is "next level". /// /// New experience points. public void setExp(float exp) { _exp = exp; NativeBridge.SetExp?.Invoke(getEntityId(), exp); } /// /// Gives the player the amount of experience specified. /// /// Exp amount to give. public void giveExp(int amount) { NativeBridge.GiveExp?.Invoke(getEntityId(), amount); } /// /// Gives the player the amount of experience levels specified. /// Levels can be taken by specifying a negative amount. /// /// Amount of experience levels to give or take. public void giveExpLevels(int amount) { NativeBridge.GiveExpLevels?.Invoke(getEntityId(), amount); } /// /// Gets the players current exhaustion level. /// Exhaustion controls how fast the food level drops. While you have a /// certain amount of exhaustion, your saturation will drop to zero, and /// then your food will drop to zero. /// /// Exhaustion level. public float getExhaustion() => _exhaustion; /// /// Sets the players current exhaustion level. /// /// Exhaustion level. public void setExhaustion(float value) { _exhaustion = value; NativeBridge.SetExhaustion?.Invoke(getEntityId(), value); } /// /// Sets the players current saturation level. /// /// Saturation level. public void setSaturation(float value) { _saturation = value; NativeBridge.SetSaturation?.Invoke(getEntityId(), value); } /// /// Gets the players current food level. /// /// Food level. public int getFoodLevel() => _foodLevel; /// /// Sets the players current food level. /// /// New food level. public void setFoodLevel(int value) { _foodLevel = value; NativeBridge.SetFoodLevel?.Invoke(getEntityId(), value); } // INTERNAL internal void SetSaturationInternal(float saturation) => _saturation = saturation; internal void SetWalkSpeedInternal(float walkSpeed) => _walkSpeed = walkSpeed; internal void SetPlayerUniqueIdInternal(Guid id) => _playerUniqueId = id; internal void SetSneakingInternal(bool sneaking) => _sneaking = sneaking; internal void SetSprintingInternal(bool sprinting) => _sprinting = sprinting; internal void SetAllowFlightInternal(bool allowFlight) => _allowFlight = allowFlight; internal void SetSleepingIgnoredInternal(bool ignored) => _sleepingIgnored = ignored; internal void SetLevelInternal(int level) => _level = level; internal void SetExpInternal(float exp) => _exp = exp; internal void SetTotalExperienceInternal(int totalExp) => _totalExperience = totalExp; internal void SetFoodLevelInternal(int foodLevel) => _foodLevel = foodLevel; internal void SetExhaustionInternal(float exhaustion) => _exhaustion = exhaustion; }