namespace Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Command;
using Minecraft.Server.FourKit.Experimental;
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 ulong _playerRawOnlineXUID;
private ulong _playerRawOfflineXUID;
private string? _displayName;
private bool _sneaking;
private bool _sprinting;
private bool _allowFlight;
private bool _sleepingIgnored;
private PlayerConnection _connection;
internal bool IsOnline { get; set; }
internal Player(int entityId, string name)
{
SetEntityIdInternal(entityId);
SetEntityTypeInternal(EntityType.PLAYER);
SetNameInternal(name);
IsOnline = true;
_playerInventory._holder = this;
_connection = new PlayerConnection(this);
}
///
public override EntityType getType() => EntityType.PLAYER;
///
public override EntityType GetType() => EntityType.PLAYER;
///
public override bool teleport(Location location)
{
int targetDimId = location.LocationWorld?.getDimensionId() ?? getLocation().LocationWorld?.getDimensionId() ?? 0;
NativeBridge.TeleportEntity?.Invoke(getEntityId(), targetDimId, location.X, location.Y, location.Z);
SetLocation(location);
return true;
}
///
/// Experimental. Gets the player's , which can be used
/// to send raw packet data directly to the client.
///
/// The player's connection.
public PlayerConnection getConnection() => _connection;
///
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;
///
/// Experimental. Gets the raw online XUID (Xbox User ID) for this player.
/// The online XUID is used for guests.
///
/// The raw online XUID value.
public ulong getRawOnlineXUID() => _playerRawOnlineXUID;
///
/// Experimental. Gets the raw offline XUID (Xbox User ID) for this player.
/// The offline XUID is the main XUID used by the client.
///
/// The raw offline XUID value.
public ulong getRawOfflineXUID() => _playerRawOfflineXUID;
///
/// Gets the player's estimated ping in milliseconds.
/// This value represents a weighted average of the response time to application layer ping packets sent. This value does not represent the network round trip time and as such may have less granularity and be impacted by other sources. For these reasons it should not be used for anti-cheat purposes. Its recommended use is only as a qualitative indicator of connection quality.
///
/// The player's estimated ping in milliseconds.
public int getPing()
{
if (NativeBridge.GetPlayerLatency == null)
return -1;
return NativeBridge.GetPlayerLatency(getEntityId());
}
///
/// 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);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
public void spawnParticle(Particle particle, Location location, int count)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
public void spawnParticle(Particle particle, double x, double y, double z, int count)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, Location location, int count, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, data);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, double x, double y, double z, int count, T? data)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, data);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, data);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, data);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The extra data for this particle, depends on the particle used (normally speed).
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The extra data for this particle, depends on the particle used (normally speed).
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The location to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The extra data for this particle, depends on the particle used (normally speed).
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, data);
}
///
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
///
/// The particle to spawn.
/// The position on the x axis to spawn at.
/// The position on the y axis to spawn at.
/// The position on the z axis to spawn at.
/// The number of particles.
/// The maximum random offset on the X axis.
/// The maximum random offset on the Y axis.
/// The maximum random offset on the Z axis.
/// The extra data for this particle, depends on the particle used (normally speed).
/// The data to use for the particle or null.
/// The type of the particle data.
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
}
private void spawnParticleInternal(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, object? data)
{
if (NativeBridge.SpawnParticle == null)
return;
int particleId = (int)particle;
if (data is ItemStack itemStack &&
(particle == Particle.ITEM_CRACK || particle == Particle.BLOCK_CRACK))
{
int id = itemStack.getTypeId();
int aux = itemStack.getDurability();
particleId = (int)particle | ((id & 0x0FFF) << 8) | (aux & 0xFF);
}
NativeBridge.SpawnParticle(getEntityId(), particleId,
(float)x, (float)y, (float)z,
(float)offsetX, (float)offsetY, (float)offsetZ,
(float)extra, count);
}
// INTERNAL
internal void SetSaturationInternal(float saturation) => _saturation = saturation;
internal void SetWalkSpeedInternal(float walkSpeed) => _walkSpeed = walkSpeed;
internal void SetPlayerUniqueIdInternal(Guid id) => _playerUniqueId = id;
internal void SetPlayerRawOnlineXUIDInternal(ulong xuid) => _playerRawOnlineXUID = xuid;
internal void SetPlayerRawOfflineXUIDInternal(ulong xuid) => _playerRawOfflineXUID = xuid;
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;
}