├── .gitignore ├── global.json ├── Client ├── Avatar │ ├── BodyType.cs │ ├── AvatarLook.cs │ ├── PartType.cs │ ├── AvatarState.cs │ ├── AvatarStats.cs │ └── BodyPart.cs ├── Gui │ ├── Enums │ │ ├── GuiPriority.cs │ │ ├── GridLayout.cs │ │ └── NoticeType.cs │ ├── Components │ │ ├── Buttons │ │ │ ├── ButtonState.cs │ │ │ └── TextureButton.cs │ │ ├── Label.cs │ │ ├── Decal.cs │ │ ├── IUIComponent.cs │ │ ├── Checkbox.cs │ │ └── TextBox.cs │ └── Panels │ │ ├── IUIPanel.cs │ │ ├── Modal.cs │ │ ├── StaticPanel.cs │ │ ├── FramePanel.cs │ │ ├── StackPanel.cs │ │ └── ButtonPanel.cs ├── Managers │ ├── IManager.cs │ ├── ServiceLocator.cs │ ├── InputManager.cs │ ├── NxManager.cs │ ├── WorldManager.cs │ ├── NetworkManager.cs │ ├── ActorManager.cs │ └── UIManager.cs ├── Program.cs ├── Scene │ ├── WorldState.cs │ ├── WorldInfo.cs │ └── IWorld.cs ├── Actors │ ├── ActorType.cs │ ├── ActorLayer.cs │ ├── ActorCompare.cs │ └── IActor.cs ├── Dockerfile ├── Client.csproj ├── Net │ ├── PacketProcessor.cs │ ├── OutPacket.cs │ ├── RequestOps.cs │ ├── InPacket.cs │ └── ResponseOps.cs ├── AppConfig.cs ├── Application.cs ├── Map │ ├── MapLoader.cs │ ├── Background.cs │ └── MapObject.cs ├── NX │ ├── NxReader.cs │ ├── NxNode.cs │ └── NxFile.cs └── UpdateLog.txt ├── README.md ├── .dockerignore ├── MapleSyrup.sln └── Changelog.txt /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | FNA/ 7 | .idea -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /Client/Avatar/BodyType.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Avatar; 2 | 3 | public enum BodyType 4 | { 5 | Body, 6 | Head, 7 | Arm, 8 | Face 9 | } -------------------------------------------------------------------------------- /Client/Gui/Enums/GuiPriority.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Gui.Enums; 2 | 3 | public enum GuiPriority 4 | { 5 | Below, 6 | Normal, 7 | Above 8 | } -------------------------------------------------------------------------------- /Client/Managers/IManager.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Managers; 2 | 3 | public interface IManager 4 | { 5 | void Initialize(); 6 | void Shutdown(); 7 | } -------------------------------------------------------------------------------- /Client/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using Client; 4 | using Client.NX; 5 | 6 | using var game = new Application(); 7 | game.Run(); -------------------------------------------------------------------------------- /Client/Scene/WorldState.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Scene; 2 | 3 | public enum WorldState 4 | { 5 | StartUp, 6 | Login, 7 | TransitionToField, 8 | Field, 9 | TransitionToLogin, 10 | Exit 11 | } -------------------------------------------------------------------------------- /Client/Gui/Enums/GridLayout.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Gui.Enums; 2 | 3 | public enum GridLayout 4 | { 5 | HorizontalLeft, 6 | HorizontalRight, 7 | VerticalUp, 8 | VerticalDown, 9 | LeftStack, 10 | RightStack 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MapleSyrup 2 | MapleSyrup, for the lack of a better name, is a C# MapleStory client emulator. 3 | 4 | # Project Rework 5 | The project is currently undergoing heavy modifications, for more information please see the **rework** branch 6 | -------------------------------------------------------------------------------- /Client/Actors/ActorType.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Actors; 2 | 3 | public enum ActorType 4 | { 5 | None, 6 | Background, 7 | StaticMapObject, 8 | AnimatedMapObject, 9 | Tile, 10 | Reactor, 11 | Npc, 12 | Player, 13 | Item, 14 | } -------------------------------------------------------------------------------- /Client/Gui/Components/Buttons/ButtonState.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Gui.Components.Buttons; 2 | 3 | /// 4 | /// Represents the state of a button. 5 | /// 6 | public enum ButtonState 7 | { 8 | Normal, 9 | MouseOver, 10 | Pressed, 11 | Disabled 12 | } -------------------------------------------------------------------------------- /Client/Avatar/AvatarLook.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Avatar; 2 | 3 | public class AvatarLook 4 | { 5 | public byte Gender { get; set; } = 0; 6 | public byte SkinId { get; set; } = 0; 7 | public int Face { get; set; } = 0; 8 | public int Hair { get; set; } = 30000; 9 | public Dictionary Equipment { get; set; } = new(); 10 | } -------------------------------------------------------------------------------- /Client/Actors/ActorLayer.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Actors; 2 | 3 | public enum ActorLayer 4 | { 5 | Background = 0, 6 | TileLayer0 = 1, 7 | TileLayer1 = 2, 8 | TileLayer2 = 3, 9 | TileLayer3 = 4, 10 | TileLayer4 = 5, 11 | TileLayer5 = 6, 12 | TileLayer6 = 7, 13 | TileLayer7 = 8, 14 | Effects = 9, 15 | Foreground = 10, 16 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Client/Gui/Enums/NoticeType.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Gui.Enums; 2 | 3 | public enum NoticeType 4 | { 5 | // There are way too many of these. 6 | NameRestriction = 0, 7 | DeleteCharacter = 1, 8 | NotRegistered = 2, 9 | IncorrectPass = 3, 10 | IncorrectUser = 4, 11 | NameTaken = 5, // 8 can also be used 12 | AvailableName = 6, 13 | ReturnToLogin = 7, 14 | NameTaken2 = 8, 15 | CharSlotsFilled = 9, 16 | } 17 | 18 | public enum NoticeBackground 19 | { 20 | Information = 0, 21 | Error = 1, 22 | EnterText = 2, 23 | } -------------------------------------------------------------------------------- /Client/Actors/ActorCompare.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Actors; 2 | 3 | public class ActorCompare : IComparer 4 | { 5 | public int Compare(IActor? x, IActor? y) 6 | { 7 | switch (x) 8 | { 9 | case null when y == null: 10 | return -2; 11 | case null: 12 | return -2; 13 | } 14 | 15 | if (y == null) return 1; 16 | 17 | if (x.Layer < y.Layer) 18 | return -1; 19 | if (x.Layer == y.Layer) 20 | return x.Z < y.Z ? -1 : 1; 21 | return 1; 22 | } 23 | } -------------------------------------------------------------------------------- /Client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base 2 | WORKDIR /app 3 | 4 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 5 | WORKDIR /src 6 | COPY ["MSClient/MSClient.csproj", "MSClient/"] 7 | RUN dotnet restore "MSClient/MSClient.csproj" 8 | COPY . . 9 | WORKDIR "/src/MSClient" 10 | RUN dotnet build "MSClient.csproj" -c Release -o /app/build 11 | 12 | FROM build AS publish 13 | RUN dotnet publish "MSClient.csproj" -c Release -o /app/publish /p:UseAppHost=false 14 | 15 | FROM base AS final 16 | WORKDIR /app 17 | COPY --from=publish /app/publish . 18 | ENTRYPOINT ["dotnet", "MSClient.dll"] 19 | -------------------------------------------------------------------------------- /Client/Managers/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Managers; 2 | 3 | public static class ServiceLocator 4 | { 5 | private static readonly HashSet _managers = new(); 6 | 7 | public static void Register(T manager) where T : IManager 8 | { 9 | manager.Initialize(); 10 | _managers.Add(manager); 11 | } 12 | 13 | public static T Get() where T : IManager 14 | { 15 | return (T)_managers.First(x => x is T); 16 | } 17 | 18 | public static void Release() 19 | { 20 | foreach (var manager in _managers) 21 | { 22 | manager.Shutdown(); 23 | } 24 | 25 | _managers.Clear(); 26 | } 27 | } -------------------------------------------------------------------------------- /Client/Avatar/PartType.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Avatar; 2 | 3 | [Flags] 4 | public enum PartType 5 | { 6 | Body = 0, 7 | Head = 1, 8 | Face = 2, 9 | Arm = 3, 10 | Hair = 4, 11 | Cap = 5, 12 | Coat = 6, 13 | Pants = 7, 14 | Shoes = 8, 15 | Weapon = 9, 16 | Glove = 10, 17 | Cape = 11, 18 | Shield = 12, 19 | Accessory1 = 13, 20 | Accessory2 = 14, 21 | Accessory3 = 15, 22 | Longcoat = 16, 23 | TamingMob = 17, 24 | Afterimage = 18, 25 | TamingMob0 = 19, 26 | FWeapon = 20, 27 | Accessory4 = 21, 28 | Accessory5 = 22, 29 | Accessory6 = 23, 30 | } -------------------------------------------------------------------------------- /Client/Avatar/AvatarState.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Avatar; 2 | 3 | public enum AvatarState 4 | { 5 | Stand1 = 0, 6 | Stand2 = 1, 7 | Alert = 2, 8 | Walk1 = 3, 9 | Walk2 = 4, 10 | Jump = 5, 11 | Shoot1 = 6, 12 | Shoot2 = 7, 13 | ShootF = 8, 14 | Sit = 9, 15 | StabO1 = 10, 16 | StabO2 = 11, 17 | StabOF = 12, 18 | StabT1 = 13, 19 | StabT2 = 14, 20 | Swing01 = 15, 21 | Swing02 = 16, 22 | Swing03 = 17, 23 | SwingP1 = 18, 24 | SwingP2 = 19, 25 | SwingT1 = 20, 26 | SwingT2 = 21, 27 | SwingT3 = 22, 28 | Dead = 23, 29 | Fly = 24, 30 | Ladder = 25, 31 | Rope = 26, 32 | ProneStab = 27, 33 | SwingOF = 28, 34 | SwingPF = 29, 35 | SwingTF = 30, 36 | StabTF = 31, 37 | Prone = 32, 38 | Heal = 33 39 | } -------------------------------------------------------------------------------- /MapleSyrup.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{7305FEAE-54E9-4A90-9D60-0B65DF6E6947}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {7305FEAE-54E9-4A90-9D60-0B65DF6E6947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {7305FEAE-54E9-4A90-9D60-0B65DF6E6947}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {7305FEAE-54E9-4A90-9D60-0B65DF6E6947}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {7305FEAE-54E9-4A90-9D60-0B65DF6E6947}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /Client/Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | enable 6 | enable 7 | Linux 8 | false 9 | net8.0 10 | true 11 | 12 | 13 | 14 | 15 | .dockerignore 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Client/Gui/Panels/IUIPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Enums; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Panels; 7 | 8 | public interface IUIPanel 9 | { 10 | public uint ID { get; init; } 11 | public string Name { get; set; } 12 | public bool Active { get; set; } 13 | public bool Visible { get; set; } 14 | public Vector2 Position { get; internal set; } 15 | public Vector2 ScreenOffset { get; internal set; } 16 | public Rectangle Bounds { get; internal set; } 17 | public Rectangle DstRectangle { get; internal set; } 18 | public GuiPriority Priority { get; } 19 | public List Nodes { get; } 20 | public string TexturePath { get; set; } 21 | public bool Add(IUIComponent node); 22 | public IUIComponent? GetNode(string node); 23 | public void Clear(); 24 | public void Draw(float frameTime); 25 | public void Update(float frameTime); 26 | } -------------------------------------------------------------------------------- /Client/Managers/InputManager.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Raylib_CsLo; 3 | 4 | namespace Client.Managers; 5 | 6 | public class InputManager : IManager 7 | { 8 | public void Initialize() 9 | { 10 | } 11 | 12 | public void Shutdown() 13 | { 14 | } 15 | 16 | public Vector2 MouseToScreen => Raylib.GetMousePosition(); 17 | 18 | public Vector2 MouseToWorld 19 | { 20 | get 21 | { 22 | var world = ServiceLocator.Get(); 23 | return Raylib.GetScreenToWorld2D(MouseToScreen, world.GetCamera()); 24 | } 25 | } 26 | 27 | public Rectangle MouseRec 28 | { 29 | get 30 | { 31 | return new Rectangle(MouseToWorld.X, MouseToWorld.Y, 1, 1); 32 | } 33 | } 34 | 35 | public bool IsKeyDown(KeyboardKey key) => Raylib.IsKeyDown(key); 36 | public bool IsKeyPressed(KeyboardKey key) => Raylib.IsKeyPressed(key); 37 | public bool IsKeyRelease(KeyboardKey key) => Raylib.IsKeyReleased(key); 38 | public bool IsKeyUp(KeyboardKey key) => Raylib.IsKeyUp(key); 39 | public float GetMouseWheel() => Raylib.GetMouseWheelMove(); 40 | } -------------------------------------------------------------------------------- /Client/Avatar/AvatarStats.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Avatar; 2 | 3 | public class AvatarStats 4 | { 5 | public int Id { get; init; } = 0; 6 | public string Name { get; set; } = string.Empty; 7 | public byte Gender { get; set; } = 0; 8 | public byte SkinId { get; set; } = 0; 9 | public int Face { get; set; } = 0; 10 | public int Hair { get; set; } = 30000; 11 | public List PetId { get; set; } = new(3); 12 | public short Level { get; set; } = 1; 13 | public short JobId { get; set; } = 0; 14 | public short Strength { get; set; } = 4; 15 | public short Dexterity { get; set; } = 4; 16 | public short Intelligence { get; set; } = 4; 17 | public short Luck { get; set; } = 4; 18 | public short Hp { get; set; } = 100; 19 | public short MaxHp { get; set; } = 100; 20 | public short Mp { get; set; } = 50; 21 | public short MaxMp { get; set; } = 50; 22 | public short RemainingAp { get; set; } = 0; 23 | public int Exp { get; set; } = 0; 24 | public short Fame { get; set; } = 0; 25 | public int GachaExp { get; set; } = 0; 26 | public int MapId { get; set; } = 10000; 27 | public byte InitialSpawnPoint { get; set; } = 0; 28 | } -------------------------------------------------------------------------------- /Client/Net/PacketProcessor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Client.Managers; 3 | 4 | namespace Client.Net; 5 | 6 | public class PacketProcessor 7 | { 8 | private readonly ConcurrentQueue _packetResponses; 9 | private readonly object _lock; 10 | 11 | public PacketProcessor() 12 | { 13 | _packetResponses = new (); 14 | _lock = new(); 15 | } 16 | 17 | public void Queue(InPacket packet) 18 | { 19 | lock (_lock) 20 | { 21 | _packetResponses.Enqueue(packet); 22 | } 23 | } 24 | 25 | public void ProcessPacketResponses() 26 | { 27 | lock (_lock) 28 | { 29 | while (_packetResponses.Count > 0) 30 | { 31 | _packetResponses.TryDequeue(out var response); 32 | if (response == null) continue; 33 | var actor = ServiceLocator.Get(); 34 | var world = ServiceLocator.Get(); 35 | Console.WriteLine( 36 | $"Processing Packet: {response.Opcode} Data: {BitConverter.ToString(response.Data)}"); 37 | 38 | world.ProcessPackets(response); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Client/Gui/Components/Label.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.NX; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Components; 7 | 8 | public class Label : IUIComponent 9 | { 10 | public IUIPanel Parent { get; init; } 11 | public int ID { get; init; } 12 | public string Name { get; set; } 13 | public Vector2 Position { get; set; } 14 | public Vector2 ScreenOffset { get; set; } 15 | public Rectangle Bounds { get; set; } 16 | public Rectangle DstRectangle { get; set; } 17 | public NxNode? Node { get; init; } 18 | public Action? Callback { get; init; } 19 | public bool Active { get; set; } 20 | public string Text { get; init; } = string.Empty; 21 | public int FontSize { get; init; } = 16; 22 | public Color Color { get; init; } = Raylib.BLACK; 23 | public string TexturePath { get; set; } = string.Empty; 24 | 25 | public void Draw(Vector2 parentPos, float frameTime) 26 | { 27 | ScreenOffset = new Vector2(parentPos.X + Position.X, parentPos.Y + Position.Y); 28 | Raylib.DrawText(Text, ScreenOffset.X, ScreenOffset.Y, FontSize, Color); 29 | } 30 | 31 | public void Update(Vector2 parentPos, float frameTime) 32 | { 33 | } 34 | 35 | public void Clear() 36 | { 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /Client/Gui/Components/Decal.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.NX; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Components; 7 | 8 | public class Decal : IUIComponent 9 | { 10 | public IUIPanel Parent { get; init; } 11 | public int ID { get; init; } 12 | public string Name { get; set; } 13 | public Vector2 Position { get; set; } 14 | public Vector2 ScreenOffset { get; set; } 15 | public Rectangle Bounds { get; set; } 16 | public Rectangle DstRectangle { get; set; } 17 | public NxNode Node { get; init; } 18 | public bool Active { get; set; } 19 | public Texture Texture { get; init; } 20 | public string TexturePath { get; set; } 21 | 22 | public void Clear() 23 | { 24 | Raylib.UnloadTexture(Texture); 25 | } 26 | 27 | public void Draw(Vector2 parentPos, float frameTime) 28 | { 29 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 30 | ScreenOffset = new Vector2(parentPos.X + Position.X, 31 | parentPos.Y + Position.Y); 32 | DstRectangle = new Rectangle( 33 | ScreenOffset.X, 34 | ScreenOffset.Y, 35 | Texture.width * AppConfig.ScaleFactor, 36 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 37 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 38 | } 39 | 40 | public void Update(Vector2 parentPos, float frameTime) 41 | { 42 | } 43 | } -------------------------------------------------------------------------------- /Client/AppConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Client; 2 | 3 | public class AppConfig 4 | { 5 | // Client Settings 6 | public const short VersionMajor = 62; 7 | public const short VersionMinor = 1; 8 | public const string ClientName = "MapleSyrup: Devel"; 9 | public const int OriginalWidth = 800; 10 | public const int OriginalHeight = 600; 11 | public static int ScreenWidth { get; set; } = 800; 12 | public static int ScreenHeight { get; set; } = 600; 13 | public static bool IsFullscreen { get; set; } = false; 14 | public static float BaseAspectRatio => 15 | (float)OriginalWidth / OriginalHeight; 16 | public static float CurrentAspectRatio => 17 | (float)ScreenWidth / ScreenHeight; 18 | public static float ScaleFactor => 19 | Math.Min(ScreenWidth / OriginalWidth, ScreenHeight / OriginalHeight); 20 | public static float ScreenOffsetX => 21 | (ScreenWidth - OriginalWidth * ScaleFactor) / 2; 22 | public static float ScreenOffsetY => 23 | (ScreenHeight - OriginalHeight * ScaleFactor) / 2; 24 | 25 | public static bool CloseWindow { get; set; } = false; 26 | public const bool IsBetaVersion = true; 27 | 28 | public static readonly string GameFilePath = $"D:/v{VersionMajor}"; 29 | public static readonly bool PseudoEditor = false; // This is just used to get better coordinates, since a lot of this are done by hand. 30 | 31 | // Network Settings 32 | public const string ServerIp = "127.0.0.1"; 33 | public const int LoginPort = 8484; 34 | public static readonly int[] ChannelPorts = [7575, 7576]; 35 | } -------------------------------------------------------------------------------- /Client/Gui/Components/IUIComponent.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.NX; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Components; 7 | 8 | public interface IUIComponent 9 | { 10 | public IUIPanel Parent { get; init; } 11 | public int ID { get; init; } 12 | /// 13 | /// Gets or sets the name of the UI Node, this can be used to retrieve it later on. 14 | /// 15 | public string Name { get; set; } 16 | 17 | /// 18 | /// Gets or sets the position of the UI based on the parent container's position. 19 | /// 20 | public Vector2 Position { get; set; } 21 | public Vector2 ScreenOffset { get; internal set; } 22 | 23 | /// 24 | /// Gets the bounds of the UI, set internally only. 25 | /// 26 | public Rectangle Bounds { get; internal set; } 27 | public Rectangle DstRectangle { get; internal set; } 28 | 29 | /// 30 | /// Gets the Node used to retrieve data, if it has one, not required. 31 | /// 32 | public NxNode? Node { get; init; } 33 | public bool Active { get; set; } 34 | public string TexturePath { get; set; } 35 | 36 | /// 37 | /// Draws the UI Node at a specific interval, referencing the parents position. 38 | /// 39 | /// Position of the container 40 | /// Time since laster frame. 41 | void Draw(Vector2 parentPos, float frameTime); 42 | 43 | /// 44 | /// Updates the UI Node at a specific interval, referencing the parents position. 45 | /// 46 | /// Position of the container 47 | /// Time since laster frame. 48 | void Update(Vector2 parentPos, float frameTime); 49 | 50 | /// 51 | /// Clears any assets used within the UI Node. 52 | /// 53 | public void Clear(); 54 | } -------------------------------------------------------------------------------- /Client/Scene/WorldInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.NX; 3 | using Raylib_CsLo; 4 | 5 | namespace Client.Scene; 6 | 7 | public struct WorldInfo 8 | { 9 | private readonly NxNode? _node; 10 | public int Version { get; } 11 | public int Cloud { get; } 12 | public int Town { get; } 13 | public float MobRate { get; } 14 | public string Bgm { get; } 15 | public int ReturnMap { get; } 16 | public string MapDesc { get; } 17 | public int HideMinimap { get; } 18 | public int ForcedReturn { get; } 19 | public int MoveLimit { get; } 20 | public string MapMark { get; } 21 | public int Swim { get; } 22 | public int FieldLimit { get; } 23 | public int VRTop { get; } 24 | public int VRLeft { get; } 25 | public int VRBottom { get; } 26 | public int VRRight { get; } 27 | public int Fly { get; } 28 | public int NoMapCmd { get; } 29 | public string OnFirstUserEnter { get; } 30 | public string OnUserEnter { get; } 31 | 32 | public WorldInfo(NxNode info) 33 | { 34 | _node = info; 35 | Version = _node?.GetInt("version") ?? 0; 36 | Cloud = _node?.GetInt("cloud") ?? 0; 37 | Town = _node?.GetInt("town") ?? 0; 38 | MobRate = (float)(_node?.GetDouble("mobRate") ?? 0); 39 | Bgm = _node?.GetString("bgm") ?? ""; 40 | ReturnMap = _node?.GetInt("returnMap") ?? 0; 41 | MapDesc = _node?.GetString("mapDesc") ?? ""; 42 | HideMinimap = _node?.GetInt("hideMinimap") ?? 0; 43 | ForcedReturn = _node?.GetInt("forcedReturn") ?? 0; 44 | MoveLimit = _node?.GetInt("moveLimit") ?? 0; 45 | MapMark = _node?.GetString("mapMark") ?? ""; 46 | Swim = _node?.GetInt("swim") ?? 0; 47 | FieldLimit = _node?.GetInt("fieldLimit") ?? 0; 48 | VRTop = _node?.GetInt("VRTop") ?? 0; 49 | VRLeft = _node?.GetInt("VRLeft") ?? 0; 50 | VRBottom = _node?.GetInt("VRBottom") ?? 0; 51 | VRRight = _node?.GetInt("VRRight") ?? 0; 52 | Fly = _node?.GetInt("fly") ?? 0; 53 | NoMapCmd = _node?.GetInt("noMapCmd") ?? 0; 54 | OnFirstUserEnter = _node?.GetString("onFirstUserEnter") ?? ""; 55 | OnUserEnter = _node?.GetString("onUserEnter") ?? ""; 56 | } 57 | } -------------------------------------------------------------------------------- /Client/Gui/Panels/Modal.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Enums; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Panels; 7 | 8 | public class Modal : IUIPanel 9 | { 10 | public uint ID { get; init; } 11 | public string Name { get; set; } 12 | public Vector2 Position { get; set; } 13 | public Vector2 ScreenOffset { get; set; } 14 | public Rectangle Bounds { get; set; } 15 | public Rectangle DstRectangle { get; set; } 16 | public GuiPriority Priority { get; init; } 17 | public List Nodes { get; } 18 | public bool Active { get; set; } 19 | public bool Visible { get; set; } 20 | public Texture Texture { get; init; } 21 | public string TexturePath { get; set; } 22 | private object _threadLock; 23 | 24 | public Modal(string texturePath) 25 | { 26 | Nodes = new(); 27 | TexturePath = texturePath; 28 | _threadLock = new(); 29 | } 30 | 31 | public bool Add(IUIComponent node) 32 | { 33 | lock (_threadLock) 34 | { 35 | Nodes.Add(node); 36 | return true; 37 | } 38 | } 39 | 40 | public IUIComponent? GetNode(string node) 41 | { 42 | return Nodes.Find(x => x.Name == node); 43 | } 44 | 45 | public void Clear() 46 | { 47 | foreach (var node in Nodes) 48 | node.Clear(); 49 | } 50 | 51 | public void Draw(float frameTime) 52 | { 53 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 54 | ScreenOffset = new Vector2(Position.X * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 55 | Position.Y * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 56 | DstRectangle = new Rectangle( 57 | ScreenOffset.X, 58 | ScreenOffset.Y, 59 | Texture.width * AppConfig.ScaleFactor, 60 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 61 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 62 | foreach (var node in Nodes) 63 | node.Draw(ScreenOffset, frameTime); 64 | } 65 | 66 | public void Update(float frameTime) 67 | { 68 | foreach (var node in Nodes) 69 | node.Update(ScreenOffset, frameTime); 70 | } 71 | } -------------------------------------------------------------------------------- /Client/Managers/NxManager.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Client.NX; 3 | 4 | namespace Client.Managers; 5 | 6 | public class NxManager : IManager 7 | { 8 | private readonly Dictionary _nx; 9 | 10 | private readonly List _fileName = 11 | [ 12 | "Character", 13 | "Effect", 14 | "Etc", 15 | "Map", 16 | "Mob", 17 | "Npc", 18 | "Quest", 19 | "Reactor", 20 | "Skill", 21 | "Sound", 22 | "TamingMob", 23 | "UI", 24 | ]; 25 | 26 | public NxManager() 27 | { 28 | _nx = new(); 29 | } 30 | 31 | public void Initialize() 32 | { 33 | if (!AppConfig.IsBetaVersion) 34 | { 35 | _nx[MapleFiles.Character] = new NxFile($"{AppConfig.GameFilePath}/Character.nx", NxSaveMode.Save); 36 | _nx[MapleFiles.Map] = new NxFile($"{AppConfig.GameFilePath}/Map.nx", NxSaveMode.Save); 37 | _nx[MapleFiles.UI] = new NxFile($"{AppConfig.GameFilePath}/UI.nx", NxSaveMode.Save); 38 | } 39 | else 40 | { 41 | _nx[MapleFiles.Data] = new NxFile($"{AppConfig.GameFilePath}/Data.nx", NxSaveMode.Save); 42 | } 43 | } 44 | 45 | public NxFile Get(MapleFiles file) 46 | { 47 | return _nx[file]; 48 | } 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public NxNode? GetNode(string node) 52 | { 53 | if (!AppConfig.IsBetaVersion) 54 | { 55 | foreach (var (_, file) in _nx) 56 | { 57 | if (file.StringPool.ContainsKey(node)) 58 | { 59 | return file.GetNode(node); 60 | } 61 | } 62 | } 63 | else 64 | { 65 | foreach (var file in _fileName) 66 | { 67 | if (!_nx[MapleFiles.Data].StringPool.ContainsKey($"{file}/{node}")) continue; 68 | return _nx[MapleFiles.Data].GetNode($"{file}/{node}"); 69 | } 70 | } 71 | 72 | return null; 73 | } 74 | 75 | public void Shutdown() 76 | { 77 | foreach (var (_, nx) in _nx) 78 | nx.Dispose(); 79 | _nx.Clear(); 80 | } 81 | } 82 | 83 | public enum MapleFiles 84 | { 85 | Data = 0, 86 | Character = 1, 87 | Effect = 2, 88 | Etc = 3, 89 | Map = 4, 90 | Mo = 5, 91 | Npc = 6, 92 | Quest = 7, 93 | Reactor = 8, 94 | Skill = 9, 95 | Sound = 10, 96 | TamingMob = 11, 97 | UI = 12, 98 | } -------------------------------------------------------------------------------- /Client/Actors/IActor.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Net; 3 | using Client.NX; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Actors; 7 | 8 | public interface IActor 9 | { 10 | /// 11 | /// Gets the id of the actor, assigned upon initialization. 12 | /// 13 | public int ID { get; init; } 14 | 15 | /// 16 | /// Gets or sets the name of the actor, can be used to find the actor later one. 17 | /// Obituary objects, such as tiles, don't need name. 18 | /// 19 | public string Name { get; set; } 20 | 21 | /// 22 | /// Gets or sets the depth of the actor on the layer the actor is in. 23 | /// 24 | public int Z { get; set; } 25 | 26 | 27 | /// 28 | /// Gets or sets the visibility of the actor. 29 | /// 30 | public bool Visible { get; set; } 31 | 32 | /// 33 | /// Gets or sets the position of the actor. 34 | /// 35 | public Vector2 Position { get; set; } 36 | 37 | /// 38 | /// Gets or sets the origin of the actor. 39 | /// 40 | public Vector2 Origin { get; set; } 41 | 42 | public Vector2 ScreenOffset { get; internal set; } 43 | 44 | /// 45 | /// Gets or sets the layer of the actor, please don't do this carelessly. 46 | /// 47 | public ActorLayer Layer { get; set; } 48 | 49 | /// 50 | /// Gets the actor type, set upon initialization of the actor. 51 | /// 52 | public ActorType ActorType { get; init; } 53 | 54 | /// 55 | /// Gets or sets the bounds of the actor...might change this later. 56 | /// 57 | public Rectangle Bounds { get; internal set; } 58 | 59 | public Rectangle DstRectangle { get; internal set; } 60 | 61 | /// 62 | /// Gets the node the actor will be using for data acquisition. Sets the node internally 63 | /// 64 | public NxNode? Node { get; internal set; } 65 | 66 | /// 67 | /// Draws the actor at a fixed interval. 68 | /// 69 | /// Time since last frame. 70 | void Draw(float frameTime); 71 | 72 | /// 73 | /// Updates the actor at a fixed interval. 74 | /// 75 | /// Time since last frame. 76 | void Update(float frameTime); 77 | 78 | /// 79 | /// Clears all assets used within the actor. 80 | /// 81 | void Clear(); 82 | } -------------------------------------------------------------------------------- /Client/Gui/Components/Checkbox.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.Managers; 4 | using Client.Net; 5 | using Client.NX; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Gui.Components; 9 | 10 | public class Checkbox : IUIComponent 11 | { 12 | public IUIPanel Parent { get; init; } 13 | public int ID { get; init; } 14 | public string Name { get; set; } 15 | public Vector2 Position { get; set; } 16 | public Vector2 ScreenOffset { get; set; } 17 | public Rectangle Bounds { get; set; } 18 | public Rectangle DstRectangle { get; set; } 19 | public NxNode Node { get; init; } 20 | public bool Active { get; set; } 21 | public string TexturePath { get; set; } 22 | 23 | private bool _isChecked; 24 | private List _states; 25 | private CheckboxState _state; 26 | 27 | public Checkbox(NxNode node) 28 | { 29 | Node = node; 30 | TexturePath = node.FullPath; 31 | _isChecked = false; 32 | _states = new(2); 33 | _states.Add(node.GetTexture("0")); 34 | _states.Add(node.GetTexture("1")); 35 | _state = CheckboxState.Unchecked; 36 | } 37 | 38 | public void Draw(Vector2 parentPos, float frameTime) 39 | { 40 | ScreenOffset = new Vector2(parentPos.X + Position.X, parentPos.Y + Position.Y); 41 | Bounds = new Rectangle(0, 0, _states[(int)_state].width, _states[(int)_state].height); 42 | DstRectangle = new Rectangle( 43 | ScreenOffset.X, 44 | ScreenOffset.Y, 45 | _states[(int)_state].width * AppConfig.ScaleFactor, 46 | _states[(int)_state].height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead Raylib.DrawTextureEx(_isChecked ? _check : _noCheck, parentPos + Position, 0f, 1.0f, Raylib.WHITE); 47 | } 48 | 49 | public void Update(Vector2 parentPos, float frameTime) 50 | { 51 | var world = ServiceLocator.Get(); 52 | if (Raylib.CheckCollisionPointRec(Raylib.GetScreenToWorld2D(Raylib.GetMousePosition(), world.GetCamera()), 53 | Bounds)) 54 | { 55 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 56 | { 57 | _isChecked = !_isChecked; 58 | _state = (CheckboxState)Convert.ToInt32(_isChecked); 59 | } 60 | } 61 | } 62 | 63 | public void Clear() 64 | { 65 | Raylib.UnloadTexture(_states[0]); 66 | Raylib.UnloadTexture(_states[1]); 67 | _states.Clear(); 68 | } 69 | } 70 | 71 | public enum CheckboxState 72 | { 73 | Unchecked, 74 | Checked 75 | } -------------------------------------------------------------------------------- /Client/Gui/Panels/StaticPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Components.Buttons; 4 | using Client.Gui.Enums; 5 | using Client.Managers; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Gui.Panels; 9 | 10 | public class StaticPanel : IUIPanel 11 | { 12 | public uint ID { get; init; } 13 | public string Name { get; set; } 14 | public Vector2 Position { get; set; } 15 | public Vector2 ScreenOffset { get; set; } 16 | public Rectangle DstRectangle { get; set; } 17 | public GuiPriority Priority { get; init; } 18 | public List Nodes { get; } = new(); 19 | public bool Active { get; set; } 20 | public Rectangle Bounds { get; set; } 21 | 22 | public bool Visible { get; set; } = true; 23 | public Texture Texture { get; init; } // if that path is empty, create an empty frame using these values. 24 | public string TexturePath { get; set; } = string.Empty; 25 | 26 | public bool Add(IUIComponent node) 27 | { 28 | Nodes.Add(node); 29 | return true; 30 | } 31 | 32 | public IUIComponent? GetNode(string name) 33 | { 34 | return Nodes.Find(x => x.Name == name); 35 | } 36 | 37 | public void Draw(float frameTime) 38 | { 39 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 40 | ScreenOffset = new Vector2(Position.X * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 41 | Position.Y * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 42 | DstRectangle = new Rectangle( 43 | ScreenOffset.X, 44 | ScreenOffset.Y, 45 | Texture.width * AppConfig.ScaleFactor, 46 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 47 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 48 | foreach (var node in Nodes) 49 | node.Draw(ScreenOffset, frameTime); 50 | } 51 | 52 | public void Update(float frameTime) 53 | { 54 | var input = ServiceLocator.Get(); 55 | var ui = ServiceLocator.Get(); 56 | 57 | ui.CheckPanelFocus(input, this, DstRectangle); 58 | foreach (var node in Nodes) 59 | { 60 | if (Active || node is TextureButton) 61 | ui.CheckNodeFocus(input, node, node.DstRectangle); 62 | 63 | if (node.Active || node is TextureButton) 64 | node.Update(ScreenOffset, frameTime); 65 | } 66 | } 67 | 68 | public void Clear() 69 | { 70 | foreach (var node in Nodes) 71 | node.Clear(); 72 | Raylib.UnloadTexture(Texture); 73 | } 74 | } -------------------------------------------------------------------------------- /Client/Net/OutPacket.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | using Client.Managers; 4 | 5 | namespace Client.Net; 6 | 7 | public class OutPacket : IDisposable 8 | { 9 | private byte[] _buffer; 10 | private readonly RequestOps _opcode; 11 | private MemoryStream _stream; 12 | private BinaryWriter _writer; 13 | private readonly NetworkManager _network; 14 | public byte[] Data => _buffer; 15 | public RequestOps Opcode => _opcode; 16 | 17 | public OutPacket(RequestOps opcode) 18 | { 19 | _opcode = opcode; 20 | _stream = new MemoryStream(); 21 | _writer = new BinaryWriter(_stream); 22 | _network = ServiceLocator.Get(); 23 | } 24 | 25 | public void WriteByte(byte val) 26 | { 27 | _writer.Write(val); 28 | } 29 | 30 | public void WriteBytes(byte[] data) 31 | { 32 | _writer.Write(data); 33 | } 34 | 35 | public void WriteShort(short val) 36 | { 37 | _writer.Write(val); 38 | } 39 | 40 | public void WriteInt(int val) 41 | { 42 | _writer.Write(val); 43 | } 44 | 45 | public void WriteLong(long val) 46 | { 47 | _writer.Write(val); 48 | } 49 | 50 | public void WriteDouble(double val) 51 | { 52 | _writer.Write(val); 53 | } 54 | 55 | public void WriteFloat(float val) 56 | { 57 | _writer.Write(val); 58 | } 59 | 60 | public void WriteBoolean(bool val) 61 | { 62 | _writer.Write(val); 63 | } 64 | 65 | public void WriteChar(char val) 66 | { 67 | _writer.Write(val); 68 | } 69 | 70 | public void WriteString(string val) 71 | { 72 | _writer.Write(val); 73 | } 74 | 75 | public void WriteMapleString(string val) 76 | { 77 | WriteShort((short)val.Length); 78 | WriteBytes(Encoding.ASCII.GetBytes(val)); 79 | } 80 | 81 | public void WriteTime(TimeOnly val) 82 | { 83 | _writer.Write(val.ToLongTimeString()); 84 | } 85 | 86 | public void WriteDate(DateOnly val) 87 | { 88 | _writer.Write(val.ToLongDateString()); 89 | } 90 | 91 | public void WriteDateTime(DateTime val) 92 | { 93 | // TODO: is this right? 94 | _writer.Write(val.ToLongDateString()); 95 | _writer.Write(val.ToLongTimeString()); 96 | } 97 | 98 | public void WriteZeroBytes(int length) 99 | { 100 | WriteBytes(new byte[length]); 101 | } 102 | 103 | public void Send() 104 | { 105 | _buffer = _stream.ToArray(); 106 | _network.SendPacket(this); 107 | } 108 | 109 | public void Dispose() 110 | { 111 | _writer.Close(); 112 | _stream.Dispose(); 113 | _writer.Dispose(); 114 | } 115 | } -------------------------------------------------------------------------------- /Client/Gui/Panels/FramePanel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Components.Buttons; 4 | using Client.Gui.Enums; 5 | using Client.Managers; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Gui.Panels; 9 | 10 | public class FramePanel : IUIPanel 11 | { 12 | public uint ID { get; init; } 13 | public string Name { get; set; } 14 | public Vector2 Position { get; set; } 15 | public Vector2 ScreenOffset { get; set; } 16 | public Rectangle Bounds { get; set; } 17 | public Rectangle DstRectangle { get; set; } 18 | public GuiPriority Priority { get; init; } = GuiPriority.Above; 19 | public List Nodes { get; } 20 | public bool Active { get; set; } 21 | public bool Visible { get; set; } 22 | public Texture Texture { get; init; } 23 | public string TexturePath { get; set; } 24 | private object _threadLock; 25 | 26 | public FramePanel(string texturePath) 27 | { 28 | TexturePath = texturePath; 29 | _threadLock = new(); 30 | Nodes = new(); 31 | } 32 | 33 | public bool Add(IUIComponent node) 34 | { 35 | lock (_threadLock) 36 | { 37 | Nodes.Add(node); 38 | return true; 39 | } 40 | } 41 | 42 | public IUIComponent? GetNode(string name) 43 | { 44 | return Nodes.Find(x => x.Name == name); 45 | } 46 | 47 | public void Draw(float frameTime) 48 | { 49 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 50 | ScreenOffset = new Vector2(Position.X * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 51 | Position.Y * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 52 | DstRectangle = new Rectangle( 53 | ScreenOffset.X, 54 | ScreenOffset.Y, 55 | Texture.width * AppConfig.ScaleFactor, 56 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 57 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 58 | foreach (var node in Nodes) 59 | node.Draw(ScreenOffset, frameTime); 60 | } 61 | 62 | public void Update(float frameTime) 63 | { 64 | var input = ServiceLocator.Get(); 65 | var ui = ServiceLocator.Get(); 66 | 67 | ui.CheckPanelFocus(input, this, DstRectangle); 68 | foreach (var node in Nodes) 69 | { 70 | if (Active || node is TextureButton) 71 | ui.CheckNodeFocus(input, node, node.Bounds); 72 | 73 | if (node.Active || node is TextureButton) 74 | node.Update(ScreenOffset, frameTime); 75 | } 76 | } 77 | 78 | public void Clear() 79 | { 80 | foreach (var node in Nodes) 81 | node.Clear(); 82 | Raylib.UnloadTexture(Texture); 83 | } 84 | } -------------------------------------------------------------------------------- /Client/Managers/WorldManager.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Actors; 3 | using Client.Map; 4 | using Client.Net; 5 | using Client.Scene; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Managers; 9 | 10 | public class WorldManager : IManager 11 | { 12 | private IWorld? _world; 13 | private bool _isLoaded; 14 | private MapLoader _loader; 15 | private WorldState _state; 16 | private WorldInfo _info; 17 | 18 | public WorldState State => _state; 19 | public WorldInfo WorldInfo => _info; 20 | 21 | public Texture Minimap { get; internal set; } 22 | public Vector2 WorldSize { get; internal set; } 23 | public Rectangle WorldBounds { get; internal set; } 24 | 25 | 26 | public void Initialize() 27 | { 28 | _isLoaded = false; 29 | _loader = new(); 30 | } 31 | 32 | public void Shutdown() 33 | { 34 | _world?.Clear(); 35 | } 36 | 37 | public void SetState(WorldState state) 38 | { 39 | if (_state == state) return; 40 | _state = state; 41 | } 42 | 43 | public void CreateLogin() 44 | { 45 | _world = new Login(); 46 | var uiNode = ServiceLocator.Get().GetNode("MapLogin.img"); 47 | _info = new WorldInfo(uiNode["info"]); 48 | if (uiNode?.Has("miniMap") ?? false) 49 | { 50 | Minimap = uiNode.GetTexture("canvas"); 51 | WorldSize = new Vector2(uiNode["miniMap"].GetInt("width"), 52 | uiNode["miniMap"].GetInt("height")); 53 | WorldBounds = new Rectangle(WorldSize.X / 2f, WorldSize.Y / 2f, WorldSize.X, WorldSize.Y); 54 | } 55 | else 56 | { 57 | Minimap = new Texture() { width = 1, height = 1 }; 58 | if (WorldInfo.VRTop != 0) 59 | { 60 | WorldBounds = new Rectangle(WorldInfo.VRLeft, WorldInfo.VRTop, WorldInfo.VRRight - WorldInfo.VRLeft, 61 | WorldInfo.VRBottom - WorldInfo.VRTop); 62 | } 63 | } 64 | _isLoaded = _loader.Load(uiNode); 65 | _world.Load(); 66 | } 67 | 68 | public void CreateWorld(int id) 69 | { 70 | // TODO: Fade. 71 | var strId = GetMapId(id); 72 | _world?.Clear(); 73 | _world = null; 74 | var node = ServiceLocator.Get().GetNode($"Map{strId[0]}/{strId}.img"); 75 | _info = new WorldInfo(node["info"]); 76 | _isLoaded = _loader.Load(node); 77 | } 78 | 79 | public IWorld GetWorld() 80 | { 81 | return _world ?? throw new Exception("World is null"); 82 | } 83 | 84 | public Camera2D GetCamera() 85 | { 86 | return _world?.Camera ?? throw new Exception("World is null");; 87 | } 88 | 89 | public void UpdateCamera(Vector2 position) 90 | { 91 | _world?.UpdateCamera(position); 92 | } 93 | 94 | public void UpdateZoom(float zoom) 95 | { 96 | _world.UpdateZoom(zoom); 97 | } 98 | 99 | public void ProcessPackets(InPacket response) 100 | { 101 | _world?.ProcessPacket(response); 102 | } 103 | 104 | public string GetMapId(int id) 105 | { 106 | return id.ToString().PadLeft(9, '0'); 107 | } 108 | } -------------------------------------------------------------------------------- /Client/Application.cs: -------------------------------------------------------------------------------- 1 | using Client.Managers; 2 | using Client.NX; 3 | using Client.Scene; 4 | using Raylib_CsLo; 5 | 6 | namespace Client; 7 | 8 | public class Application : IDisposable 9 | { 10 | public Application() 11 | { 12 | } 13 | 14 | public void Run() 15 | { 16 | Raylib.InitWindow(AppConfig.ScreenWidth, AppConfig.ScreenHeight, AppConfig.ClientName); 17 | Raylib.SetTargetFPS(30); 18 | Raylib.SetTraceLogLevel((int)TraceLogLevel.LOG_NONE); 19 | 20 | LoadContent(); 21 | var net = ServiceLocator.Get(); 22 | var world = ServiceLocator.Get(); 23 | var ui = ServiceLocator.Get(); 24 | 25 | net.Connect(); 26 | world.SetState(WorldState.StartUp); 27 | world.CreateLogin(); 28 | var scene = world.GetWorld() ?? throw new ArgumentNullException("world.GetWorld()"); 29 | while (!Raylib.WindowShouldClose() && !AppConfig.CloseWindow) 30 | { 31 | var frameTime = Raylib.GetFrameTime() * 1000; 32 | net.HandlePacket(); 33 | scene.Update(frameTime); 34 | ui.Update(frameTime); 35 | Raylib.BeginDrawing(); 36 | Raylib.ClearBackground(Raylib.GRAY); 37 | Raylib.BeginMode2D(world.GetCamera()); 38 | scene.Draw(frameTime); 39 | ui.Draw(frameTime); 40 | Raylib.EndMode2D(); 41 | Raylib.DrawFPS(0, 0); 42 | Raylib.EndDrawing(); 43 | 44 | if (Raylib.IsKeyDown(KeyboardKey.KEY_LEFT_ALT) && Raylib.IsKeyPressed(KeyboardKey.KEY_ENTER)) 45 | { 46 | if (!AppConfig.IsFullscreen) 47 | { 48 | AppConfig.ScreenWidth = 1024; 49 | AppConfig.ScreenHeight = 768; 50 | Raylib.SetWindowSize(AppConfig.ScreenWidth, AppConfig.ScreenHeight); 51 | var widthDiff = (float)AppConfig.ScreenWidth / AppConfig.OriginalWidth; 52 | var heightDiff = (float)AppConfig.ScreenHeight / AppConfig.OriginalHeight; 53 | world.GetWorld().UpdateZoom(Math.Min(widthDiff, heightDiff)); 54 | } 55 | else 56 | { 57 | AppConfig.ScreenWidth = 800; 58 | AppConfig.ScreenHeight = 600; 59 | Raylib.SetWindowSize(AppConfig.ScreenWidth, AppConfig.ScreenHeight); 60 | var widthDiff = (float)AppConfig.ScreenWidth / AppConfig.OriginalWidth; 61 | var heightDiff = (float)AppConfig.ScreenHeight / AppConfig.OriginalHeight; 62 | world.GetWorld().UpdateZoom(Math.Min(widthDiff, heightDiff)); 63 | } 64 | 65 | AppConfig.IsFullscreen = !AppConfig.IsFullscreen; 66 | } 67 | } 68 | 69 | net.Disconnect(); 70 | UnloadContent(); 71 | Raylib.CloseWindow(); 72 | } 73 | 74 | private void LoadContent() 75 | { 76 | ServiceLocator.Register(new NxManager()); 77 | ServiceLocator.Register(new NetworkManager()); 78 | ServiceLocator.Register(new ActorManager()); 79 | ServiceLocator.Register(new InputManager()); 80 | ServiceLocator.Register(new UIManager()); 81 | ServiceLocator.Register(new WorldManager()); 82 | } 83 | 84 | private void UnloadContent() 85 | { 86 | ServiceLocator.Release(); 87 | } 88 | 89 | public void Dispose() 90 | { 91 | 92 | } 93 | } -------------------------------------------------------------------------------- /Client/Gui/Components/TextBox.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.Managers; 4 | using Client.NX; 5 | using Raylib_CsLo; 6 | 7 | namespace Client.Gui.Components; 8 | 9 | public class TextBox : IUIComponent 10 | { 11 | public IUIPanel Parent { get; init; } 12 | public int ID { get; init; } 13 | public string Name { get; set; } 14 | public Vector2 Position { get; set; } 15 | public Vector2 ScreenOffset { get; set; } 16 | public Rectangle Bounds { get; set; } 17 | public Rectangle DstRectangle { get; set; } 18 | public NxNode? Node { get; init; } 19 | public Action? Callback { get; init; } 20 | public string Placeholder { get; set; } = string.Empty; 21 | public string Text { get; set; } = string.Empty; 22 | public bool Active { get; set; } 23 | public Vector2 Size { get; init; } 24 | public bool Hidden { get; init; } 25 | public int CharacterLimit { get; init; } 26 | public string TexturePath { get; set; } = string.Empty; 27 | private string _hiddenText = string.Empty; 28 | 29 | public void Clear() 30 | { 31 | } 32 | 33 | public void Draw(Vector2 parentPos, float frameTime) 34 | { 35 | Bounds = new Rectangle(0, 0, Size.X, Size.Y); 36 | ScreenOffset = new Vector2(parentPos.X + Position.X, 37 | parentPos.Y + Position.Y); 38 | DstRectangle = new Rectangle( 39 | ScreenOffset.X, 40 | ScreenOffset.Y, 41 | Size.X * AppConfig.ScaleFactor, 42 | Size.Y * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 43 | 44 | Raylib.DrawRectangleLinesEx(DstRectangle, Active ? 2.0f : 0.0f, Raylib.WHITE); 45 | 46 | if (!Hidden) 47 | { 48 | Raylib.DrawText(Text == string.Empty ? Placeholder : Text, (int)DstRectangle.x + 5, 49 | (int)DstRectangle.y + 10, 16, Raylib.BLACK); 50 | } 51 | else 52 | { 53 | Raylib.DrawText(_hiddenText == string.Empty ? Placeholder : _hiddenText, (int)DstRectangle.x + 5, 54 | (int)DstRectangle.y + 10, 16, Raylib.BLACK); 55 | } 56 | } 57 | 58 | public void Update(Vector2 parentPos, float frameTime) 59 | { 60 | var input = ServiceLocator.Get(); 61 | if (!Active) return; 62 | var keyPressed = Raylib.GetKeyPressed(); 63 | 64 | if (input.IsKeyPressed(KeyboardKey.KEY_BACKSPACE)) 65 | { 66 | var length = Text.Length; 67 | if (length == 0) return; 68 | Text = Text.Remove(length - 1); 69 | if (Hidden) 70 | _hiddenText = _hiddenText.Remove(length - 1); 71 | } 72 | 73 | if (Text.Length >= CharacterLimit) return; 74 | char character; 75 | switch (keyPressed) 76 | { 77 | case >= 1 and <= 92: 78 | if (input.IsKeyDown(KeyboardKey.KEY_LEFT_SHIFT) || input.IsKeyDown(KeyboardKey.KEY_RIGHT_SHIFT)) 79 | { 80 | character = Convert.ToChar(keyPressed); 81 | Text += char.ToUpper(character); 82 | } 83 | else 84 | { 85 | character = Convert.ToChar(keyPressed); 86 | Text += char.ToLower(character); 87 | } 88 | 89 | if (Hidden) 90 | _hiddenText += '*'; 91 | break; 92 | default: 93 | return; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /Client/Map/MapLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Numerics; 3 | using Client.Actors; 4 | using Client.Managers; 5 | using Client.NX; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Map; 9 | 10 | public class MapLoader 11 | { 12 | private NxNode _node; 13 | 14 | public bool Load(NxNode node) 15 | { 16 | _node = node; 17 | var timer = new Stopwatch(); 18 | timer.Start(); 19 | LoadBackground(); 20 | for (var i = 0; i < 8; i++) 21 | { 22 | LoadObjects(i); 23 | LoadTile(i); 24 | } 25 | timer.Stop(); 26 | ServiceLocator.Get().GetWorld().SortLayers(); 27 | Console.WriteLine($"Map fully loaded in: {timer.ElapsedMilliseconds} ms"); 28 | return true; 29 | } 30 | 31 | #region Loading Functions 32 | 33 | private void LoadBackground() 34 | { 35 | var root = _node["back"]; 36 | var actorManager = ServiceLocator.Get(); 37 | 38 | for (var i = 0; i < root.ChildCount; i++) 39 | { 40 | var background = root[$"{i}"]; 41 | actorManager.CreateBackground(background); 42 | } 43 | } 44 | 45 | private void LoadTile(int layer) 46 | { 47 | var actorManager = ServiceLocator.Get(); 48 | var root = _node[$"{layer}"]; 49 | if (root["tile"].ChildCount == 0) 50 | return; 51 | var tS = root.Has("info", out var newSet) ? newSet.GetString("tS") : _node["0"]["info"].GetString("tS"); 52 | var tileSet = ServiceLocator.Get().Get(MapleFiles.Map).GetNode($"Tile/{tS}.img"); 53 | 54 | for (var i = 0; i < root["tile"].ChildCount; i++) 55 | { 56 | var tile = root["tile"][$"{i}"]; 57 | actorManager.CreateTile(tileSet, layer, tile); 58 | } 59 | } 60 | 61 | private void LoadObjects(int layer) 62 | { 63 | var actorManager = ServiceLocator.Get();; 64 | var root = _node[$"{layer}"]; 65 | 66 | for (var i = 0; i < root["obj"].ChildCount; i++) 67 | { 68 | var obj = root["obj"][$"{i}"]; 69 | actorManager.CreateObject(obj, layer); 70 | } 71 | } 72 | 73 | private void LoadPortals() 74 | { 75 | var actorManager = ServiceLocator.Get();; 76 | var root = _node["portal"]; 77 | var helper = ServiceLocator.Get().Get(MapleFiles.Map).GetNode("MapHelper.img"); 78 | var portalNode = helper["portal"]["game"]; 79 | 80 | for (var i = 0; i < root.ChildCount; i++) 81 | { 82 | var portal = root[$"{i}"]; 83 | var portalName = portal.GetString("pn"); 84 | var portalType = portal.GetInt("pt"); 85 | var x = portal.GetInt("x"); 86 | var y = portal.GetInt("y"); 87 | var targetMap = portal.GetInt("tm"); 88 | var targetName = portal.GetString("tn"); 89 | 90 | portal.Has("delay", out _); // TODO: Handle later 91 | portal.Has("script", out _); 92 | portal.Has("hideToolTip", out _); 93 | portal.Has("onlyOnce", out _); 94 | 95 | switch (portalType) 96 | { 97 | case 0: 98 | case 1: 99 | case 2: 100 | { 101 | } 102 | break; 103 | case 9: 104 | { 105 | } 106 | break; 107 | } 108 | } 109 | } 110 | 111 | #endregion 112 | } -------------------------------------------------------------------------------- /Client/NX/NxReader.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Runtime.CompilerServices; 3 | using System.Text; 4 | 5 | namespace Client.NX; 6 | 7 | public unsafe class NxReader : IDisposable 8 | { 9 | private readonly BinaryReader _reader; 10 | private long _position; 11 | private int _nodeCount, _stringCount, _bitmapCount, _audioCount; 12 | private long _nodeBlockOffset, _stringBlockOffset, _bitmapBlockOffset, _audioBlockOffset; 13 | 14 | public int NodeCount => _nodeCount; 15 | public int StringCount => _stringCount; 16 | public int BitmapCount => _bitmapCount; 17 | public int AudioCount => _audioCount; 18 | public long NodeBlockOffset => _nodeBlockOffset; 19 | public long StringBlockOffset => _stringBlockOffset; 20 | public long BitmapBlockOffset => _bitmapBlockOffset; 21 | public long AudioBlockOffset => _audioBlockOffset; 22 | 23 | public NxReader(FileStream stream) 24 | { 25 | _reader = new BinaryReader(stream, Encoding.UTF8); 26 | _position = 0; 27 | ParseHeader(); 28 | } 29 | 30 | private void ParseHeader() 31 | { 32 | Seek(0); 33 | var magic = ReadInt(); 34 | if (magic != 0x34474B50) throw new Exception("Failed to obtain PKG4 Magic"); 35 | _nodeCount = ReadInt(); 36 | _nodeBlockOffset = ReadLong(); 37 | _stringCount = ReadInt(); 38 | _stringBlockOffset = ReadLong(); 39 | _bitmapCount = ReadInt(); 40 | _bitmapBlockOffset = _bitmapCount > 0 ? ReadLong() : 0; 41 | _audioCount = ReadInt(); 42 | _audioBlockOffset = _audioCount > 0 ? ReadLong() : 0; 43 | } 44 | 45 | public Span ReadBytes(int length) 46 | { 47 | var list = _reader.ReadBytes(length); 48 | _position += length; 49 | return list; 50 | } 51 | 52 | public Span ReadBytes(long offset, int length) 53 | { 54 | Seek(offset); 55 | var list = _reader.ReadBytes(length); 56 | _position += length; 57 | return list; 58 | } 59 | 60 | public short ReadShort() 61 | { 62 | var result = _reader.ReadInt16(); 63 | _position += sizeof(short); 64 | 65 | return result; 66 | } 67 | 68 | public int ReadInt() 69 | { 70 | var result = _reader.ReadInt32(); 71 | _position += sizeof(int); 72 | 73 | return result; 74 | } 75 | 76 | public long ReadLong() 77 | { 78 | var result = _reader.ReadInt64(); 79 | _position += sizeof(long); 80 | 81 | return result; 82 | } 83 | 84 | public string ReadString() 85 | { 86 | var length = ReadShort(); 87 | var data = ReadBytes(length); 88 | 89 | return Encoding.UTF8.GetString(data); 90 | } 91 | 92 | public string ReadString(long offset) 93 | { 94 | Seek(offset); 95 | var length = ReadShort(); 96 | var data = ReadBytes(offset + 2, length); 97 | 98 | return Encoding.UTF8.GetString(data); 99 | } 100 | 101 | public void Seek(long position) 102 | { 103 | if (position >= _reader.BaseStream.Length) 104 | throw new Exception("Position is set greater than the file size."); 105 | if (position < 0) 106 | throw new Exception("Position is set less than zero."); 107 | _position = position; 108 | _reader.BaseStream.Position = _position; 109 | } 110 | 111 | public void Skip(long numberOfBytes) 112 | { 113 | if ((_position + numberOfBytes) >= _reader.BaseStream.Length) 114 | throw new Exception("Attempted to skip further than the file size."); 115 | _position += numberOfBytes; 116 | _reader.BaseStream.Position += numberOfBytes; 117 | } 118 | 119 | public void Dispose() 120 | { 121 | _reader.Close(); 122 | _reader.Dispose(); 123 | } 124 | } -------------------------------------------------------------------------------- /Client/Scene/IWorld.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Numerics; 3 | using Client.Actors; 4 | using Client.Avatar; 5 | using Client.Map; 6 | using Client.Net; 7 | using Raylib_CsLo; 8 | 9 | namespace Client.Scene; 10 | 11 | public interface IWorld 12 | { 13 | public List Actors { get; init; } 14 | public ConcurrentQueue ToAdd { get; init; } 15 | public ConcurrentQueue ToRemove { get; init; } 16 | public ConcurrentQueue<(IActor, ActorLayer)> ToShift { get; init; } 17 | 18 | public int ActorCount { get; internal set; } 19 | public bool Cleared { get; internal set; } 20 | 21 | /// 22 | /// Represents a camera used for rendering and viewing a scene. 23 | /// 24 | public Camera2D Camera { get; } 25 | 26 | /// 27 | /// Loads the content for the world. 28 | /// 29 | public void Load(); 30 | 31 | /// 32 | /// Clears all assets used within the world. 33 | /// 34 | public void Clear(); 35 | 36 | /// 37 | /// Draws the world assets at a fixed interval. 38 | /// 39 | /// Time since the last frame. 40 | public void Draw(float frameTime); 41 | 42 | /// 43 | /// Updates the world at a fixed interval. 44 | /// 45 | /// Time since last frame. 46 | public void Update(float frameTime); 47 | 48 | /// 49 | /// Processes any packets the world needs to reactor, otherwise they're ignored. 50 | /// 51 | /// The inbound packet from the network. 52 | public void ProcessPacket(InPacket packet); 53 | 54 | /// 55 | /// Updates the position of the camera based on a specified position. 56 | /// 57 | /// The position the camera needs to reference. 58 | public void UpdateCamera(Vector2 position); 59 | 60 | public void UpdateZoom(float zoom); 61 | 62 | public int GenerateID() 63 | { 64 | return ActorCount++; 65 | } 66 | 67 | public void AddActor(IActor actor) 68 | { 69 | ToAdd.Enqueue(actor); 70 | } 71 | 72 | public void RemoveActor(IActor actor) 73 | { 74 | ToRemove.Enqueue(actor); 75 | } 76 | 77 | public void ShiftActor(IActor actor, ActorLayer layer) 78 | { 79 | ToShift.Enqueue((actor, layer)); 80 | } 81 | 82 | public sealed void ProcessPending() 83 | { 84 | var _lock = new object(); 85 | lock (_lock) 86 | { 87 | // Process actors to be added 88 | while (!ToAdd.IsEmpty) 89 | { 90 | ToAdd.TryDequeue(out var actorToAdd); 91 | Actors.Add(actorToAdd); 92 | Actors.Sort(new ActorCompare()); 93 | ActorCount++; 94 | if (actorToAdd is Player) 95 | { 96 | Console.WriteLine($"Player spawn at {actorToAdd.Position} & Layer: {actorToAdd.Layer}"); 97 | } 98 | } 99 | 100 | // Process actors whose layers need to be changed 101 | while (!ToShift.IsEmpty) 102 | { 103 | (IActor actor, ActorLayer layer) result; 104 | ToShift.TryDequeue(out result); 105 | result.actor.Layer = result.layer; 106 | Actors.Sort(new ActorCompare()); 107 | } 108 | 109 | // Process actors to be removed 110 | while (!ToRemove.IsEmpty) 111 | { 112 | ToRemove.TryDequeue(out var actorToRemove); 113 | Actors.Remove(actorToRemove); 114 | actorToRemove.Clear(); 115 | } 116 | } 117 | } 118 | 119 | public sealed void SortLayers() 120 | { 121 | Actors.Sort(new ActorCompare()); 122 | } 123 | } -------------------------------------------------------------------------------- /Client/Gui/Components/Buttons/TextureButton.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Panels; 3 | using Client.Managers; 4 | using Client.Net; 5 | using Client.NX; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Gui.Components.Buttons; 9 | 10 | public class TextureButton : IUIComponent 11 | { 12 | public IUIPanel Parent { get; init; } 13 | public int ID { get; init; } 14 | public string Name { get; set; } 15 | public Vector2 Position { get; set; } 16 | public Rectangle Bounds { get; set; } 17 | public Rectangle DstRectangle { get; set; } 18 | public NxNode Node { get; init; } 19 | public Action? OnHover { get; init; } 20 | public Action? OnClick { get; init; } 21 | public bool Active { get; set; } 22 | public string TexturePath { get; set; } 23 | public ButtonState State { get; set; } = ButtonState.Normal; 24 | public Vector2 ScreenOffset { get; set; } 25 | 26 | private readonly Dictionary _textures; 27 | 28 | public TextureButton(NxNode node) 29 | { 30 | Node = node; 31 | TexturePath = node.FullPath; 32 | _textures = new(); 33 | 34 | if (Node.Has("normal")) 35 | { 36 | var normal = Node["normal"]; 37 | if (normal.NodeType == NodeType.Bitmap) 38 | _textures[ButtonState.Normal] = Node.GetTexture("normal"); 39 | else 40 | _textures[ButtonState.Normal] = normal.GetTexture("0"); 41 | 42 | } 43 | 44 | if (Node.Has("mouseOver")) 45 | { 46 | var mouseOver = Node["mouseOver"]; 47 | if (mouseOver.NodeType == NodeType.Bitmap) 48 | _textures[ButtonState.MouseOver] = Node.GetTexture("mouseOver"); 49 | else 50 | _textures[ButtonState.MouseOver] = mouseOver.GetTexture("0"); 51 | } 52 | 53 | if (Node.Has("pressed")) 54 | { 55 | var pressed = Node["pressed"]; 56 | _textures[ButtonState.Pressed] = Node["pressed"].GetTexture("0"); 57 | } 58 | 59 | if (Node.Has("disabled")) 60 | { 61 | var disabled = Node["disabled"]; 62 | if (disabled.NodeType == NodeType.Bitmap) 63 | _textures[ButtonState.Disabled] = Node.GetTexture("disabled"); 64 | else 65 | _textures[ButtonState.Disabled] = disabled.GetTexture("0"); 66 | } 67 | } 68 | 69 | public void Draw(Vector2 parentPos, float frameTime) 70 | { 71 | Bounds = new Rectangle(0, 0, _textures[State].width, _textures[State].height); 72 | ScreenOffset = new Vector2(parentPos.X + Position.X, parentPos.Y + Position.Y); 73 | DstRectangle = new Rectangle( 74 | ScreenOffset.X, 75 | ScreenOffset.Y, 76 | _textures[State].width * AppConfig.ScaleFactor, 77 | _textures[State].height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 78 | Raylib.DrawTexturePro(_textures[State], Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 79 | } 80 | 81 | public void Update(Vector2 parentPos, float frameTime) 82 | { 83 | if (State == ButtonState.Disabled) return; 84 | var world = ServiceLocator.Get(); 85 | if (Raylib.CheckCollisionPointRec(Raylib.GetScreenToWorld2D(Raylib.GetMousePosition(), world.GetCamera()), DstRectangle)) 86 | { 87 | if (_textures.ContainsKey(ButtonState.MouseOver)) 88 | State = ButtonState.MouseOver; 89 | OnHover?.Invoke(); 90 | 91 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 92 | { 93 | if (_textures.ContainsKey(ButtonState.Pressed)) 94 | State = ButtonState.Pressed; 95 | OnClick?.Invoke(); 96 | } 97 | } 98 | else 99 | { 100 | State = ButtonState.Normal; 101 | } 102 | } 103 | 104 | public void Clear() 105 | { 106 | foreach (var (_, tex) in _textures) 107 | Raylib.UnloadTexture(tex); 108 | } 109 | 110 | public void ProcessPacket(InPacket response) 111 | { 112 | 113 | } 114 | } -------------------------------------------------------------------------------- /Client/Net/RequestOps.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Net; 2 | 3 | public enum RequestOps 4 | { 5 | LoginPassword = 0x01, 6 | GuestLogin = 0x02, 7 | ServerlistRerequest = 0x04, 8 | CharlistRequest = 0x05, 9 | ServerstatusRequest = 0x06, 10 | SetGender = 0x08, 11 | AfterLogin = 0x09, 12 | RegisterPin = 0x0A, 13 | ServerlistRequest = 0x0B, 14 | PlayerDc = 0x0C, 15 | ViewAllChar = 0x0D, 16 | PickAllChar = 0x0E, 17 | CharSelect = 0x13, 18 | PlayerLoggedin = 0x14, 19 | CheckCharName = 0x15, 20 | CreateChar = 0x16, 21 | DeleteChar = 0x17, 22 | Pong = 0x18, 23 | ClientStart = 0x19, 24 | ClientError = 0x1A, 25 | StrangeData = 0x1B, 26 | Relog = 0x1C, 27 | RegisterPic = 0x1D, 28 | CharSelectWithPic = 0x1E, 29 | ChangeMap = 0x26, 30 | ChangeChannel = 0x27, 31 | EnterCashshop = 0x28, 32 | MovePlayer = 0x29, 33 | CancelChair = 0x2A, 34 | UseChair = 0x2B, 35 | CloseRangeAttack = 0x2C, 36 | RangedAttack = 0x2D, 37 | MagicAttack = 0x2E, 38 | TouchMonsterAttack = 0x2F, 39 | TakeDamage = 0x30, 40 | GeneralChat = 0x31, 41 | CloseChalkboard = 0x32, 42 | FaceExpression = 0x33, 43 | UseItemeffect = 0x34, 44 | UseDeathitem = 0x35, 45 | MonsterBookCover = 0x39, 46 | NpcTalk = 0x3A, 47 | RemoteStore = 0x3B, 48 | NpcTalkMore = 0x3C, 49 | NpcShop = 0x3D, 50 | Storage = 0x3E, 51 | HiredMerchantRequest = 0x3F, 52 | FredrickAction = 0x40, 53 | DueyAction = 0x41, 54 | ItemSort = 0x45, 55 | ItemSort2 = 0x46, 56 | ItemMove = 0x47, 57 | UseItem = 0x48, 58 | CancelItemEffect = 0x49, 59 | UseSummonBag = 0x4B, 60 | PetFood = 0x4C, 61 | UseMountFood = 0x4D, 62 | ScriptedItem = 0x4E, 63 | UseCashItem = 0x4F, 64 | 65 | UseCatchItem = 0x51, 66 | UseSkillBook = 0x52, 67 | UseTeleportRock = 0x54, 68 | UseReturnScroll = 0x55, 69 | UseUpgradeScroll = 0x56, 70 | DistributeAp = 0x57, 71 | AutoDistributeAp = 0x58, 72 | HealOverTime = 0x59, 73 | DistributeSp = 0x5A, 74 | SpecialMove = 0x5B, 75 | CancelBuff = 0x5C, 76 | SkillEffect = 0x5D, 77 | MesoDrop = 0x5E, 78 | GiveFame = 0x5F, 79 | CharInfoRequest = 0x61, 80 | SpawnPet = 0x62, 81 | CancelDebuff = 0x63, 82 | ChangeMapSpecial = 0x64, 83 | UseInnerPortal = 0x65, 84 | TrockAddMap = 0x66, 85 | Report = 0x6A, 86 | QuestAction = 0x6B, 87 | SkillMacro = 0x6E, 88 | SpouseChat = 0x6F, 89 | UseFishingItem = 0x70, 90 | MakerSkill = 0x71, 91 | UseRemote = 0x74, 92 | Partychat = 0x77, 93 | Whisper = 0x78, 94 | Messenger = 0x7A, 95 | PlayerInteraction = 0x7B, 96 | PartyOperation = 0x7C, 97 | DenyPartyRequest = 0x7D, 98 | GuildOperation = 0x7E, 99 | DenyGuildRequest = 0x7F, 100 | AdminCommand = 0x80, 101 | AdminLog = 0x81, 102 | BuddylistModify = 0x82, 103 | NoteAction = 0x83, 104 | UseDoor = 0x85, 105 | ChangeKeymap = 0x87, 106 | RingAction = 0x89, 107 | WeddingAction = 0x8A, 108 | OpenFamily = 0x92, 109 | AddFamily = 0x93, 110 | AcceptFamily = 0x96, 111 | UseFamily = 0x97, 112 | AllianceOperation = 0x98, 113 | BbsOperation = 0x9B, 114 | EnterMts = 0x9C, 115 | UseSolomonItem = 0x9D, 116 | UseGachaExp = 0x9E, 117 | ClickGuide = 0xA2, 118 | AranComboCounter = 0xA3, 119 | MovePet = 0xA7, 120 | PetChat = 0xA8, 121 | PetCommand = 0xA9, 122 | PetLoot = 0xAA, 123 | PetAutoPot = 0xAB, 124 | PetExcludeItems = 0xAC, 125 | MoveSummon = 0xAF, 126 | SummonAttack = 0xB0, 127 | DamageSummon = 0xB1, 128 | Beholder = 0xB2, 129 | MoveLife = 0xBC, 130 | AutoAggro = 0xBD, 131 | MobDamageMobFriendly = 0xC0, 132 | MonsterBomb = 0xC1, 133 | MobDamageMob = 0xC2, 134 | NpcAction = 0xC5, 135 | ItemPickup = 0xCA, 136 | DamageReactor = 0xCD, 137 | TouchingReactor = 0xCE, 138 | TempSkill = 0xCF, 139 | Mapletv = 0xFFFE, 140 | Snowball = 0xD3, 141 | LeftKnockback = 0xD4, 142 | Coconut = 0xD5, 143 | MonsterCarnival = 0xDA, 144 | PartySearchRegister = 0xDC, 145 | PartySearchStart = 0xDE, 146 | PlayerUpdate = 0xDF, 147 | CheckCash = 0xE4, 148 | CashshopOperation = 0xE5, 149 | CouponCode = 0xE6, 150 | OpenItemui = 0xEB, 151 | CloseItemui = 0xEC, 152 | UseItemui = 0xED, 153 | MtsOperation = 0xFD, 154 | UseMaplelife = 0xFE, 155 | UseHammer = 0x104 156 | } -------------------------------------------------------------------------------- /Client/Managers/NetworkManager.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | using Client.Net; 3 | 4 | namespace Client.Managers; 5 | 6 | public class NetworkManager : IManager 7 | { 8 | private Socket _socket; 9 | private object _lock; 10 | private Thread _thread; 11 | private readonly int _loginPort = 8484; 12 | private readonly List _channelPorts = [7575, 7576]; 13 | private byte[] _buffer; 14 | private PacketProcessor _processor; 15 | 16 | public NetworkManager() 17 | { 18 | _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 19 | _lock = new(); 20 | _processor = new PacketProcessor(); 21 | _thread = new Thread(BeginConnect) 22 | { 23 | IsBackground = true 24 | }; 25 | } 26 | 27 | public void Initialize() 28 | { 29 | 30 | } 31 | 32 | public void Connect() 33 | { 34 | _thread.Start(); 35 | } 36 | 37 | public void Disconnect() 38 | { 39 | _thread.Join(); 40 | _socket.Disconnect(false); 41 | } 42 | 43 | private void BeginConnect() 44 | { 45 | _socket.BeginConnect("127.0.0.1", _loginPort, OnClientConnected, _socket); 46 | } 47 | 48 | private void OnClientConnected(IAsyncResult ar) 49 | { 50 | var socket = ar.AsyncState as Socket; 51 | socket?.EndConnect(ar); 52 | 53 | if ((bool)socket?.Connected) 54 | { 55 | Console.WriteLine("Successfully Connected To Login Server."); 56 | 57 | _buffer = new byte [6]; 58 | _socket.BeginReceive(_buffer, 0, 6, SocketFlags.None, OnHeaderReceived, _socket); 59 | } 60 | else 61 | { 62 | _socket.Disconnect(false); 63 | _socket.Dispose(); 64 | } 65 | } 66 | 67 | private void OnHeaderReceived(IAsyncResult ar) 68 | { 69 | var socket = ar.AsyncState as Socket; 70 | 71 | if (socket == null || !socket.Connected) 72 | { 73 | return; 74 | } 75 | 76 | if (socket?.ReceiveBufferSize < 6) 77 | { 78 | _buffer = new byte [6]; 79 | _socket.BeginReceive(_buffer, 0, 6, SocketFlags.None, OnHeaderReceived, _socket); 80 | return; 81 | } 82 | 83 | var stream = new MemoryStream(_buffer); 84 | var reader = new BinaryReader(stream); 85 | var packetLength = reader.ReadInt32(); 86 | var packetIdentifier = reader.ReadUInt16(); 87 | var response = new InPacket((ResponseOps)packetIdentifier, packetLength); 88 | Console.WriteLine($"Packet Received: {response.Opcode}"); 89 | _buffer = new byte[packetLength]; 90 | _socket.BeginReceive(_buffer, 0, packetLength, SocketFlags.None, OnDataReceived, response); 91 | } 92 | 93 | private void OnDataReceived(IAsyncResult ar) 94 | { 95 | var packet = ar.AsyncState as InPacket; 96 | 97 | if (packet == null || packet.PacketLength <= 0) 98 | { 99 | _buffer = new byte [6]; 100 | _socket.BeginReceive(_buffer, 0, 6, SocketFlags.None, OnHeaderReceived, _socket); 101 | return; 102 | } 103 | 104 | Console.WriteLine($"Processing Packet: {packet.Opcode}"); 105 | packet.SetData(_buffer); 106 | _processor.Queue(packet); 107 | _buffer = new byte [6]; 108 | _socket.BeginReceive(_buffer, 0, 6, SocketFlags.None, OnHeaderReceived, _socket); 109 | } 110 | 111 | public void SendPacket(OutPacket request) 112 | { 113 | if (request.Data.Length <= 0) return; 114 | byte[] packetSize = BitConverter.GetBytes(request.Data.Length); 115 | byte[] opcode = BitConverter.GetBytes((short)request.Opcode); 116 | byte[] header = new byte[6]; 117 | packetSize.CopyTo(header, 0); 118 | opcode.CopyTo(header, 4); 119 | //Console.WriteLine($"Sending Packet: {request.Opcode} to server, with length of {request.Data.Length}"); 120 | //Console.WriteLine($"HEX DUMP: {BitConverter.ToString(request.Data)}"); 121 | _socket.Send(header); 122 | _socket.Send(request.Data); 123 | } 124 | 125 | public void HandlePacket() 126 | { 127 | _processor.ProcessPacketResponses(); 128 | } 129 | 130 | public void Shutdown() 131 | { 132 | _socket.Disconnect(false); 133 | _socket.Dispose(); 134 | } 135 | } -------------------------------------------------------------------------------- /Client/Map/Background.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Actors; 3 | using Client.Managers; 4 | using Client.Net; 5 | using Client.NX; 6 | using Raylib_CsLo; 7 | 8 | namespace Client.Map; 9 | 10 | public class Background : IActor 11 | { 12 | private byte _currentFrame; 13 | private int _delay = 150; 14 | public int ID { get; init; } 15 | public string Name { get; set; } = string.Empty; 16 | public int Z { get; set; } 17 | public bool Visible { get; set; } = true; 18 | public Vector2 Position { get; set; } 19 | public Vector2 Origin { get; set; } 20 | public Vector2 ScreenOffset { get; set; } 21 | public ActorLayer Layer { get; set; } 22 | public ActorType ActorType { get; init; } = ActorType.Background; 23 | 24 | public Rectangle Bounds { get; set; } 25 | public Rectangle DstRectangle { get; set; } 26 | public int Cx { get; init; } 27 | public int Cy { get; init; } 28 | public int Rx { get; init; } 29 | public int Ry { get; init; } 30 | public int BackgroundType { get; init; } 31 | public List Frames { get; init; } = new(); 32 | public int FrameCount { get; init; } 33 | public bool Animated { get; init; } 34 | 35 | public NxNode Node { get; set; } 36 | public string TexturePath { get; set; } = String.Empty; 37 | public int Width { get; internal set; } 38 | public int Height { get; internal set; } 39 | 40 | public void Draw(float frameTime) 41 | { 42 | var frame = Frames[_currentFrame]; 43 | if (FrameCount > 1) Origin = Node[$"{_currentFrame}"].GetVector("origin"); 44 | Bounds = new Rectangle(0, 0, frame.width, frame.height); 45 | ScreenOffset = new Vector2((Position.X - Origin.X) * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 46 | (Position.Y - Origin.Y) * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 47 | DstRectangle = new Rectangle( 48 | ScreenOffset.X, 49 | ScreenOffset.Y, 50 | frame.width * AppConfig.ScaleFactor, 51 | frame.height * AppConfig.ScaleFactor); 52 | 53 | switch (BackgroundType) 54 | { 55 | case 0: // Normal. 56 | case 4: // Horizontal Scrolling (Only gets updated) 57 | Raylib.DrawTexturePro(frame, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 58 | break; 59 | case 1: // HorizontalTiled 60 | var world = ServiceLocator.Get(); 61 | DstRectangle = new Rectangle( 62 | ScreenOffset.X, 63 | ScreenOffset.Y, 64 | world.WorldBounds.width + frame.width * AppConfig.ScaleFactor, 65 | frame.height * AppConfig.ScaleFactor); 66 | Raylib.DrawTextureTiled(frame, Bounds, DstRectangle, Vector2.Zero, 0f, 1.0f, Raylib.WHITE); 67 | break; 68 | } 69 | } 70 | 71 | public void Update(float frameTime) 72 | { 73 | Animate(frameTime); 74 | ShiftBackground(frameTime); 75 | } 76 | 77 | private void Animate(float frameTime) 78 | { 79 | if (!Animated) 80 | return; 81 | 82 | if (_delay <= 0) { 83 | _currentFrame++; 84 | if (_currentFrame >= FrameCount) 85 | _currentFrame = 0; 86 | var frameNode = Node[$"{_currentFrame}"]; 87 | _delay = frameNode.Has("delay") ? frameNode.GetInt("delay") : 150; 88 | } else { 89 | _delay -= (int)frameTime; 90 | } 91 | } 92 | 93 | private void ShiftBackground(float frameTime) 94 | { 95 | if (BackgroundType is 0 or 1) return; 96 | var world = ServiceLocator.Get(); 97 | var position = Position; 98 | var frame = Frames[_currentFrame]; 99 | switch (BackgroundType) 100 | { 101 | case 4: // Horizontal Scrolling 102 | { 103 | if (position.X > Cx + frame.width) 104 | position.X = -world.WorldBounds.width - frame.width; 105 | if (position.X < -Cx - frame.width) 106 | position.X = world.WorldBounds.width + frame.width; 107 | position.X += (Rx * 5) * Raylib.GetFrameTime(); 108 | } 109 | break; 110 | } 111 | Position = position; 112 | } 113 | 114 | public void Clear() 115 | { 116 | foreach (var texture in Frames) 117 | Raylib.UnloadTexture(texture); 118 | Frames.Clear(); 119 | } 120 | } -------------------------------------------------------------------------------- /Client/Map/MapObject.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Actors; 3 | using Client.Net; 4 | using Client.NX; 5 | using Raylib_CsLo; 6 | 7 | namespace Client.Map; 8 | 9 | public class MapObject : IActor 10 | { 11 | private int _currentFrame = 0; 12 | private int _delay = 150; 13 | private bool _alphaSwap = true; 14 | public int ID { get; init; } 15 | 16 | public string Name { get; set; } = string.Empty; 17 | public int Z { get; set; } 18 | public bool Visible { get; set; } = true; 19 | public Vector2 Position { get; set; } 20 | public Vector2 Origin { get; set; } 21 | public Vector2 ScreenOffset { get; set; } 22 | public ActorLayer Layer { get; set; } 23 | public ActorType ActorType { get; init; } = ActorType.StaticMapObject; 24 | public Rectangle Bounds { get; set; } 25 | public Rectangle DstRectangle { get; set; } 26 | public List Frames { get; internal set; } 27 | public int FrameCount { get; init; } 28 | public bool Animated { get; init; } 29 | public bool Blend { get; init; } 30 | public int LoopCount { get; set; } 31 | public int Alpha { get; internal set; } = 255; 32 | public int LowerAlpha { get; init; } 33 | public int UpperAlpha { get; init; } 34 | public NxNode Node { get; set; } 35 | public string TexturePath { get; init; } = string.Empty; 36 | //public Rectangle DesRectangle { get; private set; } 37 | 38 | 39 | public void Draw(float frameTime) 40 | { 41 | var frame = Frames[_currentFrame]; 42 | if (FrameCount > 1) Origin = Node[$"{_currentFrame}"].GetVector("origin"); 43 | Bounds = new Rectangle(0, 0, frame.width, frame.height); 44 | ScreenOffset = new Vector2((Position.X - Origin.X) * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 45 | (Position.Y - Origin.Y) * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 46 | DstRectangle = new Rectangle( 47 | ScreenOffset.X, 48 | ScreenOffset.Y, 49 | frame.width * AppConfig.ScaleFactor, 50 | frame.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 51 | 52 | if (Blend) Raylib.BeginBlendMode(BlendMode.BLEND_ALPHA); 53 | 54 | Raylib.DrawTexturePro(frame, Bounds, DstRectangle, Vector2.Zero, 0f, new Color(255, 255, 255, Alpha)); 55 | 56 | //Raylib.DrawTextureEx(frame, Position - Origin, 0.0f, 1.0f, new Color(255, 255, 255, Alpha)); 57 | if (Blend) Raylib.EndBlendMode(); 58 | } 59 | 60 | public void Update(float frameTime) 61 | { 62 | LoopAnimation(frameTime); 63 | OneTimeAnimation(frameTime); 64 | BlendAnimation(frameTime); 65 | } 66 | 67 | private void LoopAnimation(float frameTime) 68 | { 69 | if (!Animated || Blend || LoopCount == -1) 70 | return; 71 | 72 | if (_delay <= 0) { 73 | _currentFrame++; 74 | if (_currentFrame >= FrameCount) 75 | _currentFrame = 0; 76 | _delay = Node[$"{_currentFrame}"].Has("delay") ? Node[$"{_currentFrame}"].GetInt("delay") : 150; 77 | } else { 78 | _delay -= (int)frameTime; 79 | } 80 | } 81 | 82 | private void OneTimeAnimation(float frameTime) 83 | { 84 | if (LoopCount <= 0) 85 | return; 86 | 87 | if (_delay <= 0) { 88 | _currentFrame++; 89 | if (_currentFrame >= FrameCount) 90 | { 91 | _currentFrame = 0; 92 | LoopCount--; 93 | } 94 | _delay = Node[$"{_currentFrame}"].GetInt("delay"); 95 | } else { 96 | _delay -= (int)frameTime; 97 | } 98 | } 99 | 100 | private void BlendAnimation(float frameTime) 101 | { 102 | if (!Blend) 103 | return; 104 | if (_alphaSwap) 105 | { 106 | if (_delay <= 0) { 107 | Alpha -= (int)frameTime; 108 | if (Alpha <= LowerAlpha) 109 | { 110 | _alphaSwap = false; 111 | Alpha = LowerAlpha; 112 | _delay = Node[$"{_currentFrame}"].GetInt("delay"); 113 | } 114 | } else { 115 | _delay -= (int)frameTime; 116 | } 117 | } 118 | else 119 | { 120 | if (_delay <= 0) { 121 | Alpha += (int)frameTime; 122 | if (Alpha >= UpperAlpha) 123 | { 124 | _alphaSwap = true; 125 | Alpha = UpperAlpha; 126 | _delay = Node[$"{_currentFrame}"].GetInt("delay"); 127 | } 128 | } else { 129 | _delay -= (int)frameTime; 130 | } 131 | } 132 | } 133 | 134 | public void Clear() 135 | { 136 | foreach (var frame in Frames) 137 | Raylib.UnloadTexture(frame); 138 | } 139 | } -------------------------------------------------------------------------------- /Client/Net/InPacket.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Client.Managers; 3 | 4 | namespace Client.Net; 5 | 6 | public class InPacket : IDisposable 7 | { 8 | private readonly ResponseOps _opcode; 9 | private readonly int _packetLength; 10 | private MemoryStream _stream; 11 | private BinaryReader _reader; 12 | private long _position; 13 | private readonly NetworkManager _network; 14 | 15 | public ResponseOps Opcode => _opcode; 16 | public int PacketLength => _packetLength; 17 | public byte[] Data => _stream.ToArray(); 18 | 19 | public InPacket(ResponseOps responseOps, int length) 20 | { 21 | _opcode = responseOps; 22 | _packetLength = length; 23 | _network = ServiceLocator.Get(); 24 | } 25 | 26 | public void SetData(byte[] data) 27 | { 28 | _stream = new MemoryStream(data); 29 | _reader = new BinaryReader(_stream); 30 | } 31 | 32 | public byte ReadByte() 33 | { 34 | if (CheckBounds(sizeof(byte))) return 0; 35 | var result = _reader.ReadByte(); 36 | _position += sizeof(byte); 37 | return result; 38 | } 39 | 40 | public Span ReadBytes(int length) 41 | { 42 | if (CheckBounds(length)) return Array.Empty(); 43 | var list = _reader.ReadBytes(length); 44 | _position += length; 45 | return list; 46 | } 47 | 48 | public Span ReadBytes(long offset, int length) 49 | { 50 | Seek(offset); 51 | var list = _reader.ReadBytes(length); 52 | _position += length; 53 | 54 | return list; 55 | } 56 | 57 | public char ReadChar() 58 | { 59 | if (CheckBounds(sizeof(char))) return '\0'; 60 | var result = _reader.ReadChar(); 61 | _position += sizeof(char); 62 | 63 | return result; 64 | } 65 | 66 | public short ReadShort() 67 | { 68 | if (CheckBounds(sizeof(short))) return 0; 69 | var result = _reader.ReadInt16(); 70 | _position += sizeof(short); 71 | 72 | return result; 73 | } 74 | 75 | public int ReadInt() 76 | { 77 | if (CheckBounds(sizeof(int))) return 0; 78 | var result = _reader.ReadInt32(); 79 | _position += sizeof(int); 80 | 81 | return result; 82 | } 83 | 84 | public long ReadLong() 85 | { 86 | if (CheckBounds(sizeof(long))) return 0; 87 | var result = _reader.ReadInt64(); 88 | _position += sizeof(long); 89 | 90 | return result; 91 | } 92 | 93 | public float ReadFloat() 94 | { 95 | if (CheckBounds(sizeof(float))) return 0.0f; 96 | var result = _reader.ReadSingle(); 97 | _position += sizeof(float); 98 | 99 | return result; 100 | } 101 | 102 | public double ReadDouble() 103 | { 104 | if (CheckBounds(sizeof(double))) return 0.0; 105 | var result = _reader.ReadDouble(); 106 | _position += sizeof(double); 107 | 108 | return result; 109 | } 110 | 111 | public bool ReadBoolean() 112 | { 113 | if (CheckBounds(sizeof(bool))) return false; 114 | var result = _reader.ReadBoolean(); 115 | _position += sizeof(bool); 116 | 117 | return result; 118 | } 119 | 120 | // TODO: Date, Time, DateTime 121 | 122 | public string ReadMapleString() 123 | { 124 | var length = ReadShort(); 125 | 126 | if (CheckBounds(length)) return ""; 127 | var data = ReadBytes(length); 128 | 129 | return Encoding.UTF8.GetString(data); 130 | } 131 | 132 | public string ReadNullTerminatedString() 133 | { 134 | var count = 0; 135 | char character; 136 | List chars = new List(); 137 | 138 | while ((character = _reader.ReadChar()) != 0) 139 | { 140 | chars[count] = character; 141 | count++; 142 | } 143 | 144 | return new string(chars.ToArray()); 145 | } 146 | 147 | public string ReadString(long offset) 148 | { 149 | Seek(offset); 150 | var length = ReadShort(); 151 | var data = ReadBytes(offset + 2, length); 152 | 153 | return Encoding.UTF8.GetString(data); 154 | } 155 | 156 | public void Seek(long position) 157 | { 158 | if (position >= _reader.BaseStream.Length) 159 | throw new Exception("Position is set greater than the file size."); 160 | if (position < 0) 161 | throw new Exception("Position is set less than zero."); 162 | _position = position; 163 | _reader.BaseStream.Position = _position; 164 | } 165 | 166 | public void Skip(long numberOfBytes) 167 | { 168 | if (_position + numberOfBytes >= _reader.BaseStream.Length) 169 | throw new Exception("Attempted to skip further than the file size."); 170 | _position += numberOfBytes; 171 | _reader.BaseStream.Position += numberOfBytes; 172 | } 173 | 174 | private bool CheckBounds(int length) 175 | { 176 | return _position + length > Data.Length; 177 | } 178 | 179 | public void Dispose() 180 | { 181 | _reader.Close(); 182 | _reader.Dispose(); 183 | } 184 | } -------------------------------------------------------------------------------- /Client/Net/ResponseOps.cs: -------------------------------------------------------------------------------- 1 | namespace Client.Net; 2 | 3 | public enum ResponseOps 4 | { 5 | LoginStatus = 0x00, 6 | SendLink = 0x01, 7 | Serverstatus = 0x03, 8 | GenderDone = 0x04, 9 | Tos = 0x05, 10 | PinOperation = 0x06, 11 | PinAssigned = 0x07, 12 | AllCharlist = 0x08, 13 | Serverlist = 0x0A, 14 | Charlist = 0x0B, 15 | ServerIp = 0x0C, 16 | CharNameResponse = 0x0D, 17 | AddNewCharEntry = 0x0E, 18 | DeleteCharResponse = 0x0F, 19 | ChangeChannel = 0x10, 20 | Ping = 0x11, 21 | ChannelSelected = 0x14, 22 | RelogResponse = 0x16, 23 | EnableRecommended = 0x1A, 24 | SendRecommended = 0x1B, 25 | WrongPic = 0x1C, 26 | ModifyInventoryItem = 0x1D, 27 | UpdateInventorySlots = 0x1E, 28 | UpdateStats = 0x1F, 29 | GiveBuff = 0x20, 30 | CancelBuff = 0x21, 31 | TemporaryStats = 0x22, 32 | TemporarySkills = 0x23, 33 | UpdateSkills = 0x24, 34 | FameResponse = 0x26, 35 | ShowStatusInfo = 0x27, 36 | GamepatchesDc = 0x28, 37 | ShowNotes = 0x29, 38 | TrockLocations = 0x2A, 39 | LieDetector = 0x2B, 40 | Reportreply = 0x2D, 41 | EnableReport = 0x2F, 42 | UpdateMount = 0x30, 43 | ShowQuestCompletion = 0x31, 44 | SendTitleBox = 0x32, 45 | UseSkillBook = 0x33, 46 | ShowEquipEffect = 0x34, 47 | FinishSort = 0x35, 48 | FinishSort2 = 0x36, 49 | ReportResponse = 0x37, 50 | MesoLimit = 0x39, 51 | Gender = 0x3A, 52 | BbsOperation = 0x3B, 53 | CharInfo = 0x3D, 54 | PartyOperation = 0x3E, 55 | Buddylist = 0x3F, 56 | GuildOperation = 0x41, 57 | AllianceOperation = 0x42, 58 | SpawnPortal = 0x43, 59 | Servermessage = 0x44, 60 | SomethingWithInventory = 0x45, 61 | OwlOfMinerva = 0x46, 62 | RingAction = 0x48, 63 | RingAction2 = 0x49, 64 | WeddingAction = 0x4A, 65 | PetMessage = 0x4C, 66 | YellowTip = 0x4D, 67 | CatchMessage = 0x4F, 68 | PlayerNpc = 0x51, 69 | MonsterbookAdd = 0x53, 70 | MonsterBookChangeCover = 0x55, 71 | MinimapShit = 0x56, 72 | Energy = 0x5C, 73 | ShowPedigree = 0x5E, 74 | OpenFamily = 0x5F, 75 | FamilyMessage = 0x60, 76 | FamilyInvite = 0x61, 77 | FamilyMessage2 = 0x62, 78 | FamilySeniorMessage = 0x63, 79 | LoadFamily = 0x64, 80 | FamilyGainRep = 0x65, 81 | FamilyLogin = 0x66, 82 | FamilyBuff = 0x67, 83 | FamilyUseRequest = 0x68, 84 | LevelupMsg = 0x69, 85 | MarriageMsg = 0x6A, 86 | JobMsg = 0x6B, 87 | BlankMessage = 0x6D, 88 | AvatarMega = 0x6F, 89 | NameChangeMessage = 0x71, 90 | CharacterTransferMessage = 0x72, 91 | UnknownErrorMsg = 0x73, 92 | GmPolice = 0x74, 93 | SilverBox = 0x75, 94 | UnknownMessage3 = 0x76, 95 | NameChangeMessage2 = 0x78, 96 | EarnTitleMsg = 0x7A, 97 | MapleAdmin = 0x7B, 98 | SkillMacro = 0x7C, 99 | WarpToMap = 0x7D, 100 | OpenMts = 0x7E, 101 | OpenCashshop = 0x7F, 102 | ResetScreen = 0x82, 103 | BlockMessage = 0x83, 104 | BlockMessage2 = 0x84, 105 | ForcedMapEquip = 0x85, 106 | Multichat = 0x86, 107 | Whisper = 0x87, 108 | SpouseChat = 0x88, 109 | WeirdMsg = 0x89, 110 | BossEnv = 0x8A, 111 | BlockPortalShop = 0x8B, 112 | MapEffect = 0x8E, 113 | HpqMoon = 0x8F, 114 | GmPacket = 0x90, 115 | OxQuiz = 0x91, 116 | GmeventInstructions = 0x92, 117 | Clock = 0x93, 118 | BoatEffect = 0x95, 119 | Popup = 0x98, 120 | StopClock = 0x9A, 121 | AriantScoreboard = 0x9B, 122 | SpawnPlayer = 0xA0, 123 | RemovePlayerFromMap = 0xA1, 124 | Chattext = 0xA2, 125 | Chattext1 = 0xA3, 126 | Chalkboard = 0xA4, 127 | UpdateCharBox = 0xA5, 128 | ShowScrollEffect = 0xA7, 129 | SpawnPet = 0xA8, 130 | MovePet = 0xAA, 131 | PetChat = 0xAB, 132 | PetNamechange = 0xAC, 133 | PetShow = 0xAD, 134 | PetCommand = 0xAE, 135 | SpawnSpecialMapobject = 0xAF, 136 | RemoveSpecialMapobject = 0xB0, 137 | MoveSummon = 0xB1, 138 | SummonAttack = 0xB2, 139 | DamageSummon = 0xB3, 140 | SummonSkill = 0xB4, 141 | MovePlayer = 0xB9, 142 | CloseRangeAttack = 0xBA, 143 | RangedAttack = 0xBB, 144 | MagicAttack = 0xBC, 145 | SkillEffect = 0xBE, 146 | CancelSkillEffect = 0xBF, 147 | DamagePlayer = 0xC0, 148 | FacialExpression = 0xC1, 149 | ShowItemEffect = 0xC2, 150 | ShowChair = 0xC4, 151 | UpdateCharLook = 0xC5, 152 | ShowForeignEffect = 0xC6, 153 | GiveForeignBuff = 0xC7, 154 | CancelForeignBuff = 0xC8, 155 | UpdatePartymemberHp = 0xC9, 156 | CancelChair = 0xCD, 157 | ShowItemGainInchat = 0xCE, 158 | DojoWarpUp = 0xCF, 159 | LucksackPass = 0xD0, 160 | LucksackFail = 0xD1, 161 | MesoBagMessage = 0xD2, 162 | UpdateQuestInfo = 0xD3, 163 | PlayerHint = 0xD6, 164 | KoreanEvent = 0xDB, 165 | OpenUi = 0xDC, 166 | LockUi = 0xDD, 167 | DisableUi = 0xDE, 168 | SpawnGuide = 0xDF, 169 | TalkGuide = 0xE0, 170 | ShowCombo = 0xE1, 171 | Cooldown = 0xEA, 172 | SpawnMonster = 0xEC, 173 | KillMonster = 0xED, 174 | SpawnMonsterControl = 0xEE, 175 | MoveMonster = 0xEF, 176 | MoveMonsterResponse = 0xF0, 177 | ApplyMonsterStatus = 0xF2, 178 | CancelMonsterStatus = 0xF3, 179 | DamageMonster = 0xF6, 180 | AriantThing = 0xF9, 181 | ShowMonsterHp = 0xFA, 182 | ShowDragged = 0xFB, 183 | CatchMonster = 0xFC, 184 | ShowMagnet = 0xFD, 185 | SpawnNpc = 0x101, 186 | RemoveNpc = 0x102, 187 | SpawnNpcRequestController = 0x103, 188 | NpcAction = 0x104, 189 | SpawnHiredMerchant = 0x109, 190 | DestroyHiredMerchant = 0x10A, 191 | UpdateHiredMerchant = 0x10B, 192 | DropItemFromMapobject = 0x10C, 193 | RemoveItemFromMap = 0x10D, 194 | KiteMessage = 0x10E, 195 | Kite = 0x10F, 196 | SpawnMist = 0x111, 197 | RemoveMist = 0x112, 198 | SpawnDoor = 0x113, 199 | RemoveDoor = 0x114, 200 | ReactorHit = 0x115, 201 | ReactorSpawn = 0x117, 202 | ReactorDestroy = 0x118, 203 | RollSnowball = 0x119, 204 | HitSnowball = 0x11A, 205 | SnowballMessage = 0x11B, 206 | LeftKnockBack = 0x11C, 207 | HitCoconut = 0x11D, 208 | CoconutScore = 0x11E, 209 | MonsterCarnivalStart = 0x121, 210 | MonsterCarnivalObtainedCp = 0x122, 211 | MonsterCarnivalPartyCp = 0x123, 212 | MonsterCarnivalSummon = 0x124, 213 | MonsterCarnivalMessage = 0x125, 214 | MonsterCarnivalDied = 0x126, 215 | MonsterCarnivalLeave = 0x127, 216 | AriantScore = 0x12D, 217 | HorntailCave = 0x12E, 218 | ZakumShrine = 0x12F, 219 | NpcTalk = 0x130, 220 | OpenNpcShop = 0x131, 221 | ConfirmShopTransaction = 0x132, 222 | OpenStorage = 0x135, 223 | FredrickMessage = 0x136, 224 | Fredrick = 0x137, 225 | Messenger = 0x139, 226 | PlayerInteraction = 0x13A, 227 | Duey = 0x13C, 228 | ShowCash = 0x144, 229 | CashshopOperation = 0x145, 230 | Keymap = 0x14F, 231 | AutoHpPot = 0x150, 232 | AutoMpPot = 0x151, 233 | SendTv = 0x155, 234 | RemoveTv = 0x156, 235 | EnableTv = 0x157, 236 | MtsOperation2 = 0x15B, 237 | MtsOperation = 0x15C, 238 | ViciousHammer = 0x162 239 | } -------------------------------------------------------------------------------- /Changelog.txt: -------------------------------------------------------------------------------- 1 | Performance Enhancements (7.21.24) 2 | --Added IActor and removed Actor, there was no need to have that inheritance. 3 | --Added CreateBackground, CreateTile, to ActorManager, will add more soon. 4 | --Changed the remaining actors to inherit IActor, everything works fine. 5 | --Enhance overall performance, Henesys used to take about 3.5 seconds to load, now it's about 1.5 seconds. 6 | --Wrapped the update function in a Task, so that it runs individually from the rest of the application. 7 | 8 | Began Login work (7.19.24) 9 | --Separated loading from the world class into a MapLoader class. 10 | --Streamlined creating worlds to wait till it's done loading. 11 | --Changed a few things in MapObject class. 12 | --Started the process of creating a world state. 13 | 14 | Back to the client (7.18.24) 15 | --Added OneTime/Counted animations 16 | --Added Blended animations (kind of weird for now) 17 | --Added LoopAnimation for all others. 18 | --Fixed an issue where the animated objects was not properly loading for login screen. 19 | --Fixed an issue where the animated backgrounds were not loading properly for login screen. 20 | 21 | Snip-it (7.14.24 to 7.17.24) 22 | --During this time I create a WZ library that works together with the NX 23 | library I create, so they can be seamlessly swapped. I will not be pushing 24 | neither of those updates to this Repo, those will remain private. 25 | 26 | Networking, Actors, Wz Parsing (7.14.24) 27 | --Began restructuring the networking system, I use my own server so there's no guarantee that 28 | it'll remain compatible with other servers, but I will do my best to keep it consistent. 29 | --Began restructuring the actor system, now that there is something that works as it should, 30 | I'm going to start restructuring it so that it will work with the networking. 31 | --Began working on a WZ parser, because why not? 32 | --Renamed RecvOps and SendOps to match what their packet is called. 33 | 34 | Nothing Much (7.13.24) 35 | --Added a CreateLogin function to the WorldManager 36 | --Added UI nx to the NxManager 37 | --Added clearing of the two lists in the player class, oops 38 | --Added condition checking if the player is in the ladder state 39 | --Removed ref out of Background 40 | 41 | Character Updates (7.12.24) 42 | --Added AvatarLayers, this will be used later one when equipment comes into play. 43 | --Added Character flipping on the X-axis, not perfect but it works for now. 44 | --Added the remainder Body types to the enum. 45 | --Added an AvatarState enum to make it easier to shift between states without knowing the name. 46 | --Added a possible state list so that you know the difference between states and actions. 47 | --Added a frame count list, that you know exactly how much frames are in each state. 48 | --Added Character movement, pretty fast for now. 49 | --Added Character StateChange upon specific movement. 50 | --Added simple, not complete, detection if the character is on a ladder, so it only updates the character when the player moves. 51 | --Added a camera update function to the world manager, since it should handle these sort of things. 52 | --Separated the Origin and Positions of the actors, it was getting a bit confusing. 53 | --Changed how the player is initiated, all states are loaded prior. Then the default is set. 54 | --Changed the ChangeState method, so it picks a state instead of creating one. 55 | --NOTE: All these lists (i.e. AvatarState, possible states, and frame count) ARE ONE TO ONE, if you even touch one of the values it will mess everything up. So be careful. 56 | --Fixed an issue with animation cycling (a test I ran to make sure everything works). All animation now work as the should. 57 | --Fixed an issue where the textures weren't clearing correctly in the player. 58 | 59 | Character & Map Update (7.11.24) 60 | --Added RayGui stuff into the client. 61 | --Added Face into the player, bodytype, and bodypart. 62 | --Added separate update methods for body and face, still needs to be touched up a bit. 63 | --Added a special case for Character.nx in the ReadNodeData method, it now grabs the appropriate nodes. 64 | --Added a command window, it's going to be used for debugging, changing/adding stuff dynamically, once things get much further. 65 | --Added BodyPart code that encompasses all cases, for now. 66 | --Added BodyType enum 67 | --Added Arm struct to handle the arms of the player 68 | --Added Clear method to Body, Head, Arm classes, to release the textures. 69 | --Added a check where the state doesn't need the face rendered. 70 | --Added random delay to the blinking, it's kind of fast right now, but it works. 71 | --Fixed an issues where the wrong values for the state were being called 72 | --Fixed an animation issue for both MapObjects and the character where the last frame was being skipped. 73 | --Fixed the positioning of all body parts of the player. 74 | --Changed the ActorManager so it only detects actors that can be click on, not all of them. 75 | --Face renders correctly, for now. Had to use an Abs function, which I'm not sure if that's correct. 76 | --Removed Arm, Body, Head classes. 77 | --Removed ResourceManager, not useful right now. 78 | --Removed Obstacle class, not needed right now. 79 | 80 | Experimental Avatar (7.10.24) 81 | -- Added ActorType to differentiate between the different actors 82 | -- Added Body, BodyPart, and Head to handle the creation of the player body for now 83 | -- Added the Player class which encompasses the whole body 84 | -- Added a GetPlayer method to ActorManager 85 | __ Made it so you can see the children in the node, in case you need the names instead of the count. 86 | -- Added a test player to the world class 87 | -- Changed how the world class is initialized so it makes more sense now. 88 | --Fixed an issue with other states that caused the client to crash. 89 | --Fixed an issue with the animation (Player/MapObject) that was iterating correctly. 90 | --Separated the different body rendering for efficiency. 91 | 92 | Small changes (7.8.2024) 93 | -- Removed the IComparable from the Actor class, not point having it there. 94 | -- Changed the actor dictionary to contain a List instead of a sorted set. 95 | -- (Possible Bug) Fixed the sorting, for now. It's hard to say since everything isn't there yet. 96 | -- Added a SortAll List, to sort everything once it's there to reduce sort calls. 97 | -- Fixed the issue with animated objects not being sorted, turns out I didn't even assign the order or layer. 98 | 99 | Rebase (7.1.24 - 7.7.24) 100 | -- Decided to take a different approach. 101 | -- Removed FNA support, amazing library, but lacks the features needed to properly emulate MS. 102 | -- Removed all files, literally. 103 | -- Added Raylib-CSLo 104 | -- Added Networking Support (doesn't use encryption) 105 | -- Added NetworkManager 106 | -- Added ActorManager 107 | -- Added Tile loading 108 | -- Added Object loading 109 | -- Internal structure changes. 110 | -- Made it so each layer is their own-sorted set, having different set allows easier transferring between layers. 111 | -- Fixed the frame time calculation. 112 | -- Added background loading, nothing special yet. 113 | -- Added an animated boolean to both MapObject and Background 114 | -- Added a Has method, without the out variable to the NxNode. 115 | -- Added a camera to the world, this will be the primary camera, nothing special yet. 116 | -- Fixed an issue where the maps were unloaded twice. 117 | -- Fixed an issues where the animations were taking forever to update. 118 | -- Found an issue with the sorting system, will fix later. -------------------------------------------------------------------------------- /Client/Avatar/BodyPart.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.NX; 3 | using Raylib_CsLo; 4 | 5 | namespace Client.Avatar; 6 | 7 | public struct BodyPart 8 | { 9 | private readonly NxNode _node; 10 | private readonly PartType _part; 11 | 12 | private readonly Dictionary> _textures; 13 | 14 | /// 15 | /// 16 | /// 17 | /// The node that contains the data we're looking, NOT THE .IMG NODE! 18 | /// 19 | public BodyPart(NxNode node, PartType partType) 20 | { 21 | _node = node; 22 | _part = partType; 23 | _textures = new(); 24 | Init(); 25 | } 26 | 27 | private void Init() 28 | { 29 | for (var i = 0; i < _node.ChildCount; i++) 30 | { 31 | switch (_part) 32 | { 33 | case PartType.Body: 34 | _textures.TryAdd("body", new ()); 35 | _textures["body"].Add(_node[$"{i}"].GetTexture("body")); 36 | break; 37 | case PartType.Arm: 38 | _textures.TryAdd("arm", new ()); 39 | _textures["arm"].Add(_node[$"{i}"].GetTexture("arm")); 40 | break; 41 | case PartType.Head: 42 | _textures.TryAdd("head", new ()); 43 | _textures["head"].Add(_node[$"{i}"].GetTexture("head")); 44 | break; 45 | case PartType.Face: 46 | _textures.TryAdd("face", new()); 47 | _textures["face"].Add(_node[$"{i}"].GetTexture("face")); 48 | break; 49 | case PartType.Hair: 50 | var hairNode = _node[$"{i}"]; 51 | if (hairNode?.Has("hairOverHead") ?? false) 52 | { 53 | _textures.TryAdd("hairOverHead", new()); 54 | _textures["hairOverHead"].Add(hairNode?.GetTexture("hairOverHead") ?? throw new Exception("[Hair] Something happened here.")); 55 | } 56 | if (hairNode?.Has("hair") ?? false) 57 | { 58 | _textures.TryAdd("hair", new()); 59 | _textures["hair"].Add(hairNode?.GetTexture("hair") ?? throw new Exception("[Hair] Something happened here.")); 60 | } 61 | if (hairNode?.Has("hairShade") ?? false) 62 | { 63 | //_hairs[i][HairType.HairOverHead] = hairNode?.GetTexture("hairShade") ?? throw new Exception("[Hair] Something happened here."); 64 | } 65 | if (hairNode?.Has("backHair") ?? false) 66 | { 67 | _textures.TryAdd("backHair", new()); 68 | _textures["backHair"].Add(hairNode?.GetTexture("backHair") ?? throw new Exception("[Hair] Something happened here.")); 69 | } 70 | if (hairNode?.Has("backHairBelowCap") ?? false) 71 | { 72 | _textures.TryAdd("backHairBelowCap", new()); 73 | _textures["backHairBelowCap"].Add(hairNode?.GetTexture("backHairBelowCap") ?? throw new Exception("[Hair] Something happened here.")); 74 | } 75 | break; 76 | case PartType.Afterimage: 77 | break; 78 | case PartType.Accessory1: 79 | case PartType.Accessory2: 80 | case PartType.Accessory3: 81 | case PartType.Accessory4: 82 | case PartType.Accessory5: 83 | case PartType.Accessory6: 84 | break; 85 | case PartType.Cap: 86 | _textures.TryAdd("cap", new()); 87 | _textures["cap"].Add(_node[$"{i}"].GetTexture("cap")); 88 | break; 89 | case PartType.Cape: 90 | _textures.TryAdd("cape", new()); 91 | _textures["cape"].Add(_node[$"{i}"].GetTexture("cape")); 92 | break; 93 | case PartType.Longcoat: 94 | case PartType.Coat: 95 | _textures.TryAdd("mail", new()); 96 | _textures.TryAdd("mailArm", new()); 97 | _textures["mail"].Add(_node[$"{i}"].GetTexture("mail")); 98 | _textures["mailArm"].Add(_node[$"{i}"].GetTexture("mailArm")); 99 | break; 100 | case PartType.Glove: 101 | _textures.TryAdd("glove", new()); 102 | _textures["glove"].Add(_node[$"{i}"].GetTexture("glove")); 103 | break; 104 | case PartType.Pants: 105 | _textures.TryAdd("pants", new()); 106 | _textures["pants"].Add(_node[$"{i}"].GetTexture("pants")); 107 | break; 108 | case PartType.Shield: 109 | _textures.TryAdd("shield", new()); 110 | _textures["shield"].Add(_node[$"{i}"].GetTexture("shield")); 111 | break; 112 | case PartType.Weapon: 113 | _textures.TryAdd("weapon", new()); 114 | _textures["weapon"].Add(_node[$"{i}"].GetTexture("weapon")); 115 | break; 116 | case PartType.FWeapon: 117 | _textures.TryAdd("weaponOverBody", new()); 118 | _textures.TryAdd("weaponArmOverHair", new()); 119 | _textures["weaponOverBody"].Add(_node[$"{i}"].GetTexture("weaponOverBody")); 120 | _textures["weaponArmOverHair"].Add(_node[$"{i}"].GetTexture("weaponArmOverHair")); 121 | break; 122 | } 123 | 124 | } 125 | } 126 | 127 | public void Clear() 128 | { 129 | foreach (var (_,state) in _textures) 130 | foreach (var tex in state) 131 | Raylib.UnloadTexture(tex); 132 | _textures.Clear(); 133 | } 134 | 135 | public Vector2 GetOrigin(int index, string part) 136 | { 137 | return _part switch 138 | { 139 | PartType.Body => _node[$"{index}"]["body"].GetVector("origin"), 140 | PartType.Arm => _node[$"{index}"]["arm"].GetVector("origin"), 141 | PartType.Head => _node[$"{index}"]["head"].GetVector("origin"), 142 | PartType.Face => _node[$"{index}"]["face"].GetVector("origin"), 143 | _ => _node[$"{index}"][part].GetVector("origin") 144 | }; 145 | } 146 | 147 | public Vector2 GetMap(int index, string part, string map) 148 | { 149 | return _part switch 150 | { 151 | PartType.Body => _node[$"{index}"]["body"]["map"].GetVector(map), 152 | PartType.Arm => _node[$"{index}"]["arm"]["map"].GetVector(map), 153 | PartType.Head => _node[$"{index}"]["head"]["map"].GetVector(map), 154 | PartType.Face => _node[$"{index}"]["face"]["map"].GetVector(map), 155 | _ => _node[$"{index}"][part]["map"].GetVector(map) 156 | }; 157 | } 158 | 159 | public Texture GetTexture(int index, string part) 160 | { 161 | return _part switch 162 | { 163 | PartType.Body => _textures["body"][index], 164 | PartType.Arm => _textures["arm"][index], 165 | PartType.Head => _textures["head"][index], 166 | PartType.Face => _textures["face"][index], 167 | _ => _textures[part][index] 168 | }; 169 | } 170 | } -------------------------------------------------------------------------------- /Client/Gui/Panels/StackPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Enums; 4 | using Raylib_CsLo; 5 | 6 | namespace Client.Gui.Panels; 7 | 8 | public class StackPanel : IUIPanel 9 | { 10 | public uint ID { get; init; } 11 | public string Name { get; set; } = string.Empty; 12 | public bool Active { get; set; } 13 | public bool Visible { get; set; } 14 | public Vector2 Position { get; set; } 15 | public Vector2 ScreenOffset { get; set; } 16 | public Rectangle Bounds { get; set; } 17 | public Rectangle DstRectangle { get; set; } 18 | public GuiPriority Priority { get; init; } 19 | public List Nodes { get; } = new(); 20 | public GridLayout Layout { get; init; } = GridLayout.VerticalDown; 21 | public Texture Texture { get; init; } = new(); 22 | public string TexturePath { get; set; } = string.Empty; 23 | public float OffsetX { get; init; } = 0; 24 | public float OffsetY { get; init; } = 0; 25 | 26 | /// 27 | /// Number of Rows 28 | /// 29 | public int RowCount { get; init; } = 1; 30 | 31 | // Number of columns 32 | public int ColumnCount { get; init; } = 1; 33 | 34 | public bool Add(IUIComponent node) 35 | { 36 | Nodes.Add(node); 37 | return true; 38 | } 39 | 40 | public IUIComponent? GetNode(string name) 41 | { 42 | return Nodes.Find(x => x.Name == name); 43 | } 44 | 45 | public void Draw(float frameTime) 46 | { 47 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 48 | ScreenOffset = new Vector2(Position.X * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 49 | Position.Y * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 50 | DstRectangle = new Rectangle( 51 | ScreenOffset.X, 52 | ScreenOffset.Y, 53 | Texture.width * AppConfig.ScaleFactor, 54 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 55 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 56 | switch (Layout) 57 | { 58 | case GridLayout.HorizontalLeft: 59 | for (var i = 0; i < Nodes.Count; i++) 60 | { 61 | Nodes[i].Draw(ScreenOffset - new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 62 | } 63 | break; 64 | case GridLayout.HorizontalRight: 65 | for (var i = 0; i < Nodes.Count; i++) 66 | { 67 | Nodes[i].Draw(ScreenOffset + new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 68 | } 69 | break; 70 | case GridLayout.VerticalUp: 71 | for (var i = 0; i < Nodes.Count; i++) 72 | { 73 | Nodes[i].Draw(ScreenOffset - new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 74 | } 75 | break; 76 | case GridLayout.VerticalDown: 77 | for (var i = 0; i < Nodes.Count; i++) 78 | { 79 | Nodes[i].Draw(ScreenOffset + new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 80 | } 81 | break; 82 | case GridLayout.LeftStack: 83 | { 84 | var row = 0; 85 | var column = 0; 86 | foreach (var button in Nodes) 87 | { 88 | if (column >= ColumnCount) 89 | { 90 | column = 0; 91 | row++; 92 | } 93 | 94 | button.Draw(ScreenOffset - new Vector2( 95 | (button.Bounds.width + OffsetX) * column, 96 | (button.Bounds.height + OffsetY) * row), 97 | frameTime); 98 | column++; 99 | } 100 | } 101 | break; 102 | case GridLayout.RightStack: 103 | { 104 | var row = 0; 105 | var column = 0; 106 | foreach (var button in Nodes) 107 | { 108 | if (column >= ColumnCount) 109 | { 110 | column = 0; 111 | row++; 112 | } 113 | 114 | button.Draw(ScreenOffset + new Vector2( 115 | (button.Bounds.width + OffsetX) * column, 116 | (button.Bounds.height + OffsetY) * row), 117 | frameTime); 118 | column++; 119 | } 120 | } 121 | break; 122 | } 123 | } 124 | 125 | public void Update(float frameTime) 126 | { 127 | switch (Layout) 128 | { 129 | case GridLayout.HorizontalLeft: 130 | for (var i = 0; i < Nodes.Count; i++) 131 | { 132 | Nodes[i].Update(ScreenOffset - new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 133 | } 134 | break; 135 | case GridLayout.HorizontalRight: 136 | for (var i = 0; i < Nodes.Count; i++) 137 | { 138 | Nodes[i].Update(ScreenOffset + new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 139 | } 140 | break; 141 | case GridLayout.VerticalUp: 142 | for (var i = 0; i < Nodes.Count; i++) 143 | { 144 | Nodes[i].Update(ScreenOffset - new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 145 | } 146 | break; 147 | case GridLayout.VerticalDown: 148 | for (var i = 0; i < Nodes.Count; i++) 149 | { 150 | Nodes[i].Update(ScreenOffset + new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 151 | } 152 | break; 153 | case GridLayout.LeftStack: 154 | { 155 | var row = 0; 156 | var column = 0; 157 | foreach (var button in Nodes) 158 | { 159 | if (column >= ColumnCount) 160 | { 161 | column = 0; 162 | row++; 163 | } 164 | 165 | button.Update(ScreenOffset - new Vector2( 166 | (button.Bounds.width + OffsetX) * column, 167 | (button.Bounds.height + OffsetY) * row), 168 | frameTime); 169 | column++; 170 | } 171 | } 172 | break; 173 | case GridLayout.RightStack: 174 | { 175 | var row = 0; 176 | var column = 0; 177 | foreach (var button in Nodes) 178 | { 179 | if (column >= ColumnCount) 180 | { 181 | column = 0; 182 | row++; 183 | } 184 | 185 | button.Update(ScreenOffset + new Vector2( 186 | (button.Bounds.width + OffsetX) * column, 187 | (button.Bounds.height + OffsetY) * row), 188 | frameTime); 189 | column++; 190 | } 191 | } 192 | break; 193 | } 194 | } 195 | 196 | public void Clear() 197 | { 198 | foreach (var node in Nodes) 199 | node.Clear(); 200 | Raylib.UnloadTexture(Texture); 201 | } 202 | } -------------------------------------------------------------------------------- /Client/Gui/Panels/ButtonPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Components.Buttons; 4 | using Client.Gui.Enums; 5 | using Raylib_CsLo; 6 | 7 | namespace Client.Gui.Panels; 8 | 9 | public class ButtonPanel : IUIPanel 10 | { 11 | public uint ID { get; init; } 12 | public string Name { get; set; } = string.Empty; 13 | public bool Active { get; set; } = false; 14 | public bool Visible { get; set; } = false; 15 | public Vector2 Position { get; set; } = Vector2.Zero; 16 | public Vector2 ScreenOffset { get; set; } 17 | public Rectangle Bounds { get; set; } = new(); 18 | public Rectangle DstRectangle { get; set; } 19 | public GuiPriority Priority { get; init; } = GuiPriority.Normal; 20 | public List Nodes { get; } = new(); 21 | public GridLayout Layout { get; init; } = GridLayout.HorizontalRight; 22 | public Texture Texture { get; init; } = new(); 23 | public int OffsetX { get; init; } = 0; 24 | public int OffsetY { get; init; } = 0; 25 | public string TexturePath { get; set; } 26 | 27 | /// 28 | /// Number of Rows 29 | /// 30 | public int RowCount { get; init; } = 1; 31 | 32 | // Number of columns 33 | public int ColumnCount { get; init; } = 1; 34 | 35 | public ButtonPanel(string texturePath) 36 | { 37 | TexturePath = texturePath; 38 | } 39 | 40 | public bool Add(IUIComponent node) 41 | { 42 | if (node is not TextureButton) return false; 43 | //if ((int)node.Bounds.width != (int)Nodes[0].Bounds.width 44 | // || (int)node.Bounds.height != (int)Nodes[0].Bounds.height) 45 | // return false; // For buttons, they all must be the same width and height 46 | Nodes.Add(node); 47 | return true; 48 | } 49 | 50 | public IUIComponent? GetNode(string node) 51 | { 52 | return Nodes.Find(x => x.Name == node); 53 | } 54 | 55 | public void Clear() 56 | { 57 | foreach (var node in Nodes) 58 | node.Clear(); 59 | Raylib.UnloadTexture(Texture); 60 | } 61 | 62 | public void Draw(float frameTime) 63 | { 64 | Bounds = new Rectangle(0, 0, Texture.width, Texture.height); 65 | ScreenOffset = new Vector2(Position.X * AppConfig.ScaleFactor + AppConfig.ScreenOffsetX, 66 | Position.Y * AppConfig.ScaleFactor + AppConfig.ScreenOffsetY); 67 | DstRectangle = new Rectangle( 68 | ScreenOffset.X, 69 | ScreenOffset.Y, 70 | Texture.width * AppConfig.ScaleFactor, 71 | Texture.height * AppConfig.ScaleFactor); // TODO: create event system to do this there instead 72 | Raylib.DrawTexturePro(Texture, Bounds, DstRectangle, Vector2.Zero, 0f, Raylib.WHITE); 73 | switch (Layout) 74 | { 75 | case GridLayout.HorizontalLeft: 76 | for (var i = 0; i < Nodes.Count; i++) 77 | { 78 | Nodes[i].Draw(ScreenOffset - new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 79 | } 80 | break; 81 | case GridLayout.HorizontalRight: 82 | for (var i = 0; i < Nodes.Count; i++) 83 | { 84 | Nodes[i].Draw(ScreenOffset + new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 85 | } 86 | break; 87 | case GridLayout.VerticalUp: 88 | for (var i = 0; i < Nodes.Count; i++) 89 | { 90 | Nodes[i].Draw(ScreenOffset - new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 91 | } 92 | break; 93 | case GridLayout.VerticalDown: 94 | for (var i = 0; i < Nodes.Count; i++) 95 | { 96 | Nodes[i].Draw(ScreenOffset + new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 97 | } 98 | break; 99 | case GridLayout.LeftStack: 100 | { 101 | var row = 0; 102 | var column = 0; 103 | foreach (var button in Nodes) 104 | { 105 | if (column >= ColumnCount) 106 | { 107 | column = 0; 108 | row++; 109 | } 110 | 111 | button.Draw(ScreenOffset - new Vector2( 112 | (button.Bounds.width + OffsetX) * column, 113 | (button.Bounds.height + OffsetY) * row), 114 | frameTime); 115 | column++; 116 | } 117 | } 118 | break; 119 | case GridLayout.RightStack: 120 | { 121 | var row = 0; 122 | var column = 0; 123 | foreach (var button in Nodes) 124 | { 125 | if (column >= ColumnCount) 126 | { 127 | column = 0; 128 | row++; 129 | } 130 | 131 | button.Draw(ScreenOffset + new Vector2( 132 | (button.Bounds.width + OffsetX) * column, 133 | (button.Bounds.height + OffsetY) * row), 134 | frameTime); 135 | column++; 136 | } 137 | } 138 | break; 139 | } 140 | } 141 | 142 | public void Update(float frameTime) 143 | { 144 | switch (Layout) 145 | { 146 | case GridLayout.HorizontalLeft: 147 | for (var i = 0; i < Nodes.Count; i++) 148 | { 149 | Nodes[i].Update(ScreenOffset - new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 150 | } 151 | break; 152 | case GridLayout.HorizontalRight: 153 | for (var i = 0; i < Nodes.Count; i++) 154 | { 155 | Nodes[i].Update(ScreenOffset + new Vector2((Nodes[i].Bounds.width + OffsetX) * i, 0), frameTime); 156 | } 157 | break; 158 | case GridLayout.VerticalUp: 159 | for (var i = 0; i < Nodes.Count; i++) 160 | { 161 | Nodes[i].Update(ScreenOffset - new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 162 | } 163 | break; 164 | case GridLayout.VerticalDown: 165 | for (var i = 0; i < Nodes.Count; i++) 166 | { 167 | Nodes[i].Update(ScreenOffset + new Vector2(0, (Nodes[i].Bounds.height + OffsetY) * i), frameTime); 168 | } 169 | break; 170 | case GridLayout.LeftStack: 171 | { 172 | var row = 0; 173 | var column = 0; 174 | foreach (var button in Nodes) 175 | { 176 | if (column >= ColumnCount) 177 | { 178 | column = 0; 179 | row++; 180 | } 181 | 182 | button.Update(ScreenOffset - new Vector2( 183 | (button.Bounds.width + OffsetX) * column, 184 | (button.Bounds.height + OffsetY) * row), 185 | frameTime); 186 | column++; 187 | } 188 | } 189 | break; 190 | case GridLayout.RightStack: 191 | { 192 | var row = 0; 193 | var column = 0; 194 | foreach (var button in Nodes) 195 | { 196 | if (column >= ColumnCount) 197 | { 198 | column = 0; 199 | row++; 200 | } 201 | 202 | button.Update(ScreenOffset + new Vector2( 203 | (button.Bounds.width + OffsetX) * column, 204 | (button.Bounds.height + OffsetY) * row), 205 | frameTime); 206 | column++; 207 | } 208 | } 209 | break; 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /Client/Managers/ActorManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Numerics; 3 | using Client.Actors; 4 | using Client.Avatar; 5 | using Client.Map; 6 | using Client.Net; 7 | using Client.NX; 8 | using Raylib_CsLo; 9 | 10 | namespace Client.Managers; 11 | 12 | /// 13 | /// The ActorManager class is responsible for managing game actors. 14 | /// 15 | public class ActorManager : IManager 16 | { 17 | /// 18 | /// The lock object used for thread synchronization in the ActorManager class. 19 | /// 20 | private object _threadLock; 21 | 22 | public ActorManager() 23 | { 24 | _threadLock = new(); 25 | } 26 | 27 | /// 28 | /// Initializes the ActorManager by creating and initializing a dictionary to store actors on different layers. 29 | /// 30 | public void Initialize() 31 | { 32 | } 33 | 34 | /// 35 | /// Shuts down the ActorManager. 36 | /// 45 | /// Creates a background actor based on the provided background node. 46 | /// 47 | /// The background node containing the information to create the background actor. 48 | public void CreateBackground(NxNode background) 49 | { 50 | var world = ServiceLocator.Get().GetWorld(); 51 | var bS = background.GetString("bS"); 52 | var no = background.GetInt("no"); 53 | var x = background.GetInt("x"); 54 | var y = background.GetInt("y"); 55 | var rx = background.GetInt("rx"); 56 | var ry = background.GetInt("ry"); 57 | var cx = background.GetInt("cx"); 58 | var cy = background.GetInt("cy"); 59 | var a = background.GetInt("a"); 60 | var front = background.GetInt("front"); 61 | var ani = background.GetInt("ani"); 62 | var f = background.GetInt("f"); 63 | var type = background.GetInt("type"); 64 | 65 | if (ani == 1) 66 | { 67 | var backgroundSet = ServiceLocator.Get().GetNode($"Back/{bS}.img"); 68 | var back = backgroundSet["ani"][$"{no}"]; 69 | var frames = new List(back.ChildCount); 70 | for (var j = 0; j < back.ChildCount - 1; j++) 71 | { 72 | var texture = back.GetTexture($"{j}"); 73 | frames.Add(texture); 74 | } 75 | 76 | if (cx == 0) cx = frames[0].width; 77 | if (cy == 0) cy = frames[0].height; 78 | 79 | world.AddActor(new Background 80 | { 81 | Node = back, 82 | ID = world.GenerateID(), 83 | Position = new Vector2(x, y), 84 | Origin = back["0"].GetVector("origin"), 85 | Z = no, 86 | Cx = cx, 87 | Cy = cy, 88 | Rx = rx, 89 | Ry = ry, 90 | BackgroundType = type, 91 | Layer = front == 1 ? ActorLayer.Foreground : ActorLayer.Background, 92 | Frames = frames, 93 | FrameCount = frames.Count, 94 | Animated = true, 95 | ActorType = ActorType.Background, 96 | TexturePath = back.FullPath, 97 | }); 98 | } 99 | else 100 | { 101 | var backgroundSet = ServiceLocator.Get().GetNode($"Back/{bS}.img"); 102 | var back = backgroundSet["back"]; 103 | var texture = back.GetTexture($"{no}"); 104 | var origin = back[$"{no}"].GetVector("origin"); 105 | if (cx == 0) cx = texture.width; 106 | if (cy == 0) cy = texture.height; 107 | 108 | world.AddActor(new Background 109 | { 110 | Node = back, 111 | ID = world.GenerateID(), 112 | Position = new Vector2(x, y), 113 | Origin = origin, 114 | Z = no, 115 | Cx = cx, 116 | Cy = cy, 117 | Rx = rx, 118 | Ry = ry, 119 | BackgroundType = type, 120 | Layer = front == 1 ? ActorLayer.Foreground : ActorLayer.Background, 121 | Frames = [texture], 122 | FrameCount = 1, 123 | Animated = false, 124 | ActorType = ActorType.Background, 125 | TexturePath = backgroundSet["back"][$"{no}"].FullPath, 126 | Width = texture.width, 127 | Height = texture.height, 128 | }); 129 | } 130 | 131 | //Console.WriteLine($"Background with type: {type} was created"); 132 | } 133 | 134 | /// 135 | /// Creates a tile actor based on the given tile data. 136 | /// 137 | /// The tile set containing the textures for the tile. 138 | /// The layer index of the tile. 139 | /// The tile data containing the position, texture, origin, and order information. 140 | public void CreateTile(NxNode tileSet, int layer, NxNode tile) 141 | { 142 | var world = ServiceLocator.Get().GetWorld(); 143 | var x = tile.GetInt("x"); 144 | var y = tile.GetInt("y"); 145 | var no = tile.GetInt("no"); 146 | var u = tile.GetString("u"); 147 | var texture = tileSet[u].GetTexture($"{no}"); 148 | var origin = tileSet[u][$"{no}"].GetVector("origin"); 149 | var z = tileSet[u][$"{no}"].GetInt("z"); 150 | var zM = tile.GetInt("zM"); 151 | var order = z + 10 * (3000 * (int)(ActorLayer.TileLayer0 + layer) - zM) - 1073721834; 152 | world.AddActor(new MapObject 153 | { 154 | Node = tile, 155 | ID = world.GenerateID(), 156 | Position = new Vector2(x, y), 157 | Origin = origin, 158 | Layer = ActorLayer.TileLayer0 + layer, 159 | Z = order, 160 | Frames = [texture], 161 | FrameCount = 1, 162 | Animated = false, 163 | Blend = false, 164 | LoopCount = -1, 165 | ActorType = ActorType.Tile, 166 | }); 167 | 168 | } 169 | 170 | /// 171 | /// Creates an object in the game world based on the given NxNode and layer. 172 | /// 173 | /// The NxNode representing the object to create. 174 | /// The layer on which to create the object. 175 | public void CreateObject(NxNode obj, int layer) 176 | { 177 | var world = ServiceLocator.Get().GetWorld(); 178 | var oS = obj.GetString("oS"); 179 | var l0 = obj.GetString("l0"); 180 | var l1 = obj.GetString("l1"); 181 | var l2 = obj.GetString("l2"); 182 | var x = obj.GetInt("x"); 183 | var y = obj.GetInt("y"); 184 | var z = obj.GetInt("z"); 185 | var f = obj.GetInt("f"); 186 | var zM = obj.GetInt("zM"); 187 | var order = 30000 * (int)(ActorLayer.TileLayer0 + layer) + z - 1073739824; 188 | var objSet = ServiceLocator.Get().GetNode($"Obj/{oS}.img"); 189 | var node = objSet[l0][l1][l2]; 190 | 191 | if (node.ChildCount > 1) 192 | { 193 | var frames = new List(node.ChildCount); 194 | var blend = node["0"].Has("a0"); 195 | for (var i = 0; i < node.ChildCount - 1; i++) 196 | { 197 | var texture = node.GetTexture($"{i}"); 198 | frames.Add(texture); 199 | } 200 | 201 | world.AddActor(new MapObject 202 | { 203 | Node = node, 204 | ID = world.GenerateID(), 205 | Position = new Vector2(x, y), 206 | Origin = node["0"].GetVector("origin"), 207 | Layer = ActorLayer.TileLayer0 + layer, 208 | Z = order, 209 | Frames = frames, 210 | FrameCount = frames.Count, 211 | Animated = true, 212 | Blend = blend, 213 | LowerAlpha = blend ? node["0"].GetInt("a0") : 255, 214 | UpperAlpha = blend ? node["0"].GetInt("a1") : 255, 215 | LoopCount = node["0"].Has("repeat") ? node["0"].GetInt("repeat") : -1, 216 | ActorType = ActorType.AnimatedMapObject, 217 | TexturePath = node.FullPath 218 | }); 219 | } 220 | else 221 | { 222 | var origin = node["0"].GetVector("origin"); 223 | var texture = node.GetTexture("0"); 224 | world.AddActor(new MapObject 225 | { 226 | Node = node, 227 | ID = world.GenerateID(), 228 | Position = new Vector2(x, y), 229 | Origin = origin, 230 | Layer = ActorLayer.TileLayer0 + layer, 231 | Z = order, 232 | Frames = [texture], 233 | FrameCount = 1, 234 | Animated = false, 235 | Blend = false, 236 | LoopCount = -1, 237 | ActorType = ActorType.StaticMapObject, 238 | TexturePath = $"{node.FullPath}/0" 239 | }); 240 | } 241 | 242 | } 243 | 244 | #endregion 245 | } -------------------------------------------------------------------------------- /Client/NX/NxNode.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using K4os.Compression.LZ4; 3 | using Raylib_CsLo; 4 | 5 | namespace Client.NX; 6 | 7 | public class NxNode : IDisposable 8 | { 9 | private NxReader _reader; 10 | private Dictionary _children; 11 | 12 | public string Name { get; } 13 | public long Offset { get; } 14 | public int FirstChildId { get; } 15 | public short ChildCount { get; } 16 | public Dictionary Children => _children; 17 | public NodeType NodeType { get; } 18 | public NxSaveMode SaveMode { get; } 19 | public string FullPath { get; init; } 20 | 21 | public NxNode(NxReader reader, long offset, string name, NxSaveMode mode = NxSaveMode.None) 22 | { 23 | _reader = reader; 24 | Offset = offset; 25 | Name = name; 26 | SaveMode = mode; 27 | _reader.Seek(offset); 28 | _reader.Skip(4); // we don't need the name; 29 | FirstChildId = _reader.ReadInt(); 30 | ChildCount = _reader.ReadShort(); 31 | NodeType = (NodeType)_reader.ReadShort(); 32 | _children = mode == NxSaveMode.Save ? new(ChildCount) : new(1); 33 | ParseChildren(); 34 | } 35 | 36 | ~NxNode() 37 | { 38 | _children.Clear(); 39 | } 40 | 41 | public NxNode? this[string name] => GetChildNode(name); 42 | 43 | private void ParseChildren() 44 | { 45 | if (SaveMode != NxSaveMode.Save) return; 46 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 47 | { 48 | var childOffset = _reader.NodeBlockOffset + 20 * i; 49 | _reader.Seek(childOffset); 50 | var nameOffset = _reader.ReadInt(); 51 | 52 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 53 | var stringOffset = _reader.ReadLong(); 54 | var childName = _reader.ReadString(stringOffset); 55 | _children?.Add(childName, childOffset); 56 | } 57 | } 58 | 59 | public NxNode? GetChildNode(string name) 60 | { 61 | if (SaveMode == NxSaveMode.Save) 62 | { 63 | _ = _children.TryGetValue(name, out var offset); 64 | return offset > 0 65 | ? new NxNode(_reader, offset, name, SaveMode){ FullPath = string.Concat(FullPath, $"/{name}") } 66 | : throw new Exception($"[NX] Parent: {Name} does not contain child {name}"); 67 | } 68 | 69 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 70 | { 71 | var childOffset = _reader.NodeBlockOffset + 20 * i; 72 | _reader.Seek(childOffset); 73 | var nameOffset = _reader.ReadInt(); 74 | 75 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 76 | var stringOffset = _reader.ReadLong(); 77 | var childName = _reader.ReadString(stringOffset); 78 | if (childName == name) 79 | { 80 | return new NxNode(_reader, childOffset, childName) 81 | { FullPath = string.Concat(FullPath, $"/{childName}") }; 82 | } 83 | } 84 | 85 | return null; 86 | } 87 | 88 | public bool Has(string node) 89 | { 90 | if (SaveMode == NxSaveMode.Save) 91 | { 92 | return _children.TryGetValue(node, out _); 93 | } 94 | 95 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 96 | { 97 | var childOffset = _reader.NodeBlockOffset + 20 * i; 98 | _reader.Seek(childOffset); 99 | var nameOffset = _reader.ReadInt(); 100 | 101 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 102 | var stringOffset = _reader.ReadLong(); 103 | var childName = _reader.ReadString(stringOffset); 104 | 105 | if (childName == node) 106 | { 107 | return true; 108 | } 109 | } 110 | 111 | return false; 112 | } 113 | 114 | public bool Has(string node, out NxNode? child) 115 | { 116 | if (SaveMode == NxSaveMode.Save) 117 | { 118 | if (!_children.TryGetValue(node, out var offset)) 119 | { 120 | child = null; 121 | return false; 122 | } 123 | 124 | child = new NxNode(_reader, offset, node, SaveMode); 125 | return true; 126 | } 127 | 128 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 129 | { 130 | var childOffset = _reader.NodeBlockOffset + 20 * i; 131 | _reader.Seek(childOffset); 132 | var nameOffset = _reader.ReadInt(); 133 | 134 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 135 | var stringOffset = _reader.ReadLong(); 136 | var childName = _reader.ReadString(stringOffset); 137 | 138 | if (childName != node) continue; 139 | child = new NxNode(_reader, childOffset, childName); 140 | return true; 141 | } 142 | 143 | child = null; 144 | return false; 145 | } 146 | 147 | public int GetInt(string node) 148 | { 149 | if (SaveMode == NxSaveMode.Save) 150 | { 151 | if (!_children.TryGetValue(node, out var offset)) return 0; 152 | _reader.Seek(offset); 153 | _reader.Skip(10); 154 | var type = (NodeType)_reader.ReadShort(); 155 | if (type != NodeType.Int64) throw new Exception($"[NX] Node: {node} is not type of {NodeType.Int64}"); 156 | var val = (int)_reader.ReadLong(); 157 | return val; 158 | } 159 | 160 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 161 | { 162 | var childOffset = _reader.NodeBlockOffset + 20 * i; 163 | _reader.Seek(childOffset); 164 | var nameOffset = _reader.ReadInt(); 165 | _reader.Skip(6); 166 | var type = (NodeType)_reader.ReadShort(); 167 | 168 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 169 | var stringOffset = _reader.ReadLong(); 170 | var childName = _reader.ReadString(stringOffset); 171 | if (childName != node || type != NodeType.Int64) continue; 172 | 173 | _reader.Seek(childOffset); 174 | _reader.Skip(12); 175 | var val = (int)_reader.ReadLong(); 176 | return val; 177 | } 178 | 179 | throw new Exception($"[NX] Node: {node} is not type of {NodeType.Int64}"); 180 | } 181 | 182 | public double GetDouble(string node) 183 | { 184 | if (SaveMode == NxSaveMode.Save) 185 | { 186 | if (!_children.TryGetValue(node, out var offset)) return 0; 187 | _reader.Seek(offset); 188 | _reader.Skip(10); 189 | var type = (NodeType)_reader.ReadShort(); 190 | if (type != NodeType.Double) throw new Exception($"[NX] Node: {node} is not type of {type}"); 191 | 192 | var val = (int)_reader.ReadLong(); 193 | return val; 194 | } 195 | 196 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 197 | { 198 | var childOffset = _reader.NodeBlockOffset + 20 * i; 199 | _reader.Seek(childOffset); 200 | var nameOffset = _reader.ReadInt(); 201 | _reader.Skip(6); 202 | var type = (NodeType)_reader.ReadShort(); 203 | 204 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 205 | var stringOffset = _reader.ReadLong(); 206 | var childName = _reader.ReadString(stringOffset); 207 | if (childName != node || type != NodeType.Double) continue; 208 | _reader.Seek(childOffset); 209 | _reader.Skip(12); 210 | 211 | return _reader.ReadLong(); 212 | } 213 | 214 | throw new Exception($"[NX] Node: {node} is not type of {NodeType.Double}"); 215 | } 216 | 217 | public Vector2 GetVector(string node) 218 | { 219 | if (SaveMode == NxSaveMode.Save) 220 | { 221 | if (!_children.TryGetValue(node, out var offset)) return Vector2.Zero; 222 | _reader.Seek(offset); 223 | _reader.Skip(10); 224 | var type = (NodeType)_reader.ReadShort(); 225 | if (type != NodeType.Vector) throw new Exception($"[NX] Node: {node} is not type of {type}"); 226 | 227 | return new Vector2(_reader.ReadInt(), _reader.ReadInt()); 228 | 229 | } 230 | 231 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 232 | { 233 | var childOffset = _reader.NodeBlockOffset + 20 * i; 234 | _reader.Seek(childOffset); 235 | var nameOffset = _reader.ReadInt(); 236 | _reader.Skip(6); 237 | var type = (NodeType)_reader.ReadShort(); 238 | 239 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 240 | var stringOffset = _reader.ReadLong(); 241 | var childName = _reader.ReadString(stringOffset); 242 | if (childName != node || type != NodeType.Vector) continue; 243 | _reader.Seek(childOffset); 244 | _reader.Skip(12); 245 | 246 | return new Vector2(_reader.ReadInt(), _reader.ReadInt()); 247 | } 248 | 249 | throw new Exception($"[NX] Node: {node} is not type of {NodeType.Vector}"); 250 | } 251 | 252 | public string GetString(string node) 253 | { 254 | if (SaveMode == NxSaveMode.Save) 255 | { 256 | if (!_children.TryGetValue(node, out var offset)) return ""; 257 | _reader.Seek(offset); 258 | _reader.Skip(10); 259 | var type = (NodeType)_reader.ReadShort(); 260 | if (type != NodeType.String) throw new Exception($"[NX] Node: {node} is not type of {type}"); 261 | 262 | var nodeNameOffset = _reader.ReadInt(); 263 | _reader.Seek(_reader.StringBlockOffset + 8 * nodeNameOffset); 264 | var nodeStringOffset = _reader.ReadLong(); 265 | var readString = _reader.ReadString(nodeStringOffset); 266 | 267 | return readString; 268 | 269 | } 270 | 271 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 272 | { 273 | var childOffset = _reader.NodeBlockOffset + 20 * i; 274 | _reader.Seek(childOffset); 275 | var nameOffset = _reader.ReadInt(); 276 | _reader.Skip(6); 277 | var type = (NodeType)_reader.ReadShort(); 278 | 279 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 280 | var stringOffset = _reader.ReadLong(); 281 | var childName = _reader.ReadString(stringOffset); 282 | 283 | if (childName != node || type != NodeType.String) continue; 284 | 285 | _reader.Seek(childOffset); 286 | _reader.Skip(12); 287 | var nodeNameOffset = _reader.ReadInt(); 288 | _reader.Seek(_reader.StringBlockOffset + 8 * nodeNameOffset); 289 | var nodeStringOffset = _reader.ReadLong(); 290 | var readString = _reader.ReadString(nodeStringOffset); 291 | 292 | return readString; 293 | } 294 | 295 | throw new Exception($"[NX] Node: {node} is not type of {NodeType.String}"); 296 | } 297 | 298 | public unsafe Texture GetTexture(string node) 299 | { 300 | lock (_children) 301 | { 302 | if (SaveMode == NxSaveMode.Save) 303 | { 304 | if (!_children.TryGetValue(node, out var offset)) return new Texture(); 305 | _reader.Seek(offset); 306 | _reader.Skip(10); 307 | var type = (NodeType)_reader.ReadShort(); 308 | if (type != NodeType.Bitmap) throw new Exception($"[NX] Node: {node} is not type of Bitmap"); 309 | 310 | var bitmapId = _reader.ReadInt(); 311 | var width = _reader.ReadShort(); 312 | var height = _reader.ReadShort(); 313 | _reader.Seek(_reader.BitmapBlockOffset + 8 * bitmapId); 314 | var bitmapImageLocation = _reader.ReadLong(); 315 | _reader.Seek(bitmapImageLocation); 316 | var length = _reader.ReadInt(); 317 | var compressedBitmap = _reader.ReadBytes(length).ToArray(); 318 | var uncompressedBitmap = new byte[width * height * 4]; 319 | var decompressedSize = LZ4Codec.Decode(compressedBitmap, 0, compressedBitmap.Length, 320 | uncompressedBitmap, 0, uncompressedBitmap.Length); 321 | 322 | if (decompressedSize <= 0) 323 | throw new Exception("Failed to decompress texture data"); 324 | 325 | var rayImage = new Raylib_CsLo.Image(); 326 | var count = 0; // just in queso 327 | fixed (byte* data = uncompressedBitmap) 328 | { 329 | // Convert it from BGRA32 to RGBA32 330 | // Raylib doesn't support anything but RGB channels. 331 | for (var j = 0; j < width * height; j++) 332 | { 333 | var b = data[count]; 334 | var g = data[count + 1]; 335 | var r = data[count + 2]; 336 | var a = data[count + 3]; 337 | 338 | data[count] = r; 339 | data[count + 1] = g; 340 | data[count + 2] = b; 341 | data[count + 3] = a; 342 | 343 | count += 4; 344 | } 345 | 346 | rayImage.data = data; 347 | rayImage.format = (int)PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 348 | rayImage.width = width; 349 | rayImage.height = height; 350 | rayImage.mipmaps = 1; 351 | } 352 | 353 | var tex = Raylib.LoadTextureFromImage(rayImage); 354 | return tex; 355 | } 356 | 357 | for (var i = FirstChildId; i < FirstChildId + ChildCount; i++) 358 | { 359 | var childOffset = _reader.NodeBlockOffset + 20 * i; 360 | _reader.Seek(childOffset); 361 | var nameOffset = _reader.ReadInt(); 362 | _reader.Skip(6); 363 | var type = (NodeType)_reader.ReadShort(); 364 | 365 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 366 | var stringOffset = _reader.ReadLong(); 367 | var childName = _reader.ReadString(stringOffset); 368 | 369 | if (childName != node || type != NodeType.Bitmap) continue; 370 | 371 | _reader.Seek(childOffset); 372 | _reader.Skip(12); 373 | var bitmapId = _reader.ReadInt(); 374 | var width = _reader.ReadShort(); 375 | var height = _reader.ReadShort(); 376 | _reader.Seek(_reader.BitmapBlockOffset + 8 * bitmapId); 377 | var bitmapImageLocation = _reader.ReadLong(); 378 | _reader.Seek(bitmapImageLocation); 379 | var length = _reader.ReadInt(); 380 | var compressedBitmap = _reader.ReadBytes(length).ToArray(); 381 | var uncompressedBitmap = new byte[width * height * 4]; 382 | var decompressedSize = LZ4Codec.Decode(compressedBitmap, 0, compressedBitmap.Length, 383 | uncompressedBitmap, 0, uncompressedBitmap.Length); 384 | 385 | if (decompressedSize <= 0) 386 | throw new Exception("Failed to decompress texture data"); 387 | 388 | var rayImage = new Raylib_CsLo.Image(); 389 | var count = 0; // just in queso 390 | fixed (byte* data = uncompressedBitmap) 391 | { 392 | // Convert it from BGRA32 to RGBA32 393 | // Raylib doesn't support anything but RGB channels. 394 | for (var j = 0; j < width * height; j++) 395 | { 396 | var b = data[count]; 397 | var g = data[count + 1]; 398 | var r = data[count + 2]; 399 | var a = data[count + 3]; 400 | 401 | data[count] = r; 402 | data[count + 1] = g; 403 | data[count + 2] = b; 404 | data[count + 3] = a; 405 | 406 | count += 4; 407 | } 408 | 409 | rayImage.data = data; 410 | rayImage.format = (int)PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; 411 | rayImage.width = width; 412 | rayImage.height = height; 413 | rayImage.mipmaps = 1; 414 | } 415 | 416 | var tex = Raylib.LoadTextureFromImage(rayImage); 417 | return tex; 418 | } 419 | 420 | throw new Exception($"[NX] Node: {node} is not type of {NodeType.Bitmap}"); 421 | } 422 | } 423 | 424 | public void Dispose() 425 | { 426 | _reader.Dispose(); 427 | _children.Clear(); 428 | } 429 | } 430 | 431 | public enum NxSaveMode 432 | { 433 | None, 434 | Save 435 | } -------------------------------------------------------------------------------- /Client/NX/NxFile.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Client.NX; 4 | 5 | public class NxFile : IDisposable 6 | { 7 | private NxReader _reader; // Used as reference. 8 | private readonly Dictionary _stringPool; 9 | private readonly string _parentFile; 10 | private readonly NxSaveMode _saveMode; 11 | 12 | public Dictionary StringPool => _stringPool; 13 | 14 | public NxFile(string path, NxSaveMode saveMode) 15 | { 16 | _parentFile = Path.GetFileNameWithoutExtension(path); 17 | _reader = new (File.OpenRead(path)); 18 | _stringPool = new(); 19 | _saveMode = saveMode; 20 | if (Path.GetFileNameWithoutExtension(path) == "Data") 21 | ReadBetaNodeData(); 22 | else 23 | ReadNodeData(); 24 | } 25 | 26 | private void ReadBetaNodeData() 27 | { 28 | var dataFileRootOffset = _reader.NodeBlockOffset; 29 | _reader.Seek(dataFileRootOffset); 30 | _reader.Skip(4); 31 | var dataFileRootFirstChildId = _reader.ReadInt(); 32 | var dataFileRootChildCount = _reader.ReadShort(); 33 | 34 | for (var i = dataFileRootFirstChildId; i < dataFileRootFirstChildId + dataFileRootChildCount; i++) 35 | { 36 | var rootFileOffset = _reader.NodeBlockOffset + 20 * i; 37 | _reader.Seek(rootFileOffset); 38 | 39 | var rootNameOffset = _reader.ReadInt(); 40 | var rootFirstChildId = _reader.ReadInt(); 41 | var rootChildCount = _reader.ReadShort(); 42 | _reader.Seek(_reader.StringBlockOffset + 8 * rootNameOffset); 43 | var rootStringOffset = _reader.ReadLong(); 44 | var rootName = _reader.ReadString(rootStringOffset); 45 | 46 | switch (rootName) 47 | { 48 | case "Character": 49 | { 50 | for (var j = rootFirstChildId; j < rootFirstChildId + rootChildCount; j++) 51 | { 52 | var offset = _reader.NodeBlockOffset + 20 * j; 53 | _reader.Seek(offset); 54 | var nameOffset = _reader.ReadInt(); 55 | var firstChildId = _reader.ReadInt(); 56 | var childCount = _reader.ReadShort(); 57 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 58 | var stringOffset = _reader.ReadLong(); 59 | var name = _reader.ReadString(stringOffset); 60 | 61 | switch (name) 62 | { 63 | case "Cap": 64 | case "Cape": 65 | case "Coat": 66 | case "Face": 67 | case "Glove": 68 | case "Hair": 69 | case "Longcoat": 70 | case "Pants": 71 | case "PetEquip": 72 | case "Ring": 73 | case "Shield": 74 | case "Shoes": 75 | case "TamingMob": 76 | case "Weapon": 77 | { 78 | for (var k = firstChildId; k < firstChildId + childCount; k++) 79 | { 80 | var childOffset = _reader.NodeBlockOffset + 20 * k; 81 | _reader.Seek(childOffset); 82 | var childNameOffset = _reader.ReadInt(); 83 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 84 | var childStringOffset = _reader.ReadLong(); 85 | var childName = _reader.ReadString(childStringOffset); 86 | 87 | if (!childName.Contains(".img")) continue; 88 | Console.WriteLine($"{rootName}/{name}/{childName}"); 89 | _stringPool.Add($"{rootName}/{name}/{childName}", childOffset); 90 | } 91 | } 92 | break; 93 | } 94 | 95 | if (!name.Contains(".img")) continue; 96 | Console.WriteLine($"{rootName}/{name}"); 97 | _stringPool.Add($"{rootName}/{name}", offset); 98 | } 99 | break; 100 | } 101 | case "Map": 102 | { 103 | for (var j = rootFirstChildId; j < rootFirstChildId + rootChildCount; j++) 104 | { 105 | var offset = _reader.NodeBlockOffset + 20 * j; 106 | _reader.Seek(offset); 107 | var nameOffset = _reader.ReadInt(); 108 | var firstChildId = _reader.ReadInt(); 109 | var childCount = _reader.ReadShort(); 110 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 111 | var stringOffset = _reader.ReadLong(); 112 | var name = _reader.ReadString(stringOffset); 113 | switch (name) 114 | { 115 | case "Map": 116 | { 117 | for (var k = firstChildId; k < firstChildId + childCount; k++) 118 | { 119 | var mapOffset = _reader.NodeBlockOffset + 20 * k; 120 | _reader.Seek(mapOffset); 121 | var mapNameOffset = _reader.ReadInt(); 122 | var mapFirstChildId = _reader.ReadInt(); 123 | var mapChildCount = _reader.ReadShort(); 124 | _reader.Seek(_reader.StringBlockOffset + 8 * mapNameOffset); 125 | var mapStringOffset = _reader.ReadLong(); 126 | var mapName = _reader.ReadString(mapStringOffset); 127 | for (var l = mapFirstChildId; l < mapFirstChildId + mapChildCount; l++) 128 | { 129 | var childOffset = _reader.NodeBlockOffset + 20 * l; 130 | _reader.Seek(childOffset); 131 | var childNameOffset = _reader.ReadInt(); 132 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 133 | var childStringOffset = _reader.ReadLong(); 134 | var childName = _reader.ReadString(childStringOffset); 135 | 136 | if (!childName.Contains(".img")) continue; 137 | _stringPool.Add($"{rootName}/{mapName}/{childName}", childOffset); 138 | } 139 | } 140 | } 141 | break; 142 | case "Back": 143 | case "Tile": 144 | case "Obj": 145 | case "WorldMap": 146 | { 147 | for (var k = firstChildId; k < firstChildId + childCount; k++) 148 | { 149 | var childOffset = _reader.NodeBlockOffset + 20 * k; 150 | _reader.Seek(childOffset); 151 | var childNameOffset = _reader.ReadInt(); 152 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 153 | var childStringOffset = _reader.ReadLong(); 154 | var childName = _reader.ReadString(childStringOffset); 155 | if (!childName.Contains(".img")) continue; 156 | _stringPool.Add($"{rootName}/{name}/{childName}", childOffset); 157 | } 158 | } 159 | break; 160 | case "Effect.img": 161 | case "MapHelper.img": 162 | case "Physics.img": 163 | _stringPool.Add($"{rootName}/{name}", offset); 164 | break; 165 | } 166 | } 167 | } 168 | break; 169 | default: 170 | for (var j = rootFirstChildId; j < rootFirstChildId + rootChildCount; j++) 171 | { 172 | var childOffset = _reader.NodeBlockOffset + 20 * j; 173 | _reader.Seek(childOffset); 174 | 175 | var childNameOffset = _reader.ReadInt(); 176 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 177 | var childStringOffset = _reader.ReadLong(); 178 | var childName = _reader.ReadString(childStringOffset); 179 | 180 | if (!childName.Contains(".img")) continue; 181 | _stringPool.Add($"{rootName}/{childName}", childOffset); 182 | } 183 | break; 184 | } 185 | } 186 | } 187 | 188 | private void ReadNodeData() 189 | { 190 | var rootNodeOffset = _reader.NodeBlockOffset; 191 | _reader.Seek(rootNodeOffset); 192 | _reader.Skip(4); 193 | var rootFirstChildId = _reader.ReadInt(); 194 | var rootChildCount = _reader.ReadShort(); 195 | 196 | switch (_parentFile) 197 | { 198 | case "Map": 199 | { 200 | for (var i = rootFirstChildId; i < rootFirstChildId + rootChildCount; i++) 201 | { 202 | var offset = _reader.NodeBlockOffset + 20 * i; 203 | _reader.Seek(offset); 204 | var nameOffset = _reader.ReadInt(); 205 | var firstChildId = _reader.ReadInt(); 206 | var childCount = _reader.ReadShort(); 207 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 208 | var stringOffset = _reader.ReadLong(); 209 | var name = _reader.ReadString(stringOffset); 210 | switch (name) 211 | { 212 | case "Map": 213 | { 214 | for (var j = firstChildId; j < firstChildId + childCount; j++) 215 | { 216 | var mapOffset = _reader.NodeBlockOffset + 20 * j; 217 | _reader.Seek(mapOffset); 218 | var mapNameOffset = _reader.ReadInt(); 219 | var mapFirstChildId = _reader.ReadInt(); 220 | var mapChildCount = _reader.ReadShort(); 221 | _reader.Seek(_reader.StringBlockOffset + 8 * mapNameOffset); 222 | var mapStringOffset = _reader.ReadLong(); 223 | var mapName = _reader.ReadString(mapStringOffset); 224 | for (var k = mapFirstChildId; k < mapFirstChildId + mapChildCount; k++) 225 | { 226 | var childOffset = _reader.NodeBlockOffset + 20 * k; 227 | _reader.Seek(childOffset); 228 | var childNameOffset = _reader.ReadInt(); 229 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 230 | var childStringOffset = _reader.ReadLong(); 231 | var childName = _reader.ReadString(childStringOffset); 232 | _stringPool.Add($"{mapName}/{childName}", childOffset); 233 | } 234 | } 235 | } 236 | break; 237 | case "Back": 238 | case "Tile": 239 | case "Obj": 240 | case "WorldMap": 241 | { 242 | for (var j = firstChildId; j < firstChildId + childCount; j++) 243 | { 244 | var childOffset = _reader.NodeBlockOffset + 20 * j; 245 | _reader.Seek(childOffset); 246 | var childNameOffset = _reader.ReadInt(); 247 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 248 | var childStringOffset = _reader.ReadLong(); 249 | var childName = _reader.ReadString(childStringOffset); 250 | _stringPool.Add($"{name}/{childName}", childOffset); 251 | } 252 | } 253 | break; 254 | case "Effect.img": 255 | case "MapHelper.img": 256 | case "Physics.img": 257 | { 258 | _stringPool.Add($"{name}", offset); 259 | } 260 | break; 261 | } 262 | } 263 | 264 | break; 265 | } 266 | case "Character": 267 | { 268 | for (var i = rootFirstChildId; i < rootFirstChildId + rootChildCount; i++) 269 | { 270 | var offset = _reader.NodeBlockOffset + 20 * i; 271 | _reader.Seek(offset); 272 | var nameOffset = _reader.ReadInt(); 273 | var firstChildId = _reader.ReadInt(); 274 | var childCount = _reader.ReadShort(); 275 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 276 | var stringOffset = _reader.ReadLong(); 277 | var name = _reader.ReadString(stringOffset); 278 | switch (name) 279 | { 280 | case "Cap": 281 | case "Cape": 282 | case "Coat": 283 | case "Face": 284 | case "Glove": 285 | case "Hair": 286 | case "Longcoat": 287 | case "Pants": 288 | case "PetEquip": 289 | case "Ring": 290 | case "Shield": 291 | case "Shoes": 292 | case "TamingMob": 293 | case "Weapon": 294 | { 295 | for (var j = firstChildId; j < firstChildId + childCount; j++) 296 | { 297 | var childOffset = _reader.NodeBlockOffset + 20 * j; 298 | _reader.Seek(childOffset); 299 | var childNameOffset = _reader.ReadInt(); 300 | _reader.Seek(_reader.StringBlockOffset + 8 * childNameOffset); 301 | var childStringOffset = _reader.ReadLong(); 302 | var childName = _reader.ReadString(childStringOffset); 303 | _stringPool.Add($"{name}/{childName}", childOffset); 304 | } 305 | } 306 | break; 307 | default: 308 | { 309 | _stringPool.Add($"{name}", offset); 310 | } 311 | break; 312 | } 313 | } 314 | 315 | break; 316 | } 317 | default: 318 | { 319 | for (var i = rootFirstChildId; i < rootFirstChildId + rootChildCount; i++) 320 | { 321 | var offset = _reader.NodeBlockOffset + 20 * i; 322 | _reader.Seek(offset); 323 | 324 | var nameOffset = _reader.ReadInt(); 325 | _reader.Seek(_reader.StringBlockOffset + 8 * nameOffset); 326 | var stringOffset = _reader.ReadLong(); 327 | var name = _reader.ReadString(stringOffset); 328 | if (!name.Contains(".img")) continue; 329 | _stringPool.Add(name, offset); 330 | } 331 | 332 | break; 333 | } 334 | } 335 | } 336 | 337 | public NxNode GetNode(string nodeName) 338 | { 339 | if (!_stringPool.TryGetValue(nodeName, out var offset)) 340 | throw new NullReferenceException($"[NX] The node ({nodeName}) was not found within the string pool."); 341 | return new NxNode(_reader, offset, nodeName, _saveMode) { FullPath = nodeName }; 342 | } 343 | 344 | public void Dispose() 345 | { 346 | _reader.Dispose(); 347 | _stringPool.Clear(); 348 | } 349 | } 350 | 351 | public enum NodeType 352 | { 353 | NoData = 0, 354 | Int64 = 1, 355 | Double = 2, 356 | String = 3, 357 | Vector = 4, 358 | Bitmap = 5, 359 | Audio = 6 360 | }; -------------------------------------------------------------------------------- /Client/Managers/UIManager.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Client.Gui.Components; 3 | using Client.Gui.Components.Buttons; 4 | using Client.Gui.Enums; 5 | using Client.Gui.Panels; 6 | using Client.NX; 7 | using Raylib_CsLo; 8 | 9 | namespace Client.Managers; 10 | 11 | /// 12 | /// The UIManager class manages the user interface of the application. 13 | /// 14 | public class UIManager : IManager 15 | { 16 | private List _interfaces; 17 | private uint _uiCount; 18 | private bool _cleared; 19 | private object _threadLock; 20 | private Queue _addQueue, _removeQueue, _changeQueue; 21 | 22 | /// 23 | /// Default Constructor 24 | /// 25 | public UIManager() 26 | { 27 | _interfaces = new(); 28 | _uiCount = 0; 29 | _cleared = false; 30 | _threadLock = new(); 31 | _addQueue = new(); 32 | _removeQueue = new(); 33 | _changeQueue = new(); 34 | } 35 | 36 | public void Initialize() 37 | { 38 | 39 | } 40 | 41 | public void Shutdown() 42 | { 43 | ClearUI(); 44 | _interfaces.Clear(); 45 | } 46 | 47 | #region Clear/Remove/Add 48 | 49 | /// 50 | /// Clears all user interface panels managed by the UIManager. 51 | /// 52 | public void ClearUI() 53 | { 54 | if (_cleared) return; 55 | 56 | foreach (var ui in _interfaces) 57 | ui.Clear(); 58 | _uiCount = 0; 59 | _cleared = true; 60 | } 61 | 62 | /// 63 | /// Adds an interface panel to the UI manager. 64 | /// 65 | /// The interface panel to be added. 66 | /// Flag indicating whether the panel should be added instantly without any delay. The default value is false. 67 | public void AddInterface(IUIPanel panel, bool instant = false) 68 | { 69 | lock (_threadLock) 70 | { 71 | if (GetUI(panel.Name) != null) return; 72 | if (!instant) _addQueue.Enqueue(panel); 73 | else _interfaces.Add(panel); 74 | } 75 | } 76 | 77 | /// 78 | /// Updates the specified interface panel in the UI manager. 79 | /// 80 | /// The interface panel to be updated. 81 | public void UpdateInterface(IUIPanel panel) 82 | { 83 | lock (_threadLock) 84 | { 85 | _changeQueue.Enqueue(panel); 86 | } 87 | } 88 | 89 | /// 90 | /// Removes an interface panel from the UI manager. 91 | /// 92 | /// The interface panel to be removed. 93 | public void RemoveInterface(IUIPanel panel) 94 | { 95 | lock (_threadLock) 96 | { 97 | _removeQueue.Enqueue(panel); 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | #region UI 104 | 105 | #region Containers 106 | 107 | /// 108 | /// Creates a static panel for the UI with the specified parameters. 109 | /// 110 | /// The name of the panel. 111 | /// The texture for the panel. 112 | /// The path to the texture. 113 | /// The position of the panel. 114 | /// Indicates whether the panel is visible. Default is true. 115 | /// The priority of the panel. Default is GuiPriority.Normal. 116 | /// The created StaticPanel object. 117 | public StaticPanel CreateStaticPanel(string name, Texture texture, string texturePath, Vector2 position, bool visible = true, GuiPriority priority = GuiPriority.Normal) 118 | { 119 | return new StaticPanel 120 | { 121 | ID = _uiCount++, 122 | Name = name, 123 | Position = position, 124 | Texture = texture, 125 | TexturePath = texturePath, 126 | Visible = visible, 127 | Priority = priority, 128 | }; 129 | } 130 | 131 | /// 132 | /// Creates a frame panel with the specified parameters. 133 | /// 134 | /// The name of the FramePanel. 135 | /// The texture of the FramePanel. 136 | /// The path to the texture of the FramePanel. 137 | /// The position of the FramePanel. 138 | /// Specifies whether the FramePanel is initially visible. Default is true. 139 | /// The priority of the FramePanel. Default is GuiPriority.Above. 140 | /// A new instance of the FramePanel class. 141 | public FramePanel CreateScreenFrame(string name, Texture texture, string texturePath, Vector2 position, bool visible = true, GuiPriority priority = GuiPriority.Above) 142 | { 143 | return new FramePanel(texturePath) 144 | { 145 | ID = _uiCount++, 146 | Name = name, 147 | Position = position, 148 | Texture = texture, 149 | Visible = visible, 150 | Priority = priority, 151 | }; 152 | } 153 | 154 | /// 155 | /// Creates a new modal panel. 156 | /// 157 | /// The name of the modal panel. 158 | /// The texture of the modal panel. 159 | /// The path to the texture of the modal panel. 160 | /// The position of the modal panel. 161 | /// Whether the modal panel is visible or not. Default is false. 162 | /// The priority of the modal panel. Default is GuiPriority.Above. 163 | /// A new instance of the class. 164 | public Modal CreateModal(string name, Texture texture, string texturePath, Vector2 position, bool visible = false, GuiPriority priority = GuiPriority.Above) 165 | { 166 | return new Modal(texturePath) 167 | { 168 | ID = _uiCount++, 169 | Name = name, 170 | Position = position, 171 | Texture = texture, 172 | Visible = visible, 173 | Priority = priority, }; 174 | } 175 | 176 | /// 177 | /// Creates a ButtonPanel object with the specified parameters. 178 | /// 179 | /// The name of the ButtonPanel. 180 | /// The texture for the ButtonPanel. 181 | /// The path to the texture for the ButtonPanel. 182 | /// The position of the ButtonPanel. 183 | /// The layout of the ButtonPanel (enum of type GridLayout). 184 | /// The X offset between each element in the ButtonPanel. Default is 0. 185 | /// The Y offset between each element in the ButtonPanel. Default is 0. 186 | /// The priority of the ButtonPanel (enum of type GuiPriority). Default is GuiPriority.Normal. 187 | /// The number of rows in the ButtonPanel. Default is 1. 188 | /// The number of columns in the ButtonPanel. Default is 1. 189 | /// The initial visibility state of the ButtonPanel. Default is false. 190 | /// The created ButtonPanel object 191 | public ButtonPanel CreateButtonPanel(string name, Texture texture, string texturePath, Vector2 position, GridLayout layout, int offsetX = 0, int offsetY = 0, GuiPriority priority = GuiPriority.Normal, int numberOfRows = 1, 192 | int numberOfColumns = 1, bool visible = false) 193 | { 194 | return new ButtonPanel(texturePath) 195 | { 196 | ID = _uiCount++, 197 | Name = name, 198 | Priority = priority, 199 | Texture = texture, 200 | Position = position, 201 | Layout = layout, 202 | OffsetX = offsetX, 203 | OffsetY = offsetY, 204 | RowCount = numberOfRows, 205 | ColumnCount = numberOfColumns, 206 | Visible = visible 207 | }; 208 | } 209 | 210 | /// 211 | /// Creates a StackPanel object with the specified parameters. 212 | /// 213 | /// The name of the ButtonPanel. 214 | /// The texture for the ButtonPanel. 215 | /// The path to the texture for the ButtonPanel. 216 | /// The position of the ButtonPanel. 217 | /// The layout of the ButtonPanel (enum of type GridLayout). 218 | /// The X offset between each element in the ButtonPanel. Default is 0. 219 | /// The Y offset between each element in the ButtonPanel. Default is 0. 220 | /// The priority of the ButtonPanel (enum of type GuiPriority). Default is GuiPriority.Normal. 221 | /// The number of rows in the ButtonPanel. Default is 1. 222 | /// The number of columns in the ButtonPanel. Default is 1. 223 | /// The initial visibility state of the ButtonPanel. Default is false. 224 | /// The created ButtonPanel object 225 | public StackPanel CreateStackPanel(string name, Texture texture, string texturePath, Vector2 position, GridLayout layout, float offsetX = 0, float offsetY = 0, GuiPriority priority = GuiPriority.Normal, int numberOfRows = 1, 226 | int numberOfColumns = 1, bool visible = false) 227 | { 228 | return new StackPanel 229 | { 230 | ID = _uiCount++, 231 | Name = name, 232 | Priority = priority, 233 | Texture = texture, 234 | Position = position, 235 | Layout = layout, 236 | OffsetX = offsetX, 237 | OffsetY = offsetY, 238 | RowCount = numberOfRows, 239 | ColumnCount = numberOfColumns, 240 | Visible = visible, 241 | TexturePath = texturePath 242 | }; 243 | } 244 | 245 | #endregion 246 | 247 | #region Components 248 | 249 | public Label CreateLabel(IUIPanel parent, Vector2 position, string text, int fontSize, Color fontColor) 250 | { 251 | return new Label 252 | { 253 | Parent = parent, 254 | Position = position, 255 | Text = text, 256 | FontSize = fontSize, 257 | Color = fontColor 258 | }; 259 | } 260 | 261 | /// 262 | /// Creates a new TextureButton and adds it to the specified parent IUIPanel. 263 | /// 264 | /// The parent IUIPanel to add the button to. 265 | /// The NxNode representing the button. 266 | /// The position of the button. 267 | /// An optional callback to be executed when you hover over the button. 268 | /// An optional callback to be executed when you click on the button. 269 | /// A new TextureButton instance. 270 | public TextureButton CreateButton(IUIPanel parent, NxNode button, Vector2 position, Action? hover = null, Action? click = null) 271 | { 272 | return new TextureButton(button) 273 | { 274 | Parent = parent, 275 | ID = parent.Nodes.Count, 276 | Name = button.Name, 277 | Position = position, 278 | OnHover = hover, 279 | OnClick = click 280 | }; 281 | } 282 | 283 | /// 284 | /// Creates a new Checkbox component within a specified parent panel. 285 | /// 286 | /// The parent panel that will contain the checkbox. 287 | /// The NxNode representing the checkbox. 288 | /// The position of the checkbox within the parent panel. 289 | /// An optional callback function that will be invoked when the checkbox is clicked. 290 | /// The newly created Checkbox component. 291 | public Checkbox CreateCheckbox(IUIPanel parent, NxNode checkBox, Vector2 position, Action? callback) 292 | { 293 | return new Checkbox(checkBox) 294 | { 295 | Parent = parent, 296 | ID = parent.Nodes.Count, 297 | Name = checkBox.Name, 298 | Position = position 299 | }; 300 | } 301 | 302 | /// 303 | /// Creates a decal UI component and adds it to the specified parent panel. 304 | /// 305 | /// The parent panel to add the decal to. 306 | /// The texture for the decal. 307 | /// The path to the texture. 308 | /// The name of the decal. 309 | /// The position of the decal. 310 | /// The created decal. 311 | public Decal CreateDecal(IUIPanel parent, Texture texture, string texturePath, string name, Vector2 position) 312 | { 313 | return new Decal 314 | { 315 | Parent = parent, 316 | ID = parent.Nodes.Count, 317 | Texture = texture, 318 | TexturePath = texturePath, 319 | Name = name, 320 | Position = position 321 | }; 322 | } 323 | 324 | /// 325 | /// Creates a TextBox UI component. 326 | /// 327 | /// The parent IUIPanel that the TextBox belongs to. 328 | /// The name of the TextBox. 329 | /// The size of the TextBox. 330 | /// The placeholder text to display in the TextBox. 331 | /// The character limit for the TextBox. 332 | /// Whether the TextBox is hidden or visible. 333 | /// The position of the TextBox. 334 | /// The created TextBox. 335 | public TextBox CreateTextbox(IUIPanel parent, string name, Vector2 size, string placeholder, int characterLimit, bool isHidden, Vector2 position) 336 | { 337 | return new TextBox() 338 | { 339 | Parent = parent, 340 | ID = parent.Nodes.Count, 341 | Name = name, 342 | Size = size, 343 | Placeholder = placeholder, 344 | CharacterLimit = characterLimit, 345 | Hidden = isHidden, 346 | Position = position, 347 | Text = string.Empty 348 | }; 349 | } 350 | 351 | #endregion 352 | 353 | #endregion 354 | 355 | /// 356 | /// Retrieves the UI panel with the specified name. 357 | /// 358 | /// The name of the UI panel to retrieve. 359 | /// The UI panel with the specified name, or null if not found. 360 | public IUIPanel? GetUI(string name) 361 | { 362 | return _interfaces.Find(x => x.Name == name) ?? null; 363 | } 364 | 365 | /// 366 | /// Checks whether the given panel has focus based on the mouse input. 367 | /// If the mouse is not within the bounds of the panel and the left mouse button is pressed, 368 | /// the panel is set to inactive. 369 | /// If the mouse is within the bounds of the panel and the left mouse button is pressed, 370 | /// the panel is set to active. 371 | /// 372 | /// The input manager for retrieving mouse position and button state. 373 | /// The panel to check focus for. 374 | public void CheckPanelFocus(InputManager input, IUIPanel panel, Rectangle bounds) 375 | { 376 | if (!Raylib.CheckCollisionPointRec(input.MouseToWorld, bounds)) 377 | { 378 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 379 | { 380 | panel.Active = false; 381 | } 382 | } 383 | 384 | if (Raylib.CheckCollisionPointRec(input.MouseToWorld, bounds)) 385 | { 386 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 387 | { 388 | panel.Active = true; 389 | } 390 | } 391 | } 392 | 393 | /// 394 | /// Checks the focus of a UI node based on the mouse position. 395 | /// If the mouse is outside the bounds of the node and the left mouse button is pressed, 396 | /// the node's Active property is set to false. 397 | /// If the mouse is inside the bounds of the node and the left mouse button is pressed, 398 | /// the node's Active property is set to true. 399 | /// 400 | /// The InputManager instance used to get the mouse position. 401 | /// The UI component to check the focus of. 402 | public void CheckNodeFocus(InputManager input, IUIComponent node, Rectangle bounds) 403 | { 404 | if (!Raylib.CheckCollisionPointRec(input.MouseToWorld, bounds)) 405 | { 406 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 407 | { 408 | node.Active = false; 409 | } 410 | } 411 | 412 | if (Raylib.CheckCollisionPointRec(input.MouseToWorld, bounds)) 413 | { 414 | if (Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) 415 | { 416 | node.Active = true; 417 | } 418 | } 419 | } 420 | 421 | #region Draw/Update/Pending 422 | 423 | public void Draw(float frameTime) 424 | { 425 | foreach (var ui in _interfaces) 426 | { 427 | if (!ui.Visible) continue; 428 | ui.Draw(frameTime); 429 | } 430 | } 431 | 432 | public void Update(float frameTime) 433 | { 434 | foreach (var ui in _interfaces) 435 | { 436 | if (!ui.Visible) continue; 437 | ui.Update(frameTime); 438 | } 439 | ProcessPending(); 440 | } 441 | 442 | private void ProcessPending() 443 | { 444 | lock (_threadLock) 445 | { 446 | while (_addQueue.Count > 0) 447 | { 448 | var ui = _addQueue.Dequeue(); 449 | _interfaces.Add(ui); 450 | _interfaces.Sort((a, b) => Convert.ToInt32(a.Priority > b.Priority)); 451 | } 452 | while (_removeQueue.Count > 0) 453 | { 454 | var ui = _removeQueue.Dequeue(); 455 | _interfaces.Remove(ui); 456 | ui.Clear(); 457 | } 458 | 459 | while (_changeQueue.Count > 0) 460 | { 461 | var ui = _changeQueue.Dequeue(); 462 | _interfaces.Remove(ui); 463 | _interfaces.Add(ui); 464 | _interfaces.Sort((a, b) => Convert.ToInt32(a.Priority > b.Priority)); 465 | } 466 | } 467 | } 468 | 469 | #endregion 470 | } -------------------------------------------------------------------------------- /Client/UpdateLog.txt: -------------------------------------------------------------------------------- 1 | Login Updates (8.10.24) 2 | --Added pants rendering to player. 3 | --Added Select/Create/Delete button to character board. 4 | --Changed values within the stack panel to float, for more accurate adjustments. 5 | --Fixed an issue with pants not having the correct information, never copypasta. 6 | 7 | --Added dummy character to creation area, whatever you wanna call it. 8 | --Added null checking in a lot of areas. 9 | --Fixed face placement, it was off by a few pixels because subtraction order. 10 | --Fixed actor sorting, backgrounds were sorted incorrectly. 11 | 12 | 13 | Login & Overall changes (8.9.24) 14 | --Added support for MS Beta (Data.nx), works and renders just fine. 15 | --Added support for modern servers, packets get read in 4 byte length and 2 byte opcode. 16 | --Fixed Worlds and Channels not spawning correctly, this was due to them being created together. 17 | --Fixed multiple worlds and channels being spawned even though I only requested one. 18 | --Fixed some issues with the parsing of the string pool for NX files. 19 | --Fixed an issue with In/Out packets not sending the correct information. 20 | --Fixed having to skip 2 bytes when the packet was being parsed, turns out the dummy 21 | server had an opcode being added in the beginning for some reason. 22 | 23 | --Added Equipment list to Avatar Look, this may change later but for now it works. 24 | --Added Cape rendering 25 | --Added weapon (only behind hand) rendering 26 | --Added coat (not long coat) rendering 27 | --Added variables to hold values to send to the server when selecting a world. Do know this is still very 28 | experimental, no guarantees. 29 | --Removed part layers, why did I waste all that time if I was going to manually sort them anyways.... 30 | 31 | 32 | Character Changes (8.8.24) 33 | --Added Changing layers, not tested yet. 34 | --Removed redundant code. 35 | 36 | Character Update (8.7.24) 37 | --Added Hair class since hair is completely difference from body parts. 38 | --Added hair rendering to the player, works both flipped and normal. 39 | --Added HairType enum, for you not to guess what type of hair to render. 40 | 41 | --Added PartLayer for the sorting that the player has. 42 | --Added a list of the layers in the Player class as well, used for later. 43 | --Removed Hair class, it's combined with the body part class now, along with equipment. 44 | --Removed HairType, no longer needed. 45 | --Removed IPart, proved to be useless. 46 | --Removed AvatarLayer, it basically got replaced by PartLayer. 47 | --Massive changes through the Player class, it's a lot so go through it. 48 | 49 | Login Screen & Background updates (8.6.24) 50 | --Added Horizontal tiling of the backgrounds. 51 | --Added Horizontal scrolling of the backgrounds. 52 | --Added a few variables to the graphics manager, it'll start getting used more later. 53 | --Defaulted the window width/height to 800x600, can be increased with Alt+Enter to 54 | 1024x768. Do note, I've only tested this with 4:3 ratio windows, use others at 55 | you own risk of things looking wonky. 56 | --Added IsLeft, OnGround, and Moving variables to the Player class, also locked controls when 57 | in login screen. 58 | --Fixed an issue when resizing windows, the background scrolling would increase 10x. 59 | --Fixed documentation to clarify what version the legacy and "New" UI support. 60 | 61 | Fixed issues with UI again, added a few things (8.5.24) 62 | --Added WorldInfo to WorldManager for easy access, since only one world will 63 | be loaded at a time, this is fine. 64 | --Added WorldBounds to WorldManager for the same reason. 65 | --Added BackgroundType detection to the Background class. 66 | --Added support for HorizontalTiling textures. 67 | --Added ScreenOffset & DstRectangle to IActor, the player is not likely to use this. 68 | --Fixed an issue with Textboxes not correctly being positioned when window is resized, 69 | I thought I fixed this earlier, but I didn't. 70 | --Fixed login signboard text boxes. 71 | --Fixed decal positioning based on the parent, same issue as the text boxes. 72 | --Removed all exceptions from the NxNode file, except important ones, this is for a good reason. 73 | 74 | Fixed more UI issues & Updated some other components (8.5.24) 75 | --Added OnHover and OnClick for the TextureButton. 76 | --Added ScreenOffset and DstRectangle to all UIPanels and UIComponents. 77 | --Added the new hover and click callbacks to the button creation. 78 | --Fixed StackPanel, didn't update it when I updated ButtonPanel. 79 | --Fixed some documentation that wasn't consistent with what was happening. 80 | --Fixed checkboxes, adding states and fixed rendering. 81 | --Fixed StaticPanel Focusing not working. 82 | --Fixed Textboxes not aligning correctly when window resized. 83 | 84 | Changed how actors are rendered (8.5.24) 85 | --Removed the dictionary of actors, it was getting complicated to handle the differences between each actor. 86 | --Added player spawning into the login screen. 87 | --Added selecting the player and changing the state to the walking state. 88 | --Added a FullScreen flag for the game. 89 | --Changed the screen width and height to getters setters. 90 | --Removed world class for now, since I'm not going to focus on that for now. 91 | --Fixed ButtonPanel not working properly due to new ratio changes. 92 | --Fixed Modal not working properly. 93 | --Fixed StaticPanel having few issues due to ratio changes. 94 | 95 | Dropped Json & Added Aspect Ratio stuff (8.4.24) 96 | --Technically there's a lot missing in the in-between since August 1st, 97 | I got lazy and didn't update the log. 98 | --Removed the last remaining bits out of the MapObject and Background classes, everything is set manually now. 99 | --Removed all traces of the Yaml and Json systems, they were useless. I will need to develop a custom solution. 100 | --Removed a lot of redundant code, mostly in the UI, texture buttons specially is one of them. 101 | --Officially added labels, works as advertised. 102 | --Added custom label on upper right of client, show version being working on and the build type. For now it's entered manually. 103 | --Added Texture resizing for all map objects, backgrounds, and UI components. 104 | --Added AvatarStats & AvatarLook classes...they look the same I might combine them. 105 | --Added a lot of documentation, will go through them once again to make sure it makes sense. 106 | --Fixed all issues with the UI components not working due to resizing. 107 | --Warning, resizing has only been tested with 800x600 and 1024x768. 108 | --Note, I am referencing available sources for most information, that's where the guess-timations I was talking 109 | about kick in, the information in there mostly dictated how I rendered and structred things. Not the best reference, 110 | but looking through PDBs wastes too much time and I personally don't have a lot of that. 111 | 112 | 113 | Working on Json Loading System (8.1.24) 114 | --Added Newtonsoft.Json to create asset files, will go more into this later. 115 | --Added AssetManager, to load already made scene or ui files. 116 | --Removed yaml, didn't like the way it did things, and also had a lot of broken features, 117 | that would cause problems if this was built in AOT mode. 118 | --Json ignored the NxNode, might changed this later 119 | 120 | Migrations & Optimizations (7.31.24) 121 | --Moved all code inside the ActorManager, aside from a few things, inside the Login (will move world later). 122 | --Moved to the enums for the Guis into a enum folder. 123 | --IWorld now handles a lot of the functionality that ActorManager had. 124 | --Reason for the move is because ActorManager was not acting like that at all, so it was getting annoying, 125 | so from now on it will only handle creation and deletion of the actors. 126 | --Added backgroundtype to backgrounds. 127 | --Completed legacy sign-in page, works fine, grabbing worlds and channels work as well. 128 | --[TO-DO] Need to fix the alignment of a few things, as well as fix the width and height of some textures. 129 | --Attempted to add Yaml parsing, it works fine for certain things, but overall it's kind of useless. 130 | --I may add it officially later when custom content comes into play. 131 | 132 | UI Updates (7.30.24) 133 | --Added ButtonPanel, replaces ButtonGroup, only accepts texture buttons for now. 134 | --Added Channel and Button board to the world button. This is still being worked on. 135 | --Added condition to check whether or not a button has the states needed, not all buttons have 136 | a pressed or hover state. 137 | --Added a few methods to the UIManager. 138 | --Added Label component, used for later on. Not tested but pretty sure it works. 139 | --Added GridLayout, used in ButtonPanel, but will be used in stack panel as well. 140 | --Changed some of the parameters to the creation of panels and components. 141 | --Changed Dictionary to list in IUIPanel. 142 | --Changed IUINode to IUIComponent, so it makes more sense. 143 | --Changed location of clear method inside IUIComponent, so it makes more consistent layouts. 144 | 145 | --Added OffsetX/Y into the button panel. 146 | --Fixed offset issue with ButtonPanel 147 | --Fixed collision detection with buttons in the panel 148 | --Fixed the column/row issue, I had them flipped. 149 | --Fixed TextureButtons not working properly with two-state buttons. 150 | --Moved ButtonPanel to the panel folder. 151 | 152 | --Added Bounds checking to the InPacket 153 | --Added IDisposeable to both InPacket & OutPacket. 154 | --Added all code from ButtonPanel to StackPanel, there's a difference between these two. One is specifically for 155 | buttons, you will see why if you try to add something else that is not a button to it. 156 | --Added a change queue to the UIManager, in case a UI changes so much to the point it needs to be reloaded. 157 | --Added an instant condition to adding UI panels, this is only used for the login, all others should not trigger this. 158 | --Added moving to character screen in newer versions, working on legacy now. 159 | --Found a problem with the sorting of the newer versions, a background that should be in the front is covering the char select. 160 | --Removed all dispatches for packets in the actor manager, was possibly the cause for packet losses. 161 | --Removed ProcessPacket from IActor 162 | 163 | 164 | UI Updates (7.29.24) 165 | --Added ScreenFrame, this will mainly be used for the login and primary UI stuff. 166 | --Added the sorting backing into the actor manager 167 | --Added ValidateActors to the end of the update cycle. 168 | --Added the parent object to the UINodes 169 | --Added IDs to the UINodes that are independent of the UIPanel, these are to keep track of which ID 170 | is currently active in the panel, will be more useful later. 171 | --Added a few more function into the UI manager. 172 | 173 | --Added a place to change the file path for now, not going to make this a foreach or for loop. 174 | --Added disposable to the NXFile, NXReader, and NXNode. 175 | --Changed IUIContainer to IUIPanel 176 | --Changed how the panels behave and some of the exposed variables. 177 | 178 | --Added a bool for a pseudo editor, this will be implemented later one and eventually built upon. 179 | --Added a few varibles into the application.cs since I forgot to do that earlier. 180 | --Added bounds checking to InPacket and default to 0 value if it goes over, this should cause most packet checks to fail. 181 | --Added World button creation. 182 | --Added Channel panel creation. 183 | --Added World btn press functionality. 184 | --Changed the modern UI version limit, turns out I'm working a post-BB UI, I thought it started in v83. 185 | --Removed a few unnecessary lines of code here and there. 186 | --Fixed an issue where all UI textures weren't unloading, whoops. 187 | 188 | 189 | UI Updates (7.28.24) 190 | --Added Modal, for notices and other notification, this takes order priority over any UI components. 191 | --Added UI priority to determine how the UI should be drawn, most are normal. 192 | --Added documentation to more functions. 193 | --Added a few new methods to the Login world. 194 | --Added a containers and components folder in the gui to differentiate the various parts. 195 | --Changed how the UI system behaves overall, had to change some of the coordinates since it no longer applied to the UI. 196 | --Removed any unnecessary parts that are no longer used. 197 | 198 | UI Updates (7.27.24) 199 | --Added IUserInterface, for the lack of a better name 200 | --Added StaticContainer, to store UIs that do not move. 201 | --Added UIManager, to control what happens to the UI. 202 | --Added StackContainer, incomplete. 203 | --Changed the primary function of the UI, no longer part of the actor system 204 | --Changed where the drawing occurs with the camera, it's not in the main application. 205 | --Changed all previous UI code to match the form of the UI system. 206 | --Changed the log name to updatelog. Makes more sense. 207 | --Removed all UI functions from the ActorManager, still needs to be cleaned up. 208 | 209 | Login & UI Updates (7.26.24) 210 | --Added a notice popup for the login response, depending on the login response 211 | depends on the notice. 212 | --Added a NoticeType enum to keep track of the different texts, too many... 213 | --Changed the Process method to HandlePacket, and the other to something 214 | similar 215 | --Added TextBox to create text boxes, each box has a parameter if you want 216 | to hide the text. 217 | --Added InputManager and GraphicsManager to start cleaning up all the raylib code. 218 | --Updated documentation to some classes, not all. Some are still in development so 219 | they may change a few more times before I solidify the design. 220 | --Added GetActorByName, needs to be optimized 221 | --Added GetMouseWheel to input manager. 222 | --Changed a few region names. 223 | --Changed position of frame variable in login. 224 | 225 | Client & Dummy Server Updates (7.25.24) 226 | --Made the update threads concurrent now, so they don't have data clashes. 227 | --Made it so that the packets get queued instead of immediately processed, 228 | this fixed an issue where network actors were not appearing. (Raylib texture, 229 | even XNA/Monogame/FNA, are not multithreaded). 230 | --Added a bool to check if the window is closed, so that we can successfully exit 231 | the window without socket errors. 232 | --Added a function to process the packet responses (InPackets) 233 | --Added a few try-catch to functions that tend to crash for dumb reasons. 234 | --Added a decal UI Node, this is pretty much a static image, some UI require it. 235 | --Changed the foreach loops to for loops in the actor system, to prevent the 236 | error that foreach loops cause. 237 | --Changed the name of the PacketResponse/Request to InPacket/OutPacket 238 | --Fixed the positioning of the checkboxes, forgot to subtract the parent position. 239 | --Moved the packet processing to their respective world 240 | 241 | Dummy Server (7.23.24 - 7.24.24) 242 | --Added a DummyServer to the solution. 243 | --Login portion of the server is almost complete. 244 | --Login portion now effectively connects with the client and receives data. 245 | --If the client disconnect without reason it doesn't cause an error. 246 | --Will begin channel and world servers later on. 247 | 248 | UI & Login Updates (7.23.24) 249 | --Added a UIContainer that will contain all UI components within it. 250 | --Added IUINode, which are placed within a UIContainer. 251 | --Added CreateButton, CreateCheck, and CreateContainer functions. 252 | --Added a function callback to both checkbox and buttons. 253 | --Added FullPath to NxNode, in order to call actors by their name. 254 | --Added the Login Button, Exit Button, and Sign board to the login screen. 255 | --Added an AppConfig so you can switch between version, I'll make it more intuitive later. 256 | --Added a condition when the login is loaded to check for which type of login to create. 257 | --Added a call back to the login button so it sends you to the correct location of the login screen. 258 | --Changed the ActorManager draw function so it only looks for visible actors. 259 | --Changed the ID in the actors to only be set upon initialization. 260 | 261 | --Added packet receiving and responding to client. It's very messy right now, so still in a super alpha phase. 262 | --Added packet sending to the login button, it send the packet successfully, all that left is the process the 263 | login status correctly and we can move forward. 264 | --Changed the network server, I will say this my server only accepts a short header and a short opcode, so modify your, or 265 | change the length to 6 in the network manager. 266 | --Removed a lot of redundant functions. 267 | --TextureButtons can now be disabled and won't switch back to normal for no reason. 268 | --CheckBoxes and TextureButtons now dispose their textures properly...oops. 269 | --Added a GetNode function to the NxManager, allows you to get the image files without calling the file. 270 | --Decided to push the version I'm working on to 113, I believe its a good balance between old and new content. 271 | --PacketProcessor now distributes the packets properly. 272 | --World and Actors now response to packets being injected. 273 | 274 | Began work on UI & Networking integration (7.22.24) 275 | --Added TextureButton to handle most buttons in the game, the UI will still be a part of the 276 | ActorSystem. 277 | --Added ButtonState. 278 | --Added an event delegate for the GUIs to handle custom events when they're clicked. 279 | --TextureButtons now change the way they need to, for the most part. I just started. 280 | --Checkboxes change the way the need to..its two states so it's kind of hard to mess this up. 281 | --Added UI to the drawing sequence. 282 | --Changed the button state names to match the ones in the image. 283 | --Removed the redundant Update and Draw functions in the application. 284 | --Removed direct calling of map objects (animated or not) from the map laoder. 285 | --All objects for the maps are now abstracted away from the map, so that if an event happens that a new object is needed, it can be spawned. 286 | 287 | Performance Enhancements (7.21.24) 288 | --Added IActor and removed Actor, there was no need to have that inheritance. 289 | --Added CreateBackground, CreateTile, to ActorManager, will add more soon. 290 | --Changed the remaining actors to inherit IActor, everything works fine. 291 | --Enhance overall performance, Henesys used to take about 3.5 seconds to load, now it's about 1.5 seconds. 292 | --Wrapped the update function in a Task, so that it runs individually from the rest of the application. 293 | 294 | Began Login work (7.19.24) 295 | --Separated loading from the world class into a MapLoader class. 296 | --Streamlined creating worlds to wait till it's done loading. 297 | --Changed a few things in MapObject class. 298 | --Started the process of creating a world state. 299 | 300 | Client Updates (7.18.24) 301 | --Added OneTime/Counted animations 302 | --Added Blended animations (kind of weird for now) 303 | --Added LoopAnimation for all others. 304 | --Added same loop up method in WZ library from NX library. 305 | --Fixed an issue where the animated objects was not properly loading for login screen. 306 | --Fixed an issue where the animated backgrounds were not loading properly for login screen. 307 | 308 | Networking, Actors, Wz Parsing (7.14.24) 309 | --Began restructuring the networking system, I use my own server so there's no guarantee that 310 | it'll remain compatible with other servers, but I will do my best to keep it consistent. 311 | --Began restructuring the actor system, now that there is something that works as it should, 312 | I'm going to start restructuring it so that it will work with the networking. 313 | --Began working on a WZ parser, because why not. Credits for the information are within the files. 314 | --Renamed RecvOps and SendOps to match what their packet is called. 315 | 316 | Nothing Much (7.13.24) 317 | --Added a CreateLogin function to the WorldManager 318 | --Added UI nx to the NxManager 319 | --Added clearing of the two lists in the player class, oops 320 | --Added condition checking if the player is in the ladder state 321 | --Removed ref out of Background 322 | 323 | Character Updates (7.12.24) 324 | --Added AvatarLayers, this will be used later one when equipment comes into play. 325 | --Added Character flipping on the X-axis, not perfect but it works for now. 326 | --Added the remainder Body types to the enum. 327 | --Added an AvatarState enum to make it easier to shift between states without knowing the name. 328 | --Added a possible state list so that you know the difference between states and actions. 329 | --Added a frame count list, that you know exactly how much frames are in each state. 330 | --Added Character movement, pretty fast for now. 331 | --Added Character StateChange upon specific movement. 332 | --Added simple, not complete, detection if the character is on a ladder, so it only updates the character when the player moves. 333 | --Added a camera update function to the world manager, since it should handle these sort of things. 334 | --Separated the Origin and Positions of the actors, it was getting a bit confusing. 335 | --Changed how the player is initiated, all states are loaded prior. Then the default is set. 336 | --Changed the ChangeState method, so it picks a state instead of creating one. 337 | --NOTE: All these lists (i.e. AvatarState, possible states, and frame count) ARE ONE TO ONE, if you even touch one of the values it will mess everything up. So be careful. 338 | --Fixed an issue with animation cycling (a test I ran to make sure everything works). All animation now work as the should. 339 | --Fixed an issue where the textures weren't clearing correctly in the player. 340 | 341 | Character & Map Update (7.11.24) 342 | --Added RayGui stuff into the client. 343 | --Added Face into the player, bodytype, and bodypart. 344 | --Added separate update methods for body and face, still needs to be touched up a bit. 345 | --Added a special case for Character.nx in the ReadNodeData method, it now grabs the appropriate nodes. 346 | --Added a command window, it's going to be used for debugging, changing/adding stuff dynamically, once things get much further. 347 | --Added BodyPart code that encompasses all cases, for now. 348 | --Added BodyType enum 349 | --Added Arm struct to handle the arms of the player 350 | --Added Clear method to Body, Head, Arm classes, to release the textures. 351 | --Added a check where the state doesn't need the face rendered. 352 | --Added random delay to the blinking, it's kind of fast right now, but it works. 353 | --Fixed an issues where the wrong values for the state were being called 354 | --Fixed an animation issue for both MapObjects and the character where the last frame was being skipped. 355 | --Fixed the positioning of all body parts of the player. 356 | --Changed the ActorManager so it only detects actors that can be click on, not all of them. 357 | --Face renders correctly, for now. Had to use an Abs function, which I'm not sure if that's correct. 358 | --Removed Arm, Body, Head classes. 359 | --Removed ResourceManager, not useful right now. 360 | --Removed Obstacle class, not needed right now. 361 | 362 | Experimental Avatar (7.10.24) 363 | -- Added ActorType to differentiate between the different actors 364 | -- Added Body, BodyPart, and Head to handle the creation of the player body for now 365 | -- Added the Player class which encompasses the whole body 366 | -- Added a GetPlayer method to ActorManager 367 | __ Made it so you can see the children in the node, in case you need the names instead of the count. 368 | -- Added a test player to the world class 369 | -- Changed how the world class is initialized so it makes more sense now. 370 | --Fixed an issue with other states that caused the client to crash. 371 | --Fixed an issue with the animation (Player/MapObject) that was iterating correctly. 372 | --Separated the different body rendering for efficiency. 373 | 374 | Small changes (7.8.2024) 375 | -- Removed the IComparable from the Actor class, not point having it there. 376 | -- Changed the actor dictionary to contain a List instead of a sorted set. 377 | -- (Possible Bug) Fixed the sorting, for now. It's hard to say since everything isn't there yet. 378 | -- Added a SortAll List, to sort everything once it's there to reduce sort calls. 379 | -- Fixed the issue with animated objects not being sorted, turns out I didn't even assign the order or layer. 380 | 381 | Rebase (7.1.24 - 7.7.24) 382 | -- Decided to take a different approach. 383 | -- Removed FNA support, amazing library, but lacks the features needed to properly emulate MS. 384 | -- Removed all files, literally. 385 | -- Added Raylib-CSLo 386 | -- Added Networking Support (doesn't use encryption) 387 | -- Added NetworkManager 388 | -- Added ActorManager 389 | -- Added Tile loading 390 | -- Added Object loading 391 | -- Internal structure changes. 392 | -- Made it so each layer is their own-sorted set, having different set allows easier transferring between layers. 393 | -- Fixed the frame time calculation. 394 | -- Added background loading, nothing special yet. 395 | -- Added an animated boolean to both MapObject and Background 396 | -- Added a Has method, without the out variable to the NxNode. 397 | -- Added a camera to the world, this will be the primary camera, nothing special yet. 398 | -- Fixed an issue where the maps were unloaded twice. 399 | -- Fixed an issues where the animations were taking forever to update. 400 | -- Found an issue with the sorting system, will fix later. --------------------------------------------------------------------------------