diff --git a/Sphere/Sphere.Client/Services/Handlers/BaseHandler.cs b/Sphere/Sphere.Client/Services/Handlers/BaseHandler.cs index 46b0f73f..7d6a494d 100644 --- a/Sphere/Sphere.Client/Services/Handlers/BaseHandler.cs +++ b/Sphere/Sphere.Client/Services/Handlers/BaseHandler.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Sphere.Common.Helpers.Extensions; using Sphere.Common.Interfaces.Tcp; using Sphere.Common.Packets; @@ -19,7 +18,6 @@ protected BaseHandler(ILogger logger, IClientAccessor tcpClientAccessor) protected async Task SendPacket(byte[] rcvBuffer) { await _clientAccessor.Client.WriteAsync(rcvBuffer); - _logger.PacketSent(rcvBuffer, _clientAccessor.ClientId); } protected async Task TerminateConnection() diff --git a/Sphere/Sphere.Client/Services/Readers/SpherePacketReader.cs b/Sphere/Sphere.Client/Services/Readers/SpherePacketReader.cs index e1eda750..dee73fc7 100644 --- a/Sphere/Sphere.Client/Services/Readers/SpherePacketReader.cs +++ b/Sphere/Sphere.Client/Services/Readers/SpherePacketReader.cs @@ -1,6 +1,9 @@ -using Sphere.Common.Interfaces.Packets; +using Microsoft.Extensions.Logging; +using Sphere.Common.Helpers.Extensions; +using Sphere.Common.Interfaces.Packets; using Sphere.Common.Interfaces.Readers; using Sphere.Common.Interfaces.Tcp; +using Sphere.Services.Services.Tcp; namespace Sphere.Services.Readers { @@ -13,11 +16,13 @@ namespace Sphere.Services.Readers public class SpherePacketReader : IPacketReader { private readonly IClientAccessor _tcpClientAccessor; + private readonly ILogger _logger; private PacketBase _current; - public SpherePacketReader(IClientAccessor tcpClientAccessor) + public SpherePacketReader(IClientAccessor tcpClientAccessor, ILogger logger) { _tcpClientAccessor = tcpClientAccessor; + _logger = logger; } public PacketBase Current => _current; @@ -55,6 +60,8 @@ public async ValueTask MoveNextAsync() _current = basePacket; + _logger.PacketReceived(_current.OriginalMessage, _tcpClientAccessor.ClientId); + return true; } diff --git a/Sphere/Sphere.Client/Services/Tcp/SphereTcpClient.cs b/Sphere/Sphere.Client/Services/Tcp/SphereTcpClient.cs index 163f899a..020332a6 100644 --- a/Sphere/Sphere.Client/Services/Tcp/SphereTcpClient.cs +++ b/Sphere/Sphere.Client/Services/Tcp/SphereTcpClient.cs @@ -1,4 +1,6 @@ -using Sphere.Common.Interfaces.Tcp; +using Microsoft.Extensions.Logging; +using Sphere.Common.Helpers.Extensions; +using Sphere.Common.Interfaces.Tcp; using System.Net.Sockets; namespace Sphere.Services.Services.Tcp @@ -6,10 +8,14 @@ namespace Sphere.Services.Services.Tcp public class SphereTcpClient : ITcpClient, IDisposable { private readonly TcpClient _tcpClient; + private readonly IClientAccessor _clientAccessor; + private readonly ILogger _logger; - public SphereTcpClient(TcpClient tcpClient) + public SphereTcpClient(ILogger logger, TcpClient tcpClient, IClientAccessor clientAccessor) { _tcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient)); + _clientAccessor = clientAccessor; + _logger = logger; } public bool Connected => _tcpClient.Connected; @@ -20,7 +26,11 @@ public SphereTcpClient(TcpClient tcpClient) public async Task ReadAsync(byte[] buffer, int offset, int count) => await GetStream().ReadAsync(buffer, offset, count); - public async ValueTask WriteAsync(byte[] buffer) => await GetStream().WriteAsync(buffer); + public async ValueTask WriteAsync(byte[] buffer) + { + await GetStream().WriteAsync(buffer); + _logger.PacketSent(buffer, _clientAccessor.ClientId); + } public Stream GetStream() => _tcpClient.GetStream(); diff --git a/Sphere/Sphere.Client/Services/Utils/PacketDefinitionParser.cs b/Sphere/Sphere.Client/Services/Utils/PacketDefinitionParser.cs new file mode 100644 index 00000000..f37237bd --- /dev/null +++ b/Sphere/Sphere.Client/Services/Utils/PacketDefinitionParser.cs @@ -0,0 +1,67 @@ +using Sphere.Common.Interfaces.Utils; +using Sphere.Common.Packets; +using Sphere.Common.Types; + +namespace Sphere.Services.Services.Utils +{ + /// + /// Reads packet definitions and adds into a static library. + /// + public class PacketDefinitionParser : IPacketDefinitionParser + { + // Move to settings or wherever + private const string Folder = "../PacketDefinitions"; + private const char Separator = '\t'; + + private static readonly HashSet IgnoreParts = new HashSet { "__undef", "skip", "skip_1", "skip_100", "delimiter_test", "next_field", "field_length", "level_maybe" }; + + /// + /// Loads .spdp files from configured folder, parses it and create a ditionary of packet definitions. + /// + /// + public Dictionary Load() + { + if (!Directory.Exists(Folder)) + { + return new Dictionary(); + } + + var parts = Directory.EnumerateFiles(Folder, "*.spdp", SearchOption.AllDirectories).Select(file => + { + try + { + var parts = ReadFile(file); + var definition = new PacketDefinition(Path.GetFileNameWithoutExtension(file), parts.ToDictionary()); + + return definition; + } + catch (Exception) + { + return null; + } + }); + + return parts.ToDictionary(x => x.Name); + } + + private IEnumerable> ReadFile(string file) + { + var content = File.ReadAllLines(file); + return content.Select(str => + { + var split = str.Split(Separator, StringSplitOptions.RemoveEmptyEntries); + var name = split[0]; + var value = split[9]; + + if (IgnoreParts.Contains(name)) + { + name = Guid.NewGuid().ToString("N"); + } + + var bytes = Enumerable.Chunk(value, 8); + + return new KeyValuePair(name, new vByte(bytes.Select(b => Convert.ToByte(new string(b), 2)).ToArray(), (ushort)value.Length)); + }); + } + } +} diff --git a/Sphere/Sphere.Common/Enums/EntityActionTypeEnum.cs b/Sphere/Sphere.Common/Enums/EntityActionTypeEnum.cs new file mode 100644 index 00000000..c1a6a699 --- /dev/null +++ b/Sphere/Sphere.Common/Enums/EntityActionTypeEnum.cs @@ -0,0 +1,13 @@ +namespace Sphere.Common.Enums +{ + internal enum EntityActionTypeEnum + { + SET_POSITION = 0x06, + FULL_SPAWN = 0x7C, + FULL_SPAWN_2 = 0x7D, + ATTACK = 0x2A, + INTERACT = 0xA, + UNKNOWN = 0x14, + UNDEF + } +} diff --git a/Sphere/Sphere.Common/Enums/GameObjectTypeEnum.cs b/Sphere/Sphere.Common/Enums/GameObjectTypeEnum.cs new file mode 100644 index 00000000..888eecef --- /dev/null +++ b/Sphere/Sphere.Common/Enums/GameObjectTypeEnum.cs @@ -0,0 +1,119 @@ +namespace Sphere.Common.Enums +{ + public enum GameObjectTypeEnum : ushort + { + Despawn = 0, + UpdateState = 2, + Player = 4, + Token = 8, + Mutator = 30, + SeedCastle = 40, + XpPillDegree = 47, + DoorEntrance = 60, + DoorExit = 61, + TeleportWithTarget = 62, + Teleport = 63, + DungeonEntrance = 65, + TeleportWild = 67, + TokenMultiuse = 66, + TradeLicense = 68, + MobSpawner = 70, + TournamentTeleport = 71, + TutorialMessage = 72, + ScrollLegend = 90, + ScrollRecipe = 91, + Mission = 95, + TokenIsland = 104, + TokenIslandGuest = 105, + NpcQuestTitle = 205, + NpcQuestKarma = 208, + NpcQuestDegree = 209, + Monster = 210, + MonsterFlyer = 211, + NpcTrade = 213, + NpcBanker = 225, + Bead = 236, + NpcGuilder = 239, + BackpackLarge = 400, + BackpackSmall = 401, + Sack = 405, + Chest = 406, + SackMobLoot = 407, + MantraBookSmall = 409, + RecipeBook = 410, + MantraBookLarge = 411, + MantraBookGreat = 412, + MapBook = 413, + ChestInDungeon = 417, + KeyBarn = 418, + PowderFinale = 451, + PowderSingleTarget = 453, + PowderAmilus = 454, + PowderAoE = 455, + ElixirCastle = 471, + ElixirTrap = 472, + WeaponSword = 500, + WeaponAxe = 501, + WeaponCrossbow = 502, + Arrows = 503, + RingDiamond = 551, + RingRuby = 552, + Ruby = 553, + RingGold = 555, + AlchemyMineral = 600, + AlchemyPlant = 601, + AlchemyMetal = 602, + FoodApple = 650, + FoodPear = 651, + FoodMeat = 652, + FoodBread = 653, + FoodFish = 655, + AlchemyBrushwood = 700, + Key = 701, + Map = 703, + Inkpot = 704, + Firecracker = 705, + Ear = 706, + EarString = 708, + MonsterPart = 709, + Firework = 712, + InkpotBroken = 715, + ArmorChest = 750, + ArmorAmulet = 751, + ArmorBoots = 752, + ArmorGloves = 754, + ArmorBelt = 755, + ArmorShield = 756, + ArmorHelmet = 757, + ArmorPants = 758, + ArmorBracelet = 759, + Ring = 760, + ArmorRobe = 761, + RingGolem = 762, + AlchemyPot = 800, + AlchemyFurnace = 803, + Blueprint = 804, + Workshop = 805, + QuestArmorChest = 949, + QuestArmorChest2 = 950, // unused? + QuestArmorBoots = 952, + QuestArmorGloves = 953, + QuestArmorBelt = 954, + QuestArmorShield = 955, + QuestArmorHelmet = 956, + QuestArmorPants = 957, + QuestArmorBracelet = 958, // unused? + QuestArmorRing = 959, // unused? + QuestArmorRobe = 960, + QuestWeaponSword = 961, + QuestWeaponAxe = 962, + QuestWeaponCrossbow = 963, + SpecialGuild = 976, // sometimes different + SpecialAbility = 977, // same type for specialization itself + SpecialAbilitySteal = 979, // same type for specialization itself + ArmorHelmetPremium = 990, + MantraWhite = 1000, + MantraBlack = 1001, + Unknown = ushort.MaxValue + } +} diff --git a/Sphere/Sphere.Common/Enums/MosterTypeEnum.cs b/Sphere/Sphere.Common/Enums/MosterTypeEnum.cs new file mode 100644 index 00000000..65f18b04 --- /dev/null +++ b/Sphere/Sphere.Common/Enums/MosterTypeEnum.cs @@ -0,0 +1,679 @@ +using static Sphere.Common.Enums.MonsterType; + +namespace Sphere.Common.Enums +{ + public enum MonsterType + { + Stick_Insect, + Steppe_Stick_Insect, + Forest_Stick_Insect, + Cave_Stick_Insect, + Doghead, + Steppe_Doghead, + Forest_Doghead, + Cave_Doghead, + Rotten_Knight, + Rusty_Knight, + Restless_Knight, + Golden_Knight, + Giant_Knight, + Air_Knight, + Flier, + Storm_Flier, + Fire_Flier, + Amber_Flier, + Giant_Flier, + Earth_Golem, + Sand_Golem, + Clay_Golem, + Free_Golem, + Giant_Golem, + Bangville_Centipede, + Royal_Centipede, + Skeleton, + Forest_Skeleton, + Umrad_Skeleton, + Air_Skeleton, + Cave_Skeleton, + Lich, + Giant_Skeleton, + Air_Lich, + Assassin2, + Boar, + Mad_Boar, + Dry_Boar, + Air_Boar, + Iron_Boar, + Fire_Boar, + Giant_Boar, + Cyanos, + Dark_Cyanos, + Dry_Cyanos, + Iron_Cyanos, + Air_Cyanos, + Giant_Cyanos, + Wolf, + Steppe_Wolf, + Ice_Wolf, + Dry_Wolf, + Iron_Wolf, + Hell_Wolf, + Spider, + Sulfur_Spider, + Dry_Spider, + Iron_Spider, + Fire_Spider, + Black_Widow, + Giant_Spider, + Air_Spider, + Salamander, + Nomrad_Salamander, + Earth_Salamander, + Iron_Salamander, + Salamandiga, + Red_Scorpion, + Scorpion, + Blue_Scorpion, + Fire_Scorpion, + Typhon, + Smoky_Typhon, + Screeching_Typhon, + Iron_Typhon, + Fire_Typhon, + Giant_Typhon, + Ogre, + Sulfur_Ogre, + Cave_Ogre, + Giant_Ogre, + Niphon, + Green_Dragon, + Blue_Dragon, + Red_Dragon, + Steel_Dragon, + Giant_Dragon, + Cat, + Bull, + Tropos, + Furious_Tropos, + Peat_Tropos, + Royal_Tropos, + Air_Tropos, + Mammoth, + Iron_Mammoth, + Snow_Mammoth, + Coal_Mammoth, + Zombie, + Gray_Zombie, + Zombader, + Giant_Zombie, + Enchanted_Tree, + Red_Tree, + Dead_Tree, + Granite_Stone_Eater, + Emerald_Stone_Eater, + Sapphire_Stone_Eater, + Master_of_Rocks, + Walking_Corpse, + Cadaver, + Cadaver_Spider, + Bony_Cadaver, + Giant_Cadaver, + Bat, + Gray_Bat, + Fire_Bat, + Giant_Bat, + Forest_Menad, + Steppe_Menad, + Cave_Menad, + Magical_Menad, + Air_Menad, + Dwarf, + Forest_Dwarf, + Craters_Dwarf, + Air_Dwarf, + Castle_Stone, + Defensive_Stone, + Assassin, + Free_Assassin, + Ghostly_Assassin, + Drakost, + Golden_Drakost, + Ice_Drakost, + Throat, + Swamp_Throat, + Herald_of_Death, + Barrow_Man, + Bandit, + Tower_Spirit, + Black_Dragon, + White_Dragon, + Black_Dragon2, + White_Dragon2, + Black_Dragon3, + White_Dragon3, + Great_Black_Dragon, + Great_White_Dragon, + Wounded_Black_Dragon, + Black_Rider, + White_Rider, + Swimmer, + Fire_Serpent, + Bony_Serpent, + Iron_Serpent, + Underground_Worm, + Mermaid, + Meat_Tentacle, + Iron_Tentacle, + Bony_Dog, + Charon_Hunter, + Fire_Hunter, + Iron_Hunter, + Dead_Hunter, + Fire_Huntress, + Iron_Huntress, + Charon_Huntress, + Dead_Huntress, + Guardian_of_the_Abyss, + Chimera, + Necromancer_Flyer, + Water_Demon, + Fire_Demon, + Air_Demon, + Earth_Demon, + Flying_Demon, + Super_Demon, + Bangville_Scolopendra2, + Stick_Insect2, + Tropos2, + Rusty_Knight2, + Castle_Spirit_Liege, + Castle_Spirit_Fief, + Castle_Spirit_Aris, + Castle_Spirit_Lator, + Castle_Spirit_Eikum_Cas, + Castle_Spirit_Gideon, + Castle_Spirit_Schatelier, + Castle_Spirit_Tuanod, + Castle_Spirit_Peltier, + Castle_Spirit_Care_Royal, + Castle_Spirit_Blessendor, + Castle_Spirit_Ternoval, + Castle_Spirit_Ammaalel, + Castle_Spirit_Kablak, + Castle_Spirit_Devanagari, + Castle_Spirit_Sabulat, + Castle_Spirit_Defensat, + Castle_Spirit_Ayonat, + Castle_Spirit_Triumphaler, + Castle_Spirit_Hangaar, + Castle_Spirit_Dabrad, + Castle_Spirit_Sed, + Castle_Spirit_Lender, + Castle_Spirit_Kellos, + Castle_Spirit_Shibron, + Castle_Spirit_Nimed, + Castle_Spirit_Kanakun, + Castle_Spirit_Elduk, + Castle_Spirit_Yang, + Castle_Spirit_Elek, + Castle_Spirit_Gavot, + Castle_Spirit_Kandur, + Castle_Spirit_Immertel, + Castle_Spirit_Narcissus, + Castle_Spirit_Randen, + Castle_Spirit_Nirgunn, + Castle_Spirit_Gelgivinn, + Castle_Spirit_Il_Suili_Rua, + } + + public static class MonsterTypeMapping + { + public static readonly Dictionary MonsterNameToMonsterTypeMapping = new() + { + [Stick_Insect] = 1000, + [Steppe_Stick_Insect] = 1001, + [Forest_Stick_Insect] = 1002, + [Cave_Stick_Insect] = 1003, + [Doghead] = 1010, + [Steppe_Doghead] = 1011, + [Forest_Doghead] = 1012, + [Cave_Doghead] = 1013, + [Rotten_Knight] = 1020, + [Rusty_Knight] = 1021, + [Restless_Knight] = 1022, + [Golden_Knight] = 1023, + [Giant_Knight] = 1024, + [Air_Knight] = 1025, + [Flier] = 1030, + [Storm_Flier] = 1031, + [Fire_Flier] = 1032, + [Amber_Flier] = 1033, + [Giant_Flier] = 1034, + [Earth_Golem] = 1040, + [Sand_Golem] = 1041, + [Clay_Golem] = 1042, + [Free_Golem] = 1043, + [Giant_Golem] = 1044, + [Bangville_Centipede] = 1050, + [Royal_Centipede] = 1051, + [Skeleton] = 1060, + [Forest_Skeleton] = 1061, + [Umrad_Skeleton] = 1062, + [Air_Skeleton] = 1063, + [Cave_Skeleton] = 1064, + [Lich] = 1065, + [Giant_Skeleton] = 1066, + [Air_Lich] = 1067, + [Assassin2] = 1069, + [Boar] = 1070, + [Mad_Boar] = 1071, + [Dry_Boar] = 1072, + [Air_Boar] = 1073, + [Iron_Boar] = 1074, + [Fire_Boar] = 1075, + [Giant_Boar] = 1076, + [Cyanos] = 1080, + [Dark_Cyanos] = 1081, + [Dry_Cyanos] = 1082, + [Iron_Cyanos] = 1083, + [Air_Cyanos] = 1084, + [Giant_Cyanos] = 1085, + [Wolf] = 1090, + [Steppe_Wolf] = 1091, + [Ice_Wolf] = 1092, + [Dry_Wolf] = 1093, + [Iron_Wolf] = 1094, + [Hell_Wolf] = 1095, + [Spider] = 1100, + [Sulfur_Spider] = 1101, + [Dry_Spider] = 1102, + [Iron_Spider] = 1103, + [Fire_Spider] = 1104, + [Black_Widow] = 1105, + [Giant_Spider] = 1106, + [Air_Spider] = 1107, + [Salamander] = 1110, + [Nomrad_Salamander] = 1111, + [Earth_Salamander] = 1112, + [Iron_Salamander] = 1113, + [Salamandiga] = 1114, + [Red_Scorpion] = 1120, + [Scorpion] = 1121, + [Blue_Scorpion] = 1122, + [Fire_Scorpion] = 1123, + [Typhon] = 1130, + [Smoky_Typhon] = 1131, + [Screeching_Typhon] = 1132, + [Iron_Typhon] = 1133, + [Fire_Typhon] = 1134, + [Giant_Typhon] = 1135, + [Ogre] = 1140, + [Sulfur_Ogre] = 1141, + [Cave_Ogre] = 1142, + [Giant_Ogre] = 1143, + [Niphon] = 1150, + [Green_Dragon] = 1160, + [Blue_Dragon] = 1161, + [Red_Dragon] = 1162, + [Steel_Dragon] = 1163, + [Giant_Dragon] = 1164, + [Cat] = 1170, + [Bull] = 1180, + [Tropos] = 1190, + [Furious_Tropos] = 1191, + [Peat_Tropos] = 1192, + [Royal_Tropos] = 1193, + [Air_Tropos] = 1194, + [Mammoth] = 1200, + [Iron_Mammoth] = 1201, + [Snow_Mammoth] = 1202, + [Coal_Mammoth] = 1203, + [Zombie] = 1210, + [Gray_Zombie] = 1211, + [Zombader] = 1212, + [Giant_Zombie] = 1213, + [Enchanted_Tree] = 1220, + [Red_Tree] = 1221, + [Dead_Tree] = 1222, + [Granite_Stone_Eater] = 1230, + [Emerald_Stone_Eater] = 1231, + [Sapphire_Stone_Eater] = 1232, + [Master_of_Rocks] = 1233, + [Walking_Corpse] = 1240, + [Cadaver] = 1241, + [Cadaver_Spider] = 1242, + [Bony_Cadaver] = 1243, + [Giant_Cadaver] = 1244, + [Bat] = 1250, + [Gray_Bat] = 1251, + [Fire_Bat] = 1252, + [Giant_Bat] = 1253, + [Forest_Menad] = 1260, + [Steppe_Menad] = 1261, + [Cave_Menad] = 1262, + [Magical_Menad] = 1263, + [Air_Menad] = 1264, + [Dwarf] = 1270, + [Forest_Dwarf] = 1271, + [Craters_Dwarf] = 1272, + [Air_Dwarf] = 1273, + [Castle_Stone] = 1280, + [Defensive_Stone] = 1281, + [Assassin] = 1290, + [Free_Assassin] = 1291, + [Ghostly_Assassin] = 1292, + [Drakost] = 1300, + [Golden_Drakost] = 1301, + [Ice_Drakost] = 1302, + [Throat] = 1310, + [Swamp_Throat] = 1311, + [Herald_of_Death] = 1320, + [Barrow_Man] = 1330, + [Bandit] = 1340, + [Tower_Spirit] = 1350, + [Black_Dragon] = 1360, + [White_Dragon] = 1361, + [Black_Dragon2] = 1362, + [White_Dragon2] = 1363, + [Black_Dragon3] = 1364, + [White_Dragon3] = 1365, + [Great_Black_Dragon] = 1366, + [Great_White_Dragon] = 1367, + [Wounded_Black_Dragon] = 1368, + [Black_Rider] = 1370, + [White_Rider] = 1371, + [Swimmer] = 1380, + [Fire_Serpent] = 1400, + [Bony_Serpent] = 1401, + [Iron_Serpent] = 1402, + [Underground_Worm] = 1410, + [Mermaid] = 1420, + [Meat_Tentacle] = 1430, + [Iron_Tentacle] = 1431, + [Bony_Dog] = 1440, + [Charon_Hunter] = 1450, + [Fire_Hunter] = 1451, + [Iron_Hunter] = 1452, + [Dead_Hunter] = 1453, + [Fire_Huntress] = 1460, + [Iron_Huntress] = 1461, + [Charon_Huntress] = 1462, + [Dead_Huntress] = 1463, + [Guardian_of_the_Abyss] = 1470, + [Chimera] = 1480, + [Necromancer_Flyer] = 1850, + [Water_Demon] = 1870, + [Fire_Demon] = 1871, + [Air_Demon] = 1872, + [Earth_Demon] = 1873, + [Flying_Demon] = 1874, + [Super_Demon] = 1875, + [Bangville_Scolopendra2] = 1880, + [Stick_Insect2] = 1881, + [Tropos2] = 1882, + [Rusty_Knight2] = 1883, + [Castle_Spirit_Liege] = 1900, + [Castle_Spirit_Fief] = 1901, + [Castle_Spirit_Aris] = 1902, + [Castle_Spirit_Lator] = 1903, + [Castle_Spirit_Eikum_Cas] = 1904, + [Castle_Spirit_Gideon] = 1905, + [Castle_Spirit_Schatelier] = 1906, + [Castle_Spirit_Tuanod] = 1907, + [Castle_Spirit_Peltier] = 1908, + [Castle_Spirit_Care_Royal] = 1909, + [Castle_Spirit_Blessendor] = 1910, + [Castle_Spirit_Ternoval] = 1911, + [Castle_Spirit_Ammaalel] = 1912, + [Castle_Spirit_Kablak] = 1913, + [Castle_Spirit_Devanagari] = 1914, + [Castle_Spirit_Sabulat] = 1915, + [Castle_Spirit_Defensat] = 1916, + [Castle_Spirit_Ayonat] = 1917, + [Castle_Spirit_Triumphaler] = 1918, + [Castle_Spirit_Hangaar] = 1919, + [Castle_Spirit_Dabrad] = 1920, + [Castle_Spirit_Sed] = 1921, + [Castle_Spirit_Lender] = 1922, + [Castle_Spirit_Kellos] = 1923, + [Castle_Spirit_Shibron] = 1924, + [Castle_Spirit_Nimed] = 1925, + [Castle_Spirit_Kanakun] = 1926, + [Castle_Spirit_Elduk] = 1927, + [Castle_Spirit_Yang] = 1928, + [Castle_Spirit_Elek] = 1929, + [Castle_Spirit_Gavot] = 1930, + [Castle_Spirit_Kandur] = 1931, + [Castle_Spirit_Immertel] = 1932, + [Castle_Spirit_Narcissus] = 1933, + [Castle_Spirit_Randen] = 1934, + [Castle_Spirit_Nirgunn] = 1935, + [Castle_Spirit_Gelgivinn] = 1936, + [Castle_Spirit_Il_Suili_Rua] = 1937, + }; + + public static readonly Dictionary MonsterTypeToMonsterNameMapping = new() + { + [1000] = Stick_Insect, + [1001] = Steppe_Stick_Insect, + [1002] = Forest_Stick_Insect, + [1003] = Cave_Stick_Insect, + [1010] = Doghead, + [1011] = Steppe_Doghead, + [1012] = Forest_Doghead, + [1013] = Cave_Doghead, + [1020] = Rotten_Knight, + [1021] = Rusty_Knight, + [1022] = Restless_Knight, + [1023] = Golden_Knight, + [1024] = Giant_Knight, + [1025] = Air_Knight, + [1030] = Flier, + [1031] = Storm_Flier, + [1032] = Fire_Flier, + [1033] = Amber_Flier, + [1034] = Giant_Flier, + [1040] = Earth_Golem, + [1041] = Sand_Golem, + [1042] = Clay_Golem, + [1043] = Free_Golem, + [1044] = Giant_Golem, + [1050] = Bangville_Centipede, + [1051] = Royal_Centipede, + [1060] = Skeleton, + [1061] = Forest_Skeleton, + [1062] = Umrad_Skeleton, + [1063] = Air_Skeleton, + [1064] = Cave_Skeleton, + [1065] = Lich, + [1066] = Giant_Skeleton, + [1067] = Air_Lich, + [1069] = Assassin2, + [1070] = Boar, + [1071] = Mad_Boar, + [1072] = Dry_Boar, + [1073] = Air_Boar, + [1074] = Iron_Boar, + [1075] = Fire_Boar, + [1076] = Giant_Boar, + [1080] = Cyanos, + [1081] = Dark_Cyanos, + [1082] = Dry_Cyanos, + [1083] = Iron_Cyanos, + [1084] = Air_Cyanos, + [1085] = Giant_Cyanos, + [1090] = Wolf, + [1091] = Steppe_Wolf, + [1092] = Ice_Wolf, + [1093] = Dry_Wolf, + [1094] = Iron_Wolf, + [1095] = Hell_Wolf, + [1100] = Spider, + [1101] = Sulfur_Spider, + [1102] = Dry_Spider, + [1103] = Iron_Spider, + [1104] = Fire_Spider, + [1105] = Black_Widow, + [1106] = Giant_Spider, + [1107] = Air_Spider, + [1110] = Salamander, + [1111] = Nomrad_Salamander, + [1112] = Earth_Salamander, + [1113] = Iron_Salamander, + [1114] = Salamandiga, + [1120] = Red_Scorpion, + [1121] = Scorpion, + [1122] = Blue_Scorpion, + [1123] = Fire_Scorpion, + [1130] = Typhon, + [1131] = Smoky_Typhon, + [1132] = Screeching_Typhon, + [1133] = Iron_Typhon, + [1134] = Fire_Typhon, + [1135] = Giant_Typhon, + [1140] = Ogre, + [1141] = Sulfur_Ogre, + [1142] = Cave_Ogre, + [1143] = Giant_Ogre, + [1150] = Niphon, + [1160] = Green_Dragon, + [1161] = Blue_Dragon, + [1162] = Red_Dragon, + [1163] = Steel_Dragon, + [1164] = Giant_Dragon, + [1170] = Cat, + [1180] = Bull, + [1190] = Tropos, + [1191] = Furious_Tropos, + [1192] = Peat_Tropos, + [1193] = Royal_Tropos, + [1194] = Air_Tropos, + [1200] = Mammoth, + [1201] = Iron_Mammoth, + [1202] = Snow_Mammoth, + [1203] = Coal_Mammoth, + [1210] = Zombie, + [1211] = Gray_Zombie, + [1212] = Zombader, + [1213] = Giant_Zombie, + [1220] = Enchanted_Tree, + [1221] = Red_Tree, + [1222] = Dead_Tree, + [1230] = Granite_Stone_Eater, + [1231] = Emerald_Stone_Eater, + [1232] = Sapphire_Stone_Eater, + [1233] = Master_of_Rocks, + [1240] = Walking_Corpse, + [1241] = Cadaver, + [1242] = Cadaver_Spider, + [1243] = Bony_Cadaver, + [1244] = Giant_Cadaver, + [1250] = Bat, + [1251] = Gray_Bat, + [1252] = Fire_Bat, + [1253] = Giant_Bat, + [1260] = Forest_Menad, + [1261] = Steppe_Menad, + [1262] = Cave_Menad, + [1263] = Magical_Menad, + [1264] = Air_Menad, + [1270] = Dwarf, + [1271] = Forest_Dwarf, + [1272] = Craters_Dwarf, + [1273] = Air_Dwarf, + [1280] = Castle_Stone, + [1281] = Defensive_Stone, + [1290] = Assassin, + [1291] = Free_Assassin, + [1292] = Ghostly_Assassin, + [1300] = Drakost, + [1301] = Golden_Drakost, + [1302] = Ice_Drakost, + [1310] = Throat, + [1311] = Swamp_Throat, + [1320] = Herald_of_Death, + [1330] = Barrow_Man, + [1340] = Bandit, + [1350] = Tower_Spirit, + [1360] = Black_Dragon, + [1361] = White_Dragon, + [1362] = Black_Dragon2, + [1363] = White_Dragon2, + [1364] = Black_Dragon3, + [1365] = White_Dragon3, + [1366] = Great_Black_Dragon, + [1367] = Great_White_Dragon, + [1368] = Wounded_Black_Dragon, + [1370] = Black_Rider, + [1371] = White_Rider, + [1380] = Swimmer, + [1400] = Fire_Serpent, + [1401] = Bony_Serpent, + [1402] = Iron_Serpent, + [1410] = Underground_Worm, + [1420] = Mermaid, + [1430] = Meat_Tentacle, + [1431] = Iron_Tentacle, + [1440] = Bony_Dog, + [1450] = Charon_Hunter, + [1451] = Fire_Hunter, + [1452] = Iron_Hunter, + [1453] = Dead_Hunter, + [1460] = Fire_Huntress, + [1461] = Iron_Huntress, + [1462] = Charon_Huntress, + [1463] = Dead_Huntress, + [1470] = Guardian_of_the_Abyss, + [1480] = Chimera, + [1850] = Necromancer_Flyer, + [1870] = Water_Demon, + [1871] = Fire_Demon, + [1872] = Air_Demon, + [1873] = Earth_Demon, + [1874] = Flying_Demon, + [1875] = Super_Demon, + [1880] = Bangville_Scolopendra2, + [1881] = Stick_Insect2, + [1882] = Tropos2, + [1883] = Rusty_Knight2, + [1900] = Castle_Spirit_Liege, + [1901] = Castle_Spirit_Fief, + [1902] = Castle_Spirit_Aris, + [1903] = Castle_Spirit_Lator, + [1904] = Castle_Spirit_Eikum_Cas, + [1905] = Castle_Spirit_Gideon, + [1906] = Castle_Spirit_Schatelier, + [1907] = Castle_Spirit_Tuanod, + [1908] = Castle_Spirit_Peltier, + [1909] = Castle_Spirit_Care_Royal, + [1910] = Castle_Spirit_Blessendor, + [1911] = Castle_Spirit_Ternoval, + [1912] = Castle_Spirit_Ammaalel, + [1913] = Castle_Spirit_Kablak, + [1914] = Castle_Spirit_Devanagari, + [1915] = Castle_Spirit_Sabulat, + [1916] = Castle_Spirit_Defensat, + [1917] = Castle_Spirit_Ayonat, + [1918] = Castle_Spirit_Triumphaler, + [1919] = Castle_Spirit_Hangaar, + [1920] = Castle_Spirit_Dabrad, + [1921] = Castle_Spirit_Sed, + [1922] = Castle_Spirit_Lender, + [1923] = Castle_Spirit_Kellos, + [1924] = Castle_Spirit_Shibron, + [1925] = Castle_Spirit_Nimed, + [1926] = Castle_Spirit_Kanakun, + [1927] = Castle_Spirit_Elduk, + [1928] = Castle_Spirit_Yang, + [1929] = Castle_Spirit_Elek, + [1930] = Castle_Spirit_Gavot, + [1931] = Castle_Spirit_Kandur, + [1932] = Castle_Spirit_Immertel, + [1933] = Castle_Spirit_Narcissus, + [1934] = Castle_Spirit_Randen, + [1935] = Castle_Spirit_Nirgunn, + [1936] = Castle_Spirit_Gelgivinn, + [1937] = Castle_Spirit_Il_Suili_Rua + }; + } +} diff --git a/Sphere/Sphere.Common/Events/SpawnObject/SpawnObjectEventArgs.cs b/Sphere/Sphere.Common/Events/SpawnObject/SpawnObjectEventArgs.cs new file mode 100644 index 00000000..63f40a6b --- /dev/null +++ b/Sphere/Sphere.Common/Events/SpawnObject/SpawnObjectEventArgs.cs @@ -0,0 +1,12 @@ +using Sphere.Common.Interfaces.GameObjects; + +namespace Sphere.Common.Events.SpawnObject +{ + /// + /// Represents an event args for any spawnable object in a game. + /// + public class SpawnObjectEventArgs : EventArgs + { + public ISpawnable Object { get; set; } + } +} diff --git a/Sphere/Sphere.Common/Extensions/BitStreamExtensions.cs b/Sphere/Sphere.Common/Extensions/BitStreamExtensions.cs new file mode 100644 index 00000000..1f5bf452 --- /dev/null +++ b/Sphere/Sphere.Common/Extensions/BitStreamExtensions.cs @@ -0,0 +1,14 @@ +using BitStreams; +using Sphere.Common.Types; + +namespace Sphere.Common.Extensions +{ + /// + /// Provide BitStream extension to write vBytes into a stream. + /// + public static class BitStreamExtensions + { + public static BitStream WriteVBytes(this BitStream bitStream, vByte value) + => vByte.WriteVBytes(bitStream, value); + } +} diff --git a/Sphere/Sphere.Common/GlobalUsings.cs b/Sphere/Sphere.Common/GlobalUsings.cs new file mode 100644 index 00000000..e0802c8f --- /dev/null +++ b/Sphere/Sphere.Common/GlobalUsings.cs @@ -0,0 +1 @@ +global using static Sphere.Common.Packets.PacketsLibrary; \ No newline at end of file diff --git a/Sphere/Sphere.Common/Helpers/CoordinatesHelper.cs b/Sphere/Sphere.Common/Helpers/CoordinatesHelper.cs index 2972ff53..96f5d7d0 100644 --- a/Sphere/Sphere.Common/Helpers/CoordinatesHelper.cs +++ b/Sphere/Sphere.Common/Helpers/CoordinatesHelper.cs @@ -79,47 +79,20 @@ public static double DecodeServerCoordinate(byte[] input, int shift = 0) return sign * (1 + (double)numToEncode / 0b100000000000000000000000) * baseCoord; } - //public static double DecodeClientCoordinateWithoutShift(byte[] a, bool shouldReverse = true) - //{ - // if (a.Length < 4) - // { - // return 0; - // } - - // if (shouldReverse) - // { - // a = a.Reverse().ToArray(); - // } - // BitOperations. - // var stream = new BitStream(a); - // var fraction = stream.ReadInt64(23); - // var scale = stream.ReadByte(); - // var sign = stream.ReadBit().AsBool() ? -1 : 1; - - // if (scale == 126) - // { - // return 0.0; - // } - - // var baseCoord = Math.Pow(2, scale - 127); - - // return (1 + (float)fraction / 0b100000000000000000000000) * baseCoord * sign; - //} - - public static double DecodeClientCoordinate(byte[] a) + public static float DecodeClientCoordinate(byte[] a) { var x_scale = ((a[4] & 0b11111) << 3) + ((a[3] & 0b11100000) >> 5); if (x_scale == 126) { - return 0.0; + return 0.0f; } var baseCoord = Math.Pow(2, x_scale - 127); var sign = (a[4] & 0b100000) > 0 ? -1 : 1; - return (1 + (float)(((a[3] & 0b11111) << 18) + (a[2] << 10) + (a[1] << 2) + - ((a[0] & 0b11000000) >> 6)) / 0b100000000000000000000000) * baseCoord * sign; + return (float)((1 + (float)(((a[3] & 0b11111) << 18) + (a[2] << 10) + (a[1] << 2) + + ((a[0] & 0b11000000) >> 6)) / 0b100000000000000000000000) * baseCoord * sign); } public static Coordinates GetCoordsFromPingBytes(byte[] rcvBuffer) @@ -127,7 +100,7 @@ public static Coordinates GetCoordsFromPingBytes(byte[] rcvBuffer) var x = DecodeClientCoordinate(rcvBuffer.AsSpan(21, 5).ToArray()); var y = -DecodeClientCoordinate(rcvBuffer.AsSpan(25, 5).ToArray()); var z = DecodeClientCoordinate(rcvBuffer.AsSpan(29, 5).ToArray()); - var turn = DecodeClientCoordinate(rcvBuffer.AsSpan(33, 5).ToArray()); + var turn = (int)DecodeClientCoordinate(rcvBuffer.AsSpan(33, 5).ToArray()); return new Coordinates(x, y, z, turn); } diff --git a/Sphere/Sphere.Common/Helpers/EncodingHelper.cs b/Sphere/Sphere.Common/Helpers/EncodingHelper.cs new file mode 100644 index 00000000..885ae0b2 --- /dev/null +++ b/Sphere/Sphere.Common/Helpers/EncodingHelper.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace Sphere.Common.Helpers +{ + public static class EncodingHelper + { + public static Encoding Win1251 => Encoding.GetEncoding(1251); + } +} diff --git a/Sphere/Sphere.Common/Helpers/Extensions/LoggerExtensions.cs b/Sphere/Sphere.Common/Helpers/Extensions/LoggerExtensions.cs index 8cb65164..5c620ec8 100644 --- a/Sphere/Sphere.Common/Helpers/Extensions/LoggerExtensions.cs +++ b/Sphere/Sphere.Common/Helpers/Extensions/LoggerExtensions.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.Logging; -using Sphere.Common.Interfaces.Packets; namespace Sphere.Common.Helpers.Extensions { public static class LoggerExtensions { - public static void PacketReceived(this ILogger logger, PacketBase packet, ushort clientId, LogLevel logLevel = LogLevel.Trace) + public static void PacketReceived(this ILogger logger, byte[] packet, ushort clientId, LogLevel logLevel = LogLevel.Trace) { - logger.Log(logLevel, "Received packet from client [{clientId}], payload [{payload}]", clientId, packet); + logger.Log(logLevel, "Received packet from client [{clientId}], payload [{payload}]", clientId, BitConverter.ToString(packet)); } public static void PacketSent(this ILogger logger, byte[] packet, ushort clientId, LogLevel logLevel = LogLevel.Trace) diff --git a/Sphere/Sphere.Common/Interfaces/GameObjects/IGameObject.cs b/Sphere/Sphere.Common/Interfaces/GameObjects/IGameObject.cs new file mode 100644 index 00000000..5aa63f2f --- /dev/null +++ b/Sphere/Sphere.Common/Interfaces/GameObjects/IGameObject.cs @@ -0,0 +1,10 @@ +namespace Sphere.Common.Interfaces.GameObjects +{ + /// + /// Basic interface for any in-game object. + /// + public interface IGameObject + { + ushort EntityId { get; set; } + } +} diff --git a/Sphere/Sphere.Common/Interfaces/GameObjects/ISpawnable.cs b/Sphere/Sphere.Common/Interfaces/GameObjects/ISpawnable.cs new file mode 100644 index 00000000..c88d0f56 --- /dev/null +++ b/Sphere/Sphere.Common/Interfaces/GameObjects/ISpawnable.cs @@ -0,0 +1,15 @@ +using Sphere.Common.Interfaces.Packets; +using Sphere.Common.Models; + +namespace Sphere.Common.Interfaces.GameObjects +{ + /// + /// Basic interface for any spawnable object in a game. + /// + public interface ISpawnable : IGameObject + { + Coordinates Coordinates { get; } + + IServerPacketStream ToServerPacket(); + } +} diff --git a/Sphere/Sphere.Common/Interfaces/Nodes/IServer.cs b/Sphere/Sphere.Common/Interfaces/Nodes/IServer.cs index eb4c3c99..b5db5c6b 100644 --- a/Sphere/Sphere.Common/Interfaces/Nodes/IServer.cs +++ b/Sphere/Sphere.Common/Interfaces/Nodes/IServer.cs @@ -1,9 +1,35 @@ -namespace Sphere.Common.Interfaces +using Sphere.Common.Events.SpawnObject; +using Sphere.Common.Models; + +namespace Sphere.Common.Interfaces { + /// + /// An interface of server component of the game. + /// public interface IServer { + /// + /// Starts server listener. + /// + /// Task StartAsync(); + /// + /// Stops server listener. + /// + /// Task StopAsync(); + + /// + /// Spawn mob command. Most likely will be generalized in future. + /// + /// + /// + void SpawnMob(int clientId, SpawnMobModel model); + + /// + /// An event raised upon spawning an object so to notify clients about new object. + /// + event EventHandler SpawnEvent; } } diff --git a/Sphere/Sphere.Common/Interfaces/Packets/IServerPacket.cs b/Sphere/Sphere.Common/Interfaces/Packets/IServerPacket.cs new file mode 100644 index 00000000..37951c37 --- /dev/null +++ b/Sphere/Sphere.Common/Interfaces/Packets/IServerPacket.cs @@ -0,0 +1,27 @@ +using Sphere.Common.Models; +using System.Numerics; + +namespace Sphere.Common.Interfaces.Packets +{ + /// + /// Interface to get data behind server packet. + /// + public interface IServerPacketStream + { + byte[] GetBytes(); + + Stream GetStream(); + } + + /// + /// Server packet builder interface. + /// + public interface IServerPacket + { + IServerPacket AddValue(string part, T value) where T : IBinaryInteger; + + IServerPacket AddValue(Coordinates coordinates); + + IServerPacketStream Build(); + } +} diff --git a/Sphere/Sphere.Common/Interfaces/Types/IBitWritable.cs b/Sphere/Sphere.Common/Interfaces/Types/IBitWritable.cs new file mode 100644 index 00000000..9b32b283 --- /dev/null +++ b/Sphere/Sphere.Common/Interfaces/Types/IBitWritable.cs @@ -0,0 +1,13 @@ +using BitStreams; + +namespace Sphere.Common.Interfaces.Types +{ + /// + /// Defines a bit writable object such as vByte. + /// + /// + public interface IBitWritable where TSelf : IBitWritable + { + abstract static BitStream WriteVBytes(BitStream stream, TSelf value); + } +} diff --git a/Sphere/Sphere.Common/Interfaces/Utils/IPacketDefinitionParser.cs b/Sphere/Sphere.Common/Interfaces/Utils/IPacketDefinitionParser.cs new file mode 100644 index 00000000..b88495eb --- /dev/null +++ b/Sphere/Sphere.Common/Interfaces/Utils/IPacketDefinitionParser.cs @@ -0,0 +1,9 @@ +using Sphere.Common.Packets; + +namespace Sphere.Common.Interfaces.Utils +{ + public interface IPacketDefinitionParser + { + Dictionary Load(); + } +} diff --git a/Sphere/Sphere.Common/Models/Coordinates.cs b/Sphere/Sphere.Common/Models/Coordinates.cs index 5cba9479..6782b1a9 100644 --- a/Sphere/Sphere.Common/Models/Coordinates.cs +++ b/Sphere/Sphere.Common/Models/Coordinates.cs @@ -1,8 +1,25 @@ -namespace Sphere.Common.Models +using BitStreams; +using Sphere.Common.Helpers; +using Sphere.Common.Interfaces.Types; + +namespace Sphere.Common.Models { - public struct Coordinates + public struct Coordinates : IBitWritable { - public Coordinates(double x, double y, double z, double angle) + public float X { get; set; } + + public float Y { get; set; } + + public float Z { get; set; } + + public int Angle { get; set; } + + public Coordinates() + { + + } + + public Coordinates(float x, float y, float z, int angle) { X = x; Y = y; @@ -10,12 +27,59 @@ public Coordinates(double x, double y, double z, double angle) Angle = angle; } - public double X { get; set; } + public Coordinates(double x, double y, double z, int angle) + { + X = (float)x; + Y = (float)y; + Z = (float)z; + Angle = angle; + } + + public float Distance(Coordinates to) + { + return (to - this).Length(); + } + + public static Coordinates operator -(Coordinates left, Coordinates right) + { + left.X -= right.X; + left.Y -= right.Y; + left.Z -= right.Z; + return left; + } + + public static Coordinates operator +(Coordinates left, Coordinates right) + { + left.X += right.X; + left.Y += right.Y; + left.Z += right.Z; + return left; + } + + public readonly float Length() + { + var num = X * X; + var num2 = Y * Y; + var num3 = Z * Z; + return MathF.Sqrt(num + num2 + num3); + } - public double Y { get; set; } + public readonly byte[] GetBytes() + { + Span bytes = [ + ..CoordinatesHelper.EncodeServerCoordinate(X), + ..CoordinatesHelper.EncodeServerCoordinate(-Y), + ..CoordinatesHelper.EncodeServerCoordinate(Z), + BitConverter.GetBytes(Angle).First(), + ]; - public double Z { get; set; } + return bytes.ToArray(); + } - public double Angle { get; set; } + public static BitStream WriteVBytes(BitStream stream, Coordinates value) + { + stream.WriteBytes(value.GetBytes()); + return stream; + } } } diff --git a/Sphere/Sphere.Common/Models/GameObject.cs b/Sphere/Sphere.Common/Models/GameObject.cs new file mode 100644 index 00000000..33744b98 --- /dev/null +++ b/Sphere/Sphere.Common/Models/GameObject.cs @@ -0,0 +1,12 @@ +using Sphere.Common.Interfaces.GameObjects; + +namespace Sphere.Common.Models +{ + /// + /// A base game object class. + /// + public class GameObject : IGameObject + { + public ushort EntityId { get; set; } + } +} diff --git a/Sphere/Sphere.Common/Models/SpawnMobModel.cs b/Sphere/Sphere.Common/Models/SpawnMobModel.cs new file mode 100644 index 00000000..5df4a519 --- /dev/null +++ b/Sphere/Sphere.Common/Models/SpawnMobModel.cs @@ -0,0 +1,39 @@ +using Sphere.Common.Enums; +using Sphere.Common.Interfaces.GameObjects; +using Sphere.Common.Interfaces.Packets; +using Sphere.Common.Packets.Server; + +namespace Sphere.Common.Models +{ + /// + /// A model that contains necessary data to spawn a mob and get packet to be sent to the clients. + /// + public class SpawnMobModel : GameObject, ISpawnable + { + private const ushort HPSize = 8; + public Coordinates Coordinates { get; set; } + + public int CurrentHP { get; set; } + + public int MaxHP { get; set; } + + public MonsterType Type { get; set; } + + public int Level { get; set; } + + public IServerPacketStream ToServerPacket() + { + return new SpawnMobPacket() + .AddValue("entity_id", this.EntityId) + .AddValue("entity_type", (int)GameObjectTypeEnum.Monster) + .AddValue("action_type", (int)EntityActionTypeEnum.FULL_SPAWN) + .AddValue(Coordinates) + .AddValue("hp_size_t", HPSize) + .AddValue("current_hp", CurrentHP) + .AddValue("max_hp", MaxHP) + .AddValue("mob_type", MonsterTypeMapping.MonsterNameToMonsterTypeMapping[Type]) + .AddValue("level", Level) + .Build(); + } + } +} diff --git a/Sphere/Sphere.Common/Packets/PacketDefinition.cs b/Sphere/Sphere.Common/Packets/PacketDefinition.cs new file mode 100644 index 00000000..b3e870c6 --- /dev/null +++ b/Sphere/Sphere.Common/Packets/PacketDefinition.cs @@ -0,0 +1,90 @@ +using BitStreams; +using Sphere.Common.Extensions; +using Sphere.Common.Types; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Sphere.Common.Packets +{ + /// + /// Describes a sphere packet definition scrapped from the client. + /// + public class PacketDefinition : ICloneable + { + /// + /// The name of the packet. + /// + public string Name { get; set; } + + /// + /// An instance of packet parts initialize during startup. + /// + private Dictionary PacketParts { get; init; } + + public PacketDefinition(string name, Dictionary packetParts) + { + Name = name; + PacketParts = packetParts; + } + + /// + /// Replaces packet part's value with provided integer value (short/int/long and unsigned versions) + /// + /// Represents an integer type that implements IBinaryInteger interface + /// Part name + /// Value that packet part will be changed to + /// + public PacketDefinition ReplacePart(string name, T value) where T : IBinaryInteger + { + var toReplace = GetPacketPart(name); + + /// magic here. Copied from .net implementation of BitConverter + var byteCount = value.GetByteCount(); + byte[] bytes = new byte[byteCount]; + Unsafe.As(ref bytes[0]) = value; + + PacketParts[name] = new vByte(bytes, toReplace.Length); + + return this; + } + + /// + /// Replaces packet part with provided raw bytes array + /// + /// Packet part name + /// Raw bytes value + /// + public PacketDefinition ReplacePart(string name, byte[] value) + { + var toReplace = GetPacketPart(name); + + PacketParts[name] = new vByte(value); + + return this; + } + + public object Clone() => new PacketDefinition(this.Name, new Dictionary(this.PacketParts)); + + /// + /// Appends packet parts into provided stream + /// + /// A BitStream to append data to + internal void AppendToStream(BitStream stream) + { + foreach(var part in PacketParts) + { + stream.WriteVBytes(part.Value); + } + } + + private vByte GetPacketPart(string name) + { + if (!PacketParts.TryGetValue(name, out var toReplace)) + { + throw new ArgumentException($"Unknown packet part {name}"); + } + + return toReplace; + } + } +} diff --git a/Sphere/Sphere.Common/Packets/PacketsLibrary.cs b/Sphere/Sphere.Common/Packets/PacketsLibrary.cs new file mode 100644 index 00000000..10c59b36 --- /dev/null +++ b/Sphere/Sphere.Common/Packets/PacketsLibrary.cs @@ -0,0 +1,33 @@ +using System.Collections.ObjectModel; + +namespace Sphere.Common.Packets +{ + /// + /// Statically holds an original library of Packet definitions. + /// + public static class PacketsLibrary + { + private static ReadOnlyDictionary Library { get; set; } + + public static void Create(ReadOnlyDictionary library) + { + Library = library; + } + + /// + /// Create a clone of original packet definition. + /// + /// + /// + /// + public static PacketDefinition GetPacketDefinition(string name) + { + if (!Library.TryGetValue(name, out var packetDefinition)) + { + throw new ArgumentException($"Unknown packet {name}"); + } + + return (PacketDefinition)packetDefinition.Clone(); + } + } +} diff --git a/Sphere/Sphere.Common/Packets/Server/ServerPacketBase.cs b/Sphere/Sphere.Common/Packets/Server/ServerPacketBase.cs new file mode 100644 index 00000000..f0bc2b4a --- /dev/null +++ b/Sphere/Sphere.Common/Packets/Server/ServerPacketBase.cs @@ -0,0 +1,112 @@ +using BitStreams; +using Sphere.Common.Extensions; +using Sphere.Common.Helpers.Extensions; +using Sphere.Common.Interfaces.Packets; +using Sphere.Common.Models; +using Sphere.Common.Types; +using System.Numerics; + +namespace Sphere.Common.Packets.Server +{ + public class ServerPacketBase : IServerPacket, IServerPacketStream + { + private static readonly ushort PacketValidationCodeOK = 0x2C01; + + protected virtual string PacketName { get; } = "empty"; + + protected virtual ushort Size => 4; // empty packet + + protected vByte EmptyPacket = new vByte([0x04, 0x00, 0xF4, 0x01]); + + protected virtual vByte Padding => new vByte(0, 24); + + /// + /// Loads new packet definition once per instantiated ServerPacker. + /// + private Lazy PacketDefinition => new Lazy(() => GetPacketDefinition(PacketName)); + + /// + /// Builds a basic set of vBytes that contains packet size and validation indicator. + /// + /// + protected vByte GetBaseBytes() + { + var size = (ushort)Size; + return new vByte([ + BitHelper.MinorByte(size), + BitHelper.MajorByte(size), + BitHelper.MajorByte(PacketValidationCodeOK), + BitHelper.MinorByte(PacketValidationCodeOK), + ]); + } + + /// + /// Builds bit stream from filled packet definition and provides it as byte array. + /// + /// + public byte[] GetBytes() + { + return BuildBitStream().GetStreamData(); + } + + /// + /// Builds bit stream from filled packet definition and provides it as stream. + /// + /// + public Stream GetStream() + { + return BuildBitStream().GetStream(); + } + + /// + /// Adds (replaces) value in packet definition + /// + /// An integer type that implements IBinaryInteger interface + /// The name of packet part + /// Value to add into packet + /// + public IServerPacket AddValue(string part, T value) where T : IBinaryInteger + { + PacketDefinition.Value.ReplacePart(part, value); + return this; + } + + /// + /// Adds coordinates into packet replacing X,Y,Z and angle parts in a definition. + /// + /// Coordinates value + /// + public IServerPacket AddValue(Coordinates coordinates) + { + var bytes = coordinates.GetBytes(); + + PacketDefinition.Value.ReplacePart("x", bytes[0..4]); + PacketDefinition.Value.ReplacePart("y", bytes[4..8]); + PacketDefinition.Value.ReplacePart("z", bytes[8..12]); + PacketDefinition.Value.ReplacePart("angle", bytes[12..13]); + + return this; + } + + /// + /// Finishes adding values into packet. + /// For now no specific purpose, maybe will be used for logging or some additional logic. + /// + /// + public IServerPacketStream Build() => this; + + private BitStream BuildBitStream() + { + var buffer = new byte[Size]; + + var bitStream = new BitStream(buffer); + + bitStream.WriteVBytes(GetBaseBytes()); + bitStream.WriteVBytes(this.Padding); + + PacketDefinition.Value.AppendToStream(bitStream); + + return bitStream; + } + } +} diff --git a/Sphere/Sphere.Common/Packets/Server/SpawnMobPacket.cs b/Sphere/Sphere.Common/Packets/Server/SpawnMobPacket.cs new file mode 100644 index 00000000..76758efd --- /dev/null +++ b/Sphere/Sphere.Common/Packets/Server/SpawnMobPacket.cs @@ -0,0 +1,11 @@ +namespace Sphere.Common.Packets.Server +{ + public class SpawnMobPacket : ServerPacketBase + { + private static readonly ushort _size = 30; + + protected override string PacketName => "monster_level_1"; + + protected override ushort Size => (ushort)(base.Size + _size + (this.Padding.Length / 8)); + } +} diff --git a/Sphere/Sphere.Common/Types/vByte.cs b/Sphere/Sphere.Common/Types/vByte.cs new file mode 100644 index 00000000..a3661f03 --- /dev/null +++ b/Sphere/Sphere.Common/Types/vByte.cs @@ -0,0 +1,66 @@ +using BitStreams; +using Sphere.Common.Helpers; +using Sphere.Common.Interfaces.Types; +using System.Numerics; + +namespace Sphere.Common.Types +{ + /// + /// Represents variable length byte in binary format. + /// E.g. if length is set to 5 and the value is Int16 in binary format it will be cut to 01000 + /// + public struct vByte : IBitWritable, IEqualityOperators + { + public readonly ushort Length = 32; + internal readonly byte[] BaseValue; + + internal vByte(int baseValue, ushort length) + { + this.Length = length; + this.BaseValue = BitConverter.GetBytes(baseValue); + } + + public vByte(byte[] bytes) + { + this.Length = (ushort)(bytes.Length * 8); + this.BaseValue = bytes; + } + + public vByte(byte[] bytes, ushort length) + { + this.Length = length; + this.BaseValue = bytes; + } + + public vByte(string str) + { + this.BaseValue = EncodingHelper.Win1251.GetBytes(str); + this.Length = (ushort)(this.BaseValue.Length * 8); + } + + public static BitStream WriteVBytes(BitStream stream, vByte value) + { + stream.WriteBytes(value.BaseValue, value.Length); + return stream; + } + + public static bool operator ==(vByte left, vByte right) => left.BaseValue == right.BaseValue && left.Length == right.Length; + public static bool operator !=(vByte left, vByte right) => left.BaseValue != right.BaseValue || left.Length != right.Length; + + public static implicit operator long(vByte value) => BitConverter.ToInt64(value.BaseValue); + public static implicit operator int(vByte value) => BitConverter.ToInt32(value.BaseValue); + public static implicit operator short(vByte value) => BitConverter.ToInt16(value.BaseValue); + + public override bool Equals(object obj) + { + return this == (vByte)obj; + } + + public override int GetHashCode() + { + return this.BaseValue.GetHashCode() ^ this.Length.GetHashCode() ^ 12983198; + } + } + + +} diff --git a/Sphere/Sphere.Godot/Configuration/DependencyInjection.cs b/Sphere/Sphere.Godot/Configuration/DependencyInjection.cs index 6340a4cf..a272849d 100644 --- a/Sphere/Sphere.Godot/Configuration/DependencyInjection.cs +++ b/Sphere/Sphere.Godot/Configuration/DependencyInjection.cs @@ -10,13 +10,15 @@ using Sphere.Common.Interfaces.Readers; using Sphere.Common.Interfaces.Services; using Sphere.Common.Interfaces.Tcp; -using Sphere.Common.Packets; +using Sphere.Common.Interfaces.Utils; +using Sphere.Common.Packets.Client; using Sphere.Godot.Configuration.Options; using Sphere.Repository.Configuration; using Sphere.Services.Misc; using Sphere.Services.Providers; using Sphere.Services.Readers; using Sphere.Services.Services.Handlers; +using Sphere.Services.Services.Utils; using System; namespace Sphere.Godot.Configuration @@ -45,9 +47,10 @@ public static IServiceCollection RegisterServices(this IServiceCollection servic services.AddSingleton(); services.AddSingleton(); services.AddSingleton, GuidIdentifierProvider>(); + services.AddSingleton(); services.AddScoped(); - services.AddScoped(); + services.AddScoped((provider) => new ClientAccessor { Server = provider.GetRequiredService() }); services.AddScoped(); services.AddScoped(); services.AddScoped, LoginPacketHandler>(); diff --git a/Sphere/Sphere.Godot/Configuration/GameConfig/MobData.txt b/Sphere/Sphere.Godot/Configuration/GameConfig/MobData.txt new file mode 100644 index 00000000..8f321660 --- /dev/null +++ b/Sphere/Sphere.Godot/Configuration/GameConfig/MobData.txt @@ -0,0 +1,50 @@ +3659 Monster FULL_SPAWN 495.16400146484375 159.89999389648438 -1220.2989501953125 159 85 85 1020 1 +365D Monster FULL_SPAWN 491.2080078125 159.9013214111328 -1222.069091796875 149 85 85 1020 1 +365E Monster FULL_SPAWN 485.64691162109375 159.89390563964844 -1206.2158203125 18 80 80 1010 1 +365F Monster FULL_SPAWN 494.2920227050781 159.91714477539062 -1210.663818359375 214 80 80 1010 1 +3660 Monster FULL_SPAWN 497.01361083984375 159.89938354492188 -1219.305908203125 160 85 85 1020 1 +3661 Monster FULL_SPAWN 482.2861328125 159.91342163085938 -1218.67138671875 91 128 128 1000 2 +3662 Monster FULL_SPAWN 494.7950439453125 159.89585876464844 -1209.9921875 214 131 131 1060 2 +3664 Monster FULL_SPAWN 494.73828125 159.89488220214844 -1205.4058837890625 233 85 85 1020 1 +368C Monster FULL_SPAWN 495.509033203125 159.9168243408203 -1209.7840576171875 60 187 187 1020 3 +368D Monster FULL_SPAWN 504.3318176269531 159.91371154785156 -1202.6455078125 229 176 176 1010 3 +369A Monster FULL_SPAWN 510.6310729980469 160.2121124267578 -1220.7918701171875 151 128 128 1000 2 +6261 Monster FULL_SPAWN 475.6944274902344 154.49668884277344 -1157.4595947265625 31 178 178 1001 3 +6A4C Monster FULL_SPAWN 427.4857482910156 146.59634399414062 -1153.390869140625 1 229 229 1011 4 +8855 Monster FULL_SPAWN 497.2626953125 158.79400634765625 -1170.2049560546875 3 131 131 1060 2 +9434 Monster FULL_SPAWN 255.4210968017578 159.3770751953125 -1327.312255859375 37 254 254 1071 4 +9507 Monster FULL_SPAWN 234.18850708007812 159.01614379882812 -1337.2235107421875 0 254 254 1071 4 +A034 Monster FULL_SPAWN 397.7215881347656 159.9121856689453 -1437.6754150390625 196 128 128 1010 2 +A7CE Monster FULL_SPAWN 282.48583984375 160.00392150878906 -1237.40771484375 178 80 80 1010 1 +A7D0 Monster FULL_SPAWN 271.2777404785156 159.953369140625 -1224.630615234375 225 176 176 1010 3 +A7D4 Monster FULL_SPAWN 266.5135803222656 159.37472534179688 -1228.906494140625 208 229 229 1060 4 +A7D5 Monster FULL_SPAWN 257.59393310546875 158.72830200195312 -1229.2625732421875 28 176 176 1010 3 +A7D9 Monster FULL_SPAWN 259.37615966796875 158.70323181152344 -1262.8837890625 189 224 224 1010 4 +A7DA Monster FULL_SPAWN 252.70986938476562 158.05441284179688 -1263.0618896484375 253 224 224 1010 4 +A7E8 Monster FULL_SPAWN 301.9194030761719 159.9119873046875 -1216.25244140625 207 243 243 1021 4 +A801 Monster FULL_SPAWN 294.9763488769531 159.9119873046875 -1220.587646484375 99 238 238 1020 4 +A806 Monster FULL_SPAWN 288.3514404296875 159.9170684814453 -1236.4207763671875 182 80 80 1000 1 +AA29 Monster FULL_SPAWN 231.00048828125 159.33477783203125 -1248.8988037109375 74 137 137 1070 2 +BCC7 Monster FULL_SPAWN 252.13320922851562 159.37245178222656 -1381.2159423828125 59 86 86 1070 1 +C0E4 Monster FULL_SPAWN 318.5399475097656 159.8998260498047 -1400.3968505859375 194 178 178 1001 3 +C0F7 Monster FULL_SPAWN 298.39306640625 159.9128875732422 -1412.685302734375 11 187 187 1020 3 +C106 Monster FULL_SPAWN 226.61981201171875 159.9185791015625 -1401.4482421875 0 299 299 1120 5 +CFBA Monster FULL_SPAWN 318.1248474121094 159.77853393554688 -1442.39453125 19 131 131 1060 2 +DB4A Monster FULL_SPAWN 377.81634521484375 159.91412353515625 -1400.070556640625 230 137 137 1070 2 +E0DE Monster FULL_SPAWN 492.14630126953125 159.90931701660156 -1391.1295166015625 234 131 131 1060 2 +E187 Monster FULL_SPAWN 397.5315246582031 159.8993682861328 -1443.6041259765625 4 80 80 1010 1 +E188 Monster FULL_SPAWN 401.60174560546875 159.9121856689453 -1444.559326171875 0 224 224 1010 4 +E270 Monster FULL_SPAWN 448.90753173828125 159.9317169189453 -1414.1998291015625 85 80 80 1010 1 +E66D Monster FULL_SPAWN 495.2185974121094 159.90931701660156 -1397.49951171875 140 128 128 1000 2 +E68A Monster FULL_SPAWN 311.7943420410156 159.90135192871094 -1407.1776123046875 143 243 243 1021 4 +E6F4 Monster FULL_SPAWN 510.0572814941406 159.6702880859375 -1367.053466796875 35 176 176 1010 3 +E909 Monster FULL_SPAWN 537.7821044921875 159.00079345703125 -1336.082275390625 194 85 85 1020 1 +E9A3 Monster FULL_SPAWN 516.0077514648438 159.5327606201172 -1351.385009765625 89 187 187 1020 3 +E9B1 Monster FULL_SPAWN 509.0428771972656 159.65414428710938 -1353.190673828125 40 187 187 1020 3 +EA8B Monster FULL_SPAWN 508.650390625 159.91500854492188 -1198.6109619140625 104 128 128 1000 2 +EC4E Monster FULL_SPAWN 493.8194885253906 159.2253875732422 -1181.0201416015625 178 178 178 1001 3 +F14B Monster FULL_SPAWN 252.58380126953125 158.7317352294922 -1336.244384765625 18 80 80 1010 1 +F14F Monster FULL_SPAWN 255.93936157226562 159.0472412109375 -1334.9410400390625 231 85 85 1020 1 +F159 Monster FULL_SPAWN 255.27218627929688 159.44113159179688 -1339.832275390625 184 85 85 1020 1 +FD22 Monster FULL_SPAWN 452.6856994628906 156.59080505371094 -1141.26220703125 166 82 82 1060 1 +FD46 Monster FULL_SPAWN 351.9879150390625 159.39401245117188 -1383.9324951171875 96 32 32 1170 1 diff --git a/Sphere/Sphere.Godot/Nodes/ClientNode.cs b/Sphere/Sphere.Godot/Nodes/ClientNode.cs index 9b1f66c9..a36e8f69 100644 --- a/Sphere/Sphere.Godot/Nodes/ClientNode.cs +++ b/Sphere/Sphere.Godot/Nodes/ClientNode.cs @@ -1,6 +1,7 @@ using Godot; using Microsoft.Extensions.Logging; using Sphere.Common.Enums; +using Sphere.Common.Events.SpawnObject; using Sphere.Common.Helpers.Extensions; using Sphere.Common.Interfaces.Nodes; using Sphere.Common.Interfaces.Packets; @@ -16,7 +17,7 @@ namespace Sphere.Godot.Nodes { public class Client : IClient - { + { private readonly ILogger _logger; private readonly ILocalIdProvider _localIdProvider; private static readonly PackedScene _clientScene; @@ -98,8 +99,6 @@ private async ValueTask ReceivePacket() { var packet = _packetReader.Current; - _logger.PacketReceived(packet, _tcpClientAccessor.ClientId); - return packet; } @@ -112,6 +111,34 @@ private async Task SendPacket(byte[] rcvBuffer) _logger.PacketSent(rcvBuffer, _tcpClientAccessor.ClientId); } + + public void ClientConnected() + { + this._clientState = this._tcpClientAccessor.ClientState = ClientState.INGAME_DEFAULT; + this._tcpClientAccessor.Server.SpawnEvent += Server_SpawnEvent; + + } + + private void Server_SpawnEvent(object sender, SpawnObjectEventArgs e) + { + try + { + var coordinates = e.Object.Coordinates; + + var playerCoords = this._tcpClientAccessor.Character.Coordinates; + + if (coordinates.Distance(playerCoords) >= 100) + return; + + var packet = e.Object.ToServerPacket(); + + this.SendPacket(packet.GetBytes()).GetAwaiter().GetResult(); + } + catch (Exception) + { + // _logger.LogError(ex, "Unhandled exception."); + } + } } partial class ClientNode : Node diff --git a/Sphere/Sphere.Godot/Nodes/MainNode.cs b/Sphere/Sphere.Godot/Nodes/MainNode.cs index 6c963263..3ffd1a4f 100644 --- a/Sphere/Sphere.Godot/Nodes/MainNode.cs +++ b/Sphere/Sphere.Godot/Nodes/MainNode.cs @@ -3,15 +3,20 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Sphere.Common.Events.SpawnObject; using Sphere.Common.Interfaces; using Sphere.Common.Interfaces.Nodes; using Sphere.Common.Interfaces.Providers; using Sphere.Common.Interfaces.Tcp; +using Sphere.Common.Interfaces.Utils; +using Sphere.Common.Models; +using Sphere.Common.Packets; using Sphere.Godot.Configuration; using Sphere.Godot.Configuration.Options; using Sphere.Repository.Configuration; using Sphere.Services.Services.Tcp; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; @@ -56,6 +61,14 @@ public override void _Ready() Console.WriteLine("End service registration..."); + Console.WriteLine("Start loading packet definitions..."); + + var packetsDefinitionParser = serviceProvider.GetRequiredService(); + var packets = packetsDefinitionParser.Load(); + PacketsLibrary.Create(packets.AsReadOnly()); + + Console.WriteLine("End loading packet definitions..."); + _logger = serviceProvider.GetRequiredService>(); var server = serviceProvider.GetRequiredService(); @@ -81,6 +94,17 @@ public override void _Ready() server.StopAsync(); running = false; break; + case "spawn_mob": + Console.WriteLine("Enter client id: "); + var clientId = Convert.ToInt16(Console.ReadLine()); + server.SpawnMob(clientId, new SpawnMobModel + { + CurrentHP = 100, + Level = 1, + MaxHP = 100, + Type = Common.Enums.MonsterType.Assassin + }); + break; } } }); @@ -99,10 +123,13 @@ public partial class Server : Node, IServer private readonly IOptions _serverConfiguration; private readonly IServiceProvider _serviceProvider; private readonly ILocalIdProvider _localIdProvider; + private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); // private PackedScene _clientScene; private Dictionary _portClientMap = new Dictionary(); + public event EventHandler SpawnEvent; + public Server(ILogger logger, IOptions options, IServiceProvider serviceProvider, ILocalIdProvider localIdProvider) { _listener = new TcpListener(IPAddress.Any, options.Value.Port); @@ -139,11 +166,20 @@ private async Task HandleConnection(TcpClient tcpClient) // setup current "context" accessor which grants access to tcpClient and clientId in all subsequent services in that scope var tcpClientAccessor = scope.ServiceProvider.GetRequiredService(); - tcpClientAccessor.Client = new SphereTcpClient(tcpClient); + var tcpLogger = scope.ServiceProvider.GetRequiredService>(); + tcpClientAccessor.ClientId = _localIdProvider.GetIdentifier(); + tcpClientAccessor.Client = new SphereTcpClient(tcpLogger, tcpClient, tcpClientAccessor); tcpClientAccessor.ClientState = Common.Enums.ClientState.I_AM_BREAD; - + var client = scope.ServiceProvider.GetRequiredService(); + tcpClientAccessor.GameClient = client; + + if (!this._clients.TryAdd(tcpClientAccessor.ClientId, tcpClientAccessor)) + { + tcpClientAccessor.Client.Close(); + return; + } this.AddChild(client.Node); @@ -177,5 +213,21 @@ public Task StopAsync() return Task.CompletedTask; } + + public void SpawnMob(int clientId, SpawnMobModel model) + { + if (!this._clients.TryGetValue(clientId, out var clientAccessor)) + { + return; + } + + model.EntityId = _localIdProvider.GetIdentifier(); + model.Coordinates = clientAccessor.Character.Coordinates; + + this.SpawnEvent?.Invoke(this, new SpawnObjectEventArgs + { + Object = model, + }); + } } } diff --git a/Sphere/Sphere.Godot/Sphere.Godot.csproj b/Sphere/Sphere.Godot/Sphere.Godot.csproj index 6bab09eb..0e4a9691 100644 --- a/Sphere/Sphere.Godot/Sphere.Godot.csproj +++ b/Sphere/Sphere.Godot/Sphere.Godot.csproj @@ -6,6 +6,11 @@ true Exe + + + PreserveNewest + + diff --git a/Sphere/Sphere.Godot/appsettings.json b/Sphere/Sphere.Godot/appsettings.json index 02a09dd3..d3586bbd 100644 --- a/Sphere/Sphere.Godot/appsettings.json +++ b/Sphere/Sphere.Godot/appsettings.json @@ -3,7 +3,7 @@ "LogLevel": { "Default": "Information", "Microsoft": "Information", - } + }, }, "Sphere": { "Server": { diff --git a/Sphere/Sphere.Godot/nlog.config b/Sphere/Sphere.Godot/nlog.config index 4e089ecb..b5d08fdd 100644 --- a/Sphere/Sphere.Godot/nlog.config +++ b/Sphere/Sphere.Godot/nlog.config @@ -17,6 +17,9 @@ + + @@ -24,7 +27,8 @@ - + + \ No newline at end of file diff --git a/Sphere/Sphere.Godot/sph.db b/Sphere/Sphere.Godot/sph.db index e01beb88..55e8f41b 100644 Binary files a/Sphere/Sphere.Godot/sph.db and b/Sphere/Sphere.Godot/sph.db differ diff --git a/Sphere/Sphere.Test.Unit/AssemblyInfo.cs b/Sphere/Sphere.Test.Unit/AssemblyInfo.cs index fa3a4fa5..a9d48811 100644 --- a/Sphere/Sphere.Test.Unit/AssemblyInfo.cs +++ b/Sphere/Sphere.Test.Unit/AssemblyInfo.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Xunit.Extensions.AssemblyFixture; @@ -17,3 +18,4 @@ [assembly: Guid("f91221b8-2065-4217-ad15-b75cdfdffd1a")] [assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] +[assembly: InternalsVisibleTo("Sphere.Test.Unit")] diff --git a/Sphere/Sphere.Test.Unit/Readers/PacketReaderTests.cs b/Sphere/Sphere.Test.Unit/Readers/PacketReaderTests.cs index e9de4b55..42c4d068 100644 --- a/Sphere/Sphere.Test.Unit/Readers/PacketReaderTests.cs +++ b/Sphere/Sphere.Test.Unit/Readers/PacketReaderTests.cs @@ -1,8 +1,10 @@ using FluentAssertions; +using Microsoft.Extensions.Logging; using Moq; using Sphere.Common.Interfaces.Tcp; using Sphere.Common.Packets.Client; using Sphere.Services.Readers; +using Sphere.Services.Services.Tcp; using Sphere.Test.Common; namespace Sphere.Test.Unit.Readers @@ -11,6 +13,7 @@ public class PacketReaderTests { private readonly Mock _tcpClientAccessor = new Mock(); private readonly Mock _tcpClientMock = new Mock(); + private readonly Mock> _loggerMock = new Mock>(); private readonly Stream _stream = new MemoryStream(); public PacketReaderTests() { @@ -32,7 +35,7 @@ public async Task Reader_shouldReadOnePacketAtATime() _tcpClientAccessor.Setup(x => x.Client).Returns(_tcpClientMock.Object); _tcpClientAccessor.Setup(x => x.ClientId).Returns(1); - var reader = new SpherePacketReader(_tcpClientAccessor.Object); + var reader = new SpherePacketReader(_tcpClientAccessor.Object, _loggerMock.Object); // Act await reader.MoveNextAsync(); @@ -55,7 +58,7 @@ public async Task Reader_shouldReturnFalseIfStreamIsEmpty() tcpClientAccessor.Setup(x => x.Client).Returns(tcpClient.Object); tcpClientAccessor.Setup(x => x.ClientId).Returns(1); - var reader = new SpherePacketReader(tcpClientAccessor.Object); + var reader = new SpherePacketReader(tcpClientAccessor.Object, _loggerMock.Object); // Act var result = await reader.MoveNextAsync(); @@ -78,7 +81,7 @@ public async Task Reader_shouldNotThrowOnInvalidPacket() tcpClientAccessor.Setup(x => x.Client).Returns(tcpClient.Object); tcpClientAccessor.Setup(x => x.ClientId).Returns(1); - var reader = new SpherePacketReader(tcpClientAccessor.Object); + var reader = new SpherePacketReader(tcpClientAccessor.Object, _loggerMock.Object); // Act var result = await reader.MoveNextAsync(); diff --git a/Sphere/Sphere.Test.Unit/ServerPacketTests/SpawnMobPacketTests.cs b/Sphere/Sphere.Test.Unit/ServerPacketTests/SpawnMobPacketTests.cs new file mode 100644 index 00000000..92e8e3d6 --- /dev/null +++ b/Sphere/Sphere.Test.Unit/ServerPacketTests/SpawnMobPacketTests.cs @@ -0,0 +1,36 @@ +using Sphere.Common.Enums; +using Sphere.Common.Models; + +namespace Sphere.Test.Unit.ServerPacketTests +{ + public class SpawnMobPacketTests + { + [Fact] + public void Serialization() + { + // Arrange + var packet = new SpawnMobModel + { + EntityId = 4269, + CurrentHP = 100, + MaxHP = 100, + Type = MonsterType.Assassin, + Level = 1, + Coordinates = new Coordinates + { + Angle = 25, + X = 425.9232f, + Y = 153.42842f, + Z = -1301.6813f + } + }; + + // Act + var serverPacket = packet.ToServerPacket(); + var bytes = serverPacket.GetBytes(); + + // Assert + Console.WriteLine(BitConverter.ToString(bytes)); + } + } +}