diff --git a/Obsidian.API/_Interfaces/IPlayer.cs b/Obsidian.API/_Interfaces/IPlayer.cs index 33eb6ec05..f7a4e90ec 100644 --- a/Obsidian.API/_Interfaces/IPlayer.cs +++ b/Obsidian.API/_Interfaces/IPlayer.cs @@ -1,4 +1,5 @@ -using System.Net; +using Obsidian.API._Types; +using System.Net; namespace Obsidian.API; @@ -21,6 +22,8 @@ public interface IPlayer : ILiving public IPAddress? ClientIP { get; } public Gamemode Gamemode { get; set; } + public PlayerAbility Abilities { get; } + public bool Sleeping { get; set; } public bool InHorseInventory { get; set; } diff --git a/Obsidian.API/_Types/PlayerAbilities.cs b/Obsidian.API/_Types/PlayerAbilities.cs new file mode 100644 index 000000000..e2d9d23c7 --- /dev/null +++ b/Obsidian.API/_Types/PlayerAbilities.cs @@ -0,0 +1,11 @@ +namespace Obsidian.API._Types; + +[Flags] +public enum PlayerAbility +{ + None = 0x00, + Invulnerable = 0x01, + Flying = 0x02, + AllowFlying = 0x04, + CreativeMode = 0x08 +} diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index 7ce8f1bf1..dbabbcf13 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -1,6 +1,7 @@ // This would be saved in a file called [playeruuid].dat which holds a bunch of NBT data. // https://wiki.vg/Map_Format using Microsoft.Extensions.Logging; +using Obsidian.API._Types; using Obsidian.API.Events; using Obsidian.API.Utilities; using Obsidian.Nbt; @@ -62,7 +63,24 @@ public sealed partial class Player : Living, IPlayer public IBlock? LastClickedBlock { get; internal set; } - public Gamemode Gamemode { get; set; } + public Gamemode Gamemode + { + get => gamemode; + set + { + gamemode = value; + + Abilities = Gamemode switch + { + Gamemode.Creative => PlayerAbility.CreativeMode | PlayerAbility.AllowFlying | PlayerAbility.Invulnerable, + Gamemode.Spectator => PlayerAbility.AllowFlying | PlayerAbility.Invulnerable, + Gamemode.Survival or Gamemode.Adventure or Gamemode.Hardcore => PlayerAbility.None, + _ => throw new ArgumentOutOfRangeException(nameof(Gamemode), Gamemode, "Unknown gamemode.") + }; + } + } + + public PlayerAbility Abilities { get; internal set; } public IScoreboard? CurrentScoreboard { get; set; } @@ -115,6 +133,8 @@ internal set public IPAddress? ClientIP => (client.RemoteEndPoint as IPEndPoint)?.Address; + private Gamemode gamemode = Gamemode.Creative; + [SetsRequiredMembers] internal Player(Guid uuid, string username, Client client, World world) { diff --git a/Obsidian/Net/Packets/Play/Clientbound/PlayerAbilitiesPacket.cs b/Obsidian/Net/Packets/Play/Clientbound/PlayerAbilitiesPacket.cs new file mode 100644 index 000000000..211f133ee --- /dev/null +++ b/Obsidian/Net/Packets/Play/Clientbound/PlayerAbilitiesPacket.cs @@ -0,0 +1,58 @@ +using Obsidian.API._Types; +using Obsidian.Entities; + +namespace Obsidian.Net.Packets.Play.Clientbound; + +public class PlayerAbilitiesPacket : IClientboundPacket, IServerboundPacket +{ + public PlayerAbility Abilities { get; set; } = PlayerAbility.None; + + public float FlyingSpeed { get; set; } = 0.05F; + + public float FieldOfViewModifier { get; set; } = 0.1F; + + public int Id { get; } + + public PlayerAbilitiesPacket(bool toClient) + { + Id = toClient ? 0x36 : 0x20; + } + + public void Serialize(MinecraftStream stream) + { + using var packetStream = new MinecraftStream(); + packetStream.WriteByte((byte)Abilities); + packetStream.WriteFloat(FlyingSpeed); + packetStream.WriteFloat(FieldOfViewModifier); + + stream.Lock.Wait(); + stream.WriteVarInt(Id.GetVarIntLength() + (int)packetStream.Length); + stream.WriteVarInt(Id); + packetStream.Position = 0; + packetStream.CopyTo(stream); + stream.Lock.Release(); + } + + public void Populate(MinecraftStream stream) + { + Abilities = (PlayerAbility) stream.ReadByte(); + } + + public void Populate(byte[] data) + { + using var stream = new MinecraftStream(data); + Populate(stream); + } + + public async ValueTask HandleAsync(Server server, Player player) + { + if (Abilities.HasFlag(PlayerAbility.Flying) + && !Abilities.HasFlag(PlayerAbility.AllowFlying) + && player.Gamemode is not Gamemode.Creative or Gamemode.Spectator) + { + await player.KickAsync("Cheating is not allowed!"); + } + + player.Abilities |= Abilities; + } +}