From 4b7966ffda3b6ad34d355148ddccbe2959061730 Mon Sep 17 00:00:00 2001 From: Sarah Bradley Date: Mon, 18 Dec 2023 19:51:26 -0800 Subject: Networked Mice!! Also combined Spritesheet + Username packets into one UserInfo packet. --- Drawable.cs | 8 ++++++-- Main.cs | 6 +++--- Mouse.cs | 23 +++++++++++---------- MouseLocal.cs | 29 ++++++++++++++++++++++++++ MouseNet.cs | 35 ++++++++++++++++++++++++++++++++ Net.cs | 5 +---- NetClient.cs | 16 ++++++--------- NetServer.cs | 65 ++++++++++++++++++++++++++++------------------------------- Oneko.cs | 18 ++++++----------- OnekoLocal.cs | 6 +++--- OnekoNet.cs | 4 +++- 11 files changed, 135 insertions(+), 80 deletions(-) create mode 100644 MouseLocal.cs create mode 100644 MouseNet.cs diff --git a/Drawable.cs b/Drawable.cs index 0f74a1c..f01c478 100644 --- a/Drawable.cs +++ b/Drawable.cs @@ -6,6 +6,7 @@ namespace OnekoOnline; abstract class Drawable : IDisposable { protected int DrawOrder = 0; + public bool Visible = true; public Vector2 Position { get => _position; @@ -16,13 +17,16 @@ abstract class Drawable : IDisposable public Vector2 Size; public float Rotation = 0f; - public static readonly List Drawables = []; + public static readonly HashSet Drawables = []; + + public Drawable() => Drawables.Add(this); public static void DrawAll() { float delta = Raylib.GetFrameTime(); foreach (Drawable drawable in Drawables.OrderBy(d => d.Position.Y + d.DrawOrder*1000)) { drawable?.Update(delta); + if (drawable == null || !drawable.Visible) continue; drawable?.Draw(); } } @@ -36,5 +40,5 @@ abstract class Drawable : IDisposable public abstract void Update(float delta); - public abstract void Dispose(); + public virtual void Dispose() => Drawables.Remove(this); } \ No newline at end of file diff --git a/Main.cs b/Main.cs index 0194169..79db391 100644 --- a/Main.cs +++ b/Main.cs @@ -28,7 +28,7 @@ static class OnekoOnline Raylib.HideCursor(); OnekoLocal LocalOneko = new(); - Mouse LocalMouse = new(); + MouseLocal LocalMouse = new(); RenderTexture2D RenderTexture = Raylib.LoadRenderTexture(WindowX, WindowY); @@ -53,8 +53,8 @@ static class OnekoOnline Raylib.BeginTextureMode(RenderTexture); Raylib.ClearBackground(Color.GRAY); - Raylib.DrawTextEx(DefaultFont, "こんにちは", new(32,32), 11, 0, Color.WHITE); - Raylib.DrawText("Oneko Online", 12, 12, 8, Color.WHITE); + Raylib.DrawTextEx(DefaultFont, "こんにちは", new(17,18), 11, 0, Color.WHITE); + Raylib.DrawText("Oneko Online", 10, 9, 8, Color.WHITE); Drawable.DrawAll(); diff --git a/Mouse.cs b/Mouse.cs index bec6eec..d316ea2 100644 --- a/Mouse.cs +++ b/Mouse.cs @@ -1,32 +1,33 @@ -using System.Numerics; -using OnekoOnline.Net; using Raylib_cs; +using System.Collections.ObjectModel; +using System.Numerics; namespace OnekoOnline; -class Mouse : Drawable +abstract class Mouse : Drawable { + public string Name = "Mouse"; + Texture2D CursorTex = Raylib.LoadTexture("misc/cursor.png"); - public Mouse() - { - DrawOrder = 100; - Drawables.Add(this); - } + protected static List allMice = []; + public static ReadOnlyCollection AllMice => allMice.AsReadOnly(); - public override void Update(float delta) + public Mouse() : base() { - Position = Raylib.GetMousePosition()/OnekoOnline.WindowScale; + DrawOrder = 100; + allMice.Add(this); } public override void Draw() { - if (Raylib.IsCursorOnScreen()) Raylib.DrawTexture(CursorTex, (int)Position.X, (int)Position.Y, Color.WHITE); } public override void Dispose() { Raylib.UnloadTexture(CursorTex); + allMice.Remove(this); + base.Dispose(); } } \ No newline at end of file diff --git a/MouseLocal.cs b/MouseLocal.cs new file mode 100644 index 0000000..6a84731 --- /dev/null +++ b/MouseLocal.cs @@ -0,0 +1,29 @@ +using System.Numerics; +using OnekoOnline.Net; +using LiteNetLib.Utils; +using Raylib_cs; + +namespace OnekoOnline; + +class MouseLocal : Mouse +{ + public MouseLocal() : base() + { + Client.UserConnected += user => { + MouseNet NewNetMouse = new(user); + }; + } + + public override void Update(float delta) + { + Visible = Raylib.IsCursorOnScreen() && Raylib.IsWindowFocused(); + Position = Raylib.GetMousePosition()/OnekoOnline.WindowScale; + + if (Visible && OnekoOnline.Client!.Connected) { + NetDataWriter writer = new(); + writer.Put(new PacketInfo(PacketType.MousePosition, OnekoOnline.Client.Id)); + writer.Put(Position); + OnekoOnline.Client?.ConnectedServer.Send(writer, LiteNetLib.DeliveryMethod.Unreliable); + } + } +} \ No newline at end of file diff --git a/MouseNet.cs b/MouseNet.cs new file mode 100644 index 0000000..9521a25 --- /dev/null +++ b/MouseNet.cs @@ -0,0 +1,35 @@ +using OnekoOnline.Net; + +namespace OnekoOnline; + +class MouseNet : Mouse +{ + readonly User MyUser; + + float InvisibleTimer; + + public MouseNet(User user) : base() + { + MyUser = user; + Name = user.Username!; + + Client.UserDisconnected += disconnectedUser => { + if (disconnectedUser == MyUser) Dispose(); + }; + + Client.PacketRecived += (reader, user, packetType) => { + if (user != MyUser || packetType != PacketType.MousePosition) return; + + Position = reader.GetVector2(); + InvisibleTimer = 0f; + }; + + Client.ServerDisconnected += Dispose; + } + + public override void Update(float delta) + { + InvisibleTimer += delta; + Visible = InvisibleTimer < 0.3f; + } +} \ No newline at end of file diff --git a/Net.cs b/Net.cs index 83b6b26..3fa5f8a 100644 --- a/Net.cs +++ b/Net.cs @@ -52,8 +52,7 @@ public enum PacketType : byte { MousePosition, OnekoTargetPosition, - OnekoSpritesheet, - Username, + UserInfo, UserId, Disconnect, Invalid @@ -103,6 +102,4 @@ class User(int id) //Oneko Stuff public byte[]? SpriteSheet; public string? Username; - - public bool ExchangedData => SpriteSheet != null && Username != null; } \ No newline at end of file diff --git a/NetClient.cs b/NetClient.cs index ce7d8fe..da287df 100644 --- a/NetClient.cs +++ b/NetClient.cs @@ -36,12 +36,9 @@ class Client Console.WriteLine("Connected to the Server!"); NetDataWriter writer = new(); - writer.Put(new PacketInfo(PacketType.OnekoSpritesheet, Id)); - writer.Put(OnekoLocal.Instance!.SpriteSheet.Serialize()); - peer.Send(writer, DeliveryMethod.ReliableUnordered); - - writer.ResetWithInfo(new PacketInfo(PacketType.Username, Id)); + writer.Put(new PacketInfo(PacketType.UserInfo, Id)); writer.Put(UserName); + writer.PutBytesWithLength(OnekoLocal.Instance!.SpriteSheet.Serialize()); peer.Send(writer, DeliveryMethod.ReliableUnordered); }; @@ -74,14 +71,13 @@ class Client return; } - else if (info.Type == PacketType.OnekoSpritesheet) from.SpriteSheet = reader.GetRemainingBytes(); - else if (info.Type == PacketType.Username) from.Username = reader.GetString(); - - if (from.ExchangedData && !from.Initialized) { - //Announce user connection + else if (info.Type == PacketType.UserInfo) { + from.Username = reader.GetString(); + from.SpriteSheet = reader.GetBytesWithLength(); Console.WriteLine($"User {from.Username} joined!"); from.Initialized = true; UserConnected?.Invoke(from); + return; } NetDataReader newReader = new(reader.GetRemainingBytes()); diff --git a/NetServer.cs b/NetServer.cs index c2c626a..5ff35f1 100644 --- a/NetServer.cs +++ b/NetServer.cs @@ -45,44 +45,41 @@ class Server ServerUser user = users[fromPeer.Id]; //Size limits for packet types - if (info.Type == PacketType.OnekoSpritesheet && dataReader.AvailableBytes > 30000) return; - else if (info.Type != PacketType.OnekoSpritesheet && dataReader.AvailableBytes > 500) return; - - if (info.Type == PacketType.OnekoSpritesheet) user.SpriteSheet = dataReader.GetRemainingBytes(); - else if (info.Type == PacketType.Username) user.Username = dataReader.GetString(64); + if (info.Type == PacketType.UserInfo && dataReader.AvailableBytes > 40000) return; + else if (info.Type != PacketType.UserInfo && dataReader.AvailableBytes > 500) return; NetDataWriter writer = new(); - if (user.ExchangedData && !user.Initialized) { - //Send ID - writer.ResetWithInfo(new PacketInfo(PacketType.UserId, -1)); - writer.Put(user.Id); - fromPeer.Send(writer, DeliveryMethod.ReliableUnordered); - user.Initialized = true; - - Console.WriteLine($"{fromPeer.EndPoint} is {user.Username}!"); + if (info.Type == PacketType.UserInfo) { + user.Username = dataReader.GetString(); + if (user.Username.Length > 40) user.Username = user.Username[0..40]; //Clamp username length + user.SpriteSheet = dataReader.GetBytesWithLength(); - foreach (ServerUser toSend in users.Values) - { - if (!toSend.ExchangedData || toSend == user) continue; - - //Send all current users spritesheets to this user. - writer.ResetWithInfo(new PacketInfo(PacketType.OnekoSpritesheet, toSend.Id), toSend.SpriteSheet!.Length); - writer.Put(toSend.SpriteSheet); + if (!user.Initialized) { + //Send ID + writer.ResetWithInfo(new PacketInfo(PacketType.UserId, -1)); + writer.Put(user.Id); fromPeer.Send(writer, DeliveryMethod.ReliableUnordered); - - writer.ResetWithInfo(new PacketInfo(PacketType.Username, toSend.Id)); - writer.Put(toSend.Username); - fromPeer.Send(writer, DeliveryMethod.ReliableUnordered); - - //Send all current users this users spritesheet - writer.ResetWithInfo(new PacketInfo(PacketType.OnekoSpritesheet, user.Id), user.SpriteSheet!.Length); - writer.Put(user.SpriteSheet); - toSend.Peer.Send(writer, DeliveryMethod.ReliableUnordered); - - writer.ResetWithInfo(new PacketInfo(PacketType.Username, user.Id)); - writer.Put(user.Username); - toSend.Peer.Send(writer, DeliveryMethod.ReliableUnordered); + user.Initialized = true; + + Console.WriteLine($"{fromPeer.EndPoint} is {user.Username}!"); + + foreach (ServerUser toSend in users.Values) + { + if (!toSend.Initialized || toSend == user) continue; + + //Send all current users spritesheets to this user. + writer.ResetWithInfo(new PacketInfo(PacketType.UserInfo, toSend.Id)); + writer.Put(toSend.Username); + writer.PutBytesWithLength(toSend.SpriteSheet); + fromPeer.Send(writer, DeliveryMethod.ReliableUnordered); + + //Send all current users this users spritesheet + writer.ResetWithInfo(new PacketInfo(PacketType.UserInfo, user.Id)); + writer.Put(user.Username); + writer.PutBytesWithLength(user.SpriteSheet); + toSend.Peer.Send(writer, DeliveryMethod.ReliableUnordered); + } } } @@ -90,7 +87,7 @@ class Server writer.ResetWithInfo(new PacketInfo(info.Type, fromPeer.Id)); writer.Put(dataReader.GetRemainingBytes()); foreach (ServerUser toSend in users.Values) { - if (!toSend.ExchangedData || toSend == user) continue; + if (!toSend.Initialized || toSend == user) continue; toSend.Peer.Send(writer, DeliveryMethod); } } diff --git a/Oneko.cs b/Oneko.cs index 361a6e2..742d8c0 100644 --- a/Oneko.cs +++ b/Oneko.cs @@ -19,7 +19,7 @@ class Oneko : Drawable public string Name = "Oneko"; - public Oneko() + public Oneko() : base() { Size = new(32,32); Position = new(320/2, 240/2); @@ -32,27 +32,21 @@ class Oneko : Drawable Console.WriteLine("Path to spritesheet was invalid, using the default."); SpriteSheet = Bitmap.FromPNGMemory(EmbeddedResources.GetResource("nekos.oneko.png")); } - - Drawables.Add(this); } - public Oneko(Bitmap spriteSheet) + public Oneko(Bitmap spriteSheet) : base() { Size = new(32,32); Position = new(0, 0); SpriteSheet = spriteSheet; - - Drawables.Add(this); } public override void Draw() { //Nametag - if (Vector2.Distance(Raylib.GetMousePosition()/OnekoOnline.WindowScale, Position) < 20f) { - Vector2 NametagPosition = new(Position.X-(Name.Length*3)+4, Position.Y-28); - Raylib.DrawTextEx(OnekoOnline.DefaultFont, Name, NametagPosition+Directions.Down, 11, 0, Color.BLACK); //Shadow - Raylib.DrawTextEx(OnekoOnline.DefaultFont, Name, NametagPosition, 11, 0, Color.WHITE); - } + Vector2 NametagPosition = new(Position.X-(Name.Length*3)+4, Position.Y-28); + Raylib.DrawTextEx(OnekoOnline.DefaultFont, Name, NametagPosition+Directions.Down, 11, 0, Color.BLACK); //Shadow + Raylib.DrawTextEx(OnekoOnline.DefaultFont, Name, NametagPosition, 11, 0, Color.WHITE); //The neko Raylib.DrawTexturePro(SpriteSheet.Texture, Animation.GetFrame(Frame), new Rectangle(Position.X, Position.Y, Size.X, Size.Y), Size/2, Rotation, Color.WHITE); @@ -88,7 +82,7 @@ class Oneko : Drawable public override void Dispose() { SpriteSheet.Dispose(); - Drawables.Remove(this); + base.Dispose(); } struct OnekoAnimation(Rectangle frame1, Rectangle frame2) diff --git a/OnekoLocal.cs b/OnekoLocal.cs index b33f376..00eaf82 100644 --- a/OnekoLocal.cs +++ b/OnekoLocal.cs @@ -16,15 +16,15 @@ class OnekoLocal : Oneko Instance ??= this; Client.UserConnected += OnekoNet.SpawnNetNeko; - Client.ServerDisconnected += OnekoNet.DisconnectAll; Name = Client.UserName; } public override void OnekoUpdate() { - if (Raylib.IsWindowFocused() && Raylib.IsCursorOnScreen()) TargetPosition = Raylib.GetMousePosition()/OnekoOnline.WindowScale; - else TargetPosition = new Vector2(320/2, 240/2); + Mouse? NearestMouse = Mouse.AllMice.Where(m => m.Visible).MinBy(m => Vector2.Distance(m.Position, Position)); + if (NearestMouse != null) TargetPosition = NearestMouse.Position; + else TargetPosition = new Vector2(Math.Clamp((Id+1)*40, 20, 300), 240/2); if (OnekoOnline.Client!.Connected) { NetDataWriter writer = new(); diff --git a/OnekoNet.cs b/OnekoNet.cs index b7cd595..27d6187 100644 --- a/OnekoNet.cs +++ b/OnekoNet.cs @@ -24,11 +24,13 @@ class OnekoNet : Oneko if (packetType == PacketType.OnekoTargetPosition) TargetPosition = reader.GetVector2(); }; + + Client.ServerDisconnected += Dispose; } public static void SpawnNetNeko(User user) { - if (user.ExchangedData && !NetNekos.ContainsKey(user.Id)) { + if (!NetNekos.ContainsKey(user.Id)) { Bitmap spriteSheet = Bitmap.Deserialize(user.SpriteSheet); NetNekos.Add(user.Id, new OnekoNet(spriteSheet, user)); } -- cgit