diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e1c79c57..e58eea533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ ## Changelog +0.9.0: +- Now compatible with Dark Fog (DSP 0.10.x) with enemies disabled +- @phantomgamers: fix compilation after update and overall fixes/cleanup +- @phantomgamers: fix UIVirtualStarmap patches +- @phantomgamers: reviewing code from other contributers +- @starfi5h: fix runtime issues after the update and overall fixes/cleanup +- @starfi5h: improve UI and Keybinding +- @starfi5h: rework Wireless Power Tower syncing +- @starfi5h: add syncing for Battlefield Analysis Base +- @mmjr: disable dark fog switch in lobby and prevent df enabled saves to be loaded. +- @ajh16: sync dark fog lobby settings +- @highrizk: sync storage filters +- @highrizk: update serializer and fix broken packets +- @highrizk: add serialization support and unit tests for dictionaries +- @zzarek: add turret UI syncing +- @sp00ktober: add syncing for new mecha settings and features +- @sp00ktober: sync mecha and battle base construction drones +- @sp00ktober: overall NRE fixes + 0.8.14: - @starfi5h: Fix mecha animation when player count > 2 - @starfi5h: Fix UIPerformance save test in multiplayer diff --git a/Directory.Build.props b/Directory.Build.props index 40eebb5d9..11e81d6a2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,7 +20,7 @@ false true - $(PluginOutputDirectory) + $(PluginOutputDirectory) net472 latest true diff --git a/Nebula.sln b/Nebula.sln index ed2b3f52e..6c9204d7b 100644 --- a/Nebula.sln +++ b/Nebula.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAPI", "NebulaAPI\Nebu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "discord_game_sdk_dotnet", "dep\discord_game_sdk\discord_game_sdk_dotnet.csproj", "{E691A758-2D19-47EF-9410-3C78D99E2488}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaTests", "NebulaTests\NebulaTests.csproj", "{2F4901A9-8BDB-49A7-AD89-9E38B0B3350A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,6 +63,10 @@ Global {E691A758-2D19-47EF-9410-3C78D99E2488}.Debug|Any CPU.Build.0 = Debug|Any CPU {E691A758-2D19-47EF-9410-3C78D99E2488}.Release|Any CPU.ActiveCfg = Release|Any CPU {E691A758-2D19-47EF-9410-3C78D99E2488}.Release|Any CPU.Build.0 = Release|Any CPU + {2F4901A9-8BDB-49A7-AD89-9E38B0B3350A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F4901A9-8BDB-49A7-AD89-9E38B0B3350A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F4901A9-8BDB-49A7-AD89-9E38B0B3350A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F4901A9-8BDB-49A7-AD89-9E38B0B3350A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NebulaAPI/DataStructures/IMechaData.cs b/NebulaAPI/DataStructures/IMechaData.cs index 7a21aed54..ee32d8b38 100644 --- a/NebulaAPI/DataStructures/IMechaData.cs +++ b/NebulaAPI/DataStructures/IMechaData.cs @@ -8,7 +8,7 @@ namespace NebulaAPI.DataStructures; public interface IMechaData : INetSerializable { - int SandCount { get; set; } + long SandCount { get; set; } double CoreEnergy { get; set; } double ReactorEnergy { get; set; } StorageComponent Inventory { get; set; } @@ -16,4 +16,8 @@ public interface IMechaData : INetSerializable StorageComponent ReactorStorage { get; set; } StorageComponent WarpStorage { get; set; } MechaForge Forge { get; set; } + ConstructionModuleComponent ConstructionModule { get; set; } + IMechaFightData FightData { get; set; } + + public void UpdateMech(Player destination); } diff --git a/NebulaAPI/DataStructures/IMechaFightData.cs b/NebulaAPI/DataStructures/IMechaFightData.cs new file mode 100644 index 000000000..745427002 --- /dev/null +++ b/NebulaAPI/DataStructures/IMechaFightData.cs @@ -0,0 +1,36 @@ +using NebulaAPI.Interfaces; + +namespace NebulaAPI.DataStructures +{ + public interface IMechaFightData + { + // TODO: what about spaceSector? Will it be synced in dynamically? Or should it be stored? + bool AutoReplenishFuel { get; set; } + bool AutoReplenishAmmo { get; set; } + bool AutoReplenishHangar { get; set; } + int Hp { get; set; } + long EnergyShieldEnergy { get; set; } + int AmmoItemId { get; set; } + int AmmoInc { get; set; } + int AmmoBulletCount { get; set; } + int AmmoSelectSlot { get; set; } + int AmmoMuzzleFire { get; set; } + int AmmoRoundFire { get; set; } + int AmmoMuzzleIndex { get; set; } + bool LaserActive { get; set; } + bool LaserRecharging { get; set; } + long LaserEnergy { get; set; } + int LaserFire { get; set; } + int BombFire { get; set; } + StorageComponent AmmoStorage { get; set; } + StorageComponent BombStorage { get; set; } + EnemyHatredTarget AmmoHatredTarget { get; set; } + EnemyHatredTarget LaserHatredTarget { get; set; } + StorageComponent FighterStorage { get; set; } + CombatModuleComponent GroundCombatModule { get; set; } + CombatModuleComponent SpaceCombatModule { get; set; } + public void Serialize(INetDataWriter writer); + public void Deserialize(INetDataReader reader); + public void UpdateMech(Player destination); + } +} diff --git a/NebulaAPI/DataStructures/IPlayerTechBonuses.cs b/NebulaAPI/DataStructures/IPlayerTechBonuses.cs index 18b3828f5..2d48b8d45 100644 --- a/NebulaAPI/DataStructures/IPlayerTechBonuses.cs +++ b/NebulaAPI/DataStructures/IPlayerTechBonuses.cs @@ -31,10 +31,22 @@ public interface IPlayerTechBonuses : INetSerializable float maxWarpSpeed { get; set; } float buildArea { get; set; } int droneCount { get; set; } - float droneSpeed { get; set; } - int droneMovement { get; set; } int inventorySize { get; set; } bool deliveryPackageUnlocked { get; set; } int deliveryPackageColCount { get; set; } int deliveryPackageStackSizeMultiplier { get; set; } + double instantBuildEnergy { get; set; } + int hpMaxUpgrade { get; set; } + bool energyShieldUnlocked { get; set; } + float energyShieldRadius { get; set; } + long energyShieldCapacity { get; set; } + long laserEnergyCapacity { get; set; } + float laserLocalAttackRange { get; set; } + float laserSpaceAttackRange { get; set; } + int laserLocalEnergyCost { get; set; } + int laserSpaceEnergyCost { get; set; } + int laserLocalDamage { get; set; } + int laserSpaceDamage { get; set; } + int groundFleetCount { get; set; } + int spaceFleetCount { get; set; } } diff --git a/NebulaAPI/GameState/IFactoryManager.cs b/NebulaAPI/GameState/IFactoryManager.cs index 63bba542a..3b5d9ea2b 100644 --- a/NebulaAPI/GameState/IFactoryManager.cs +++ b/NebulaAPI/GameState/IFactoryManager.cs @@ -44,8 +44,4 @@ public interface IFactoryManager : IDisposable int GetNextPrebuildId(int planetId); int GetNextPrebuildId(PlanetFactory factory); - - void OnNewSetInserterPickTarget(int objId, int otherObjId, int inserterId, int offset, Vector3 pointPos); - - void OnNewSetInserterInsertTarget(int objId, int otherObjId, int inserterId, int offset, Vector3 pointPos); } diff --git a/NebulaAPI/Interfaces/INetDataReader.cs b/NebulaAPI/Interfaces/INetDataReader.cs new file mode 100644 index 000000000..263c1ca4b --- /dev/null +++ b/NebulaAPI/Interfaces/INetDataReader.cs @@ -0,0 +1,151 @@ +// #pragma once +// #ifndef INetDataReader.cs_H_ +// #define INetDataReader.cs_H_ +// +// #endif + +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace NebulaAPI.Interfaces; + +public interface INetDataReader +{ + byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int UserDataOffset + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + void SkipBytes(int count); + void SetPosition(int position); + void SetSource(INetDataWriter dataWriter); + void SetSource(byte[] source); + void SetSource(byte[] source, int offset, int maxSize); + IPEndPoint GetNetEndPoint(); + byte GetByte(); + sbyte GetSByte(); + T[] GetArray(int size); + bool[] GetBoolArray(); + ushort[] GetUShortArray(); + short[] GetShortArray(); + int[] GetIntArray(); + uint[] GetUIntArray(); + float[] GetFloatArray(); + double[] GetDoubleArray(); + long[] GetLongArray(); + ulong[] GetULongArray(); + string[] GetStringArray(); + + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + string[] GetStringArray(int maxStringLength); + + bool GetBool(); + char GetChar(); + ushort GetUShort(); + short GetShort(); + long GetLong(); + ulong GetULong(); + int GetInt(); + uint GetUInt(); + float GetFloat(); + double GetDouble(); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + /// "string.Empty" if value > "maxLength" + string GetString(int maxLength); + + string GetString(); + ArraySegment GetBytesSegment(int count); + ArraySegment GetRemainingBytesSegment(); + T Get() where T : struct, INetSerializable; + T Get(Func constructor) where T : class, INetSerializable; + byte[] GetRemainingBytes(); + void GetBytes(byte[] destination, int start, int count); + void GetBytes(byte[] destination, int count); + sbyte[] GetSBytesWithLength(); + byte[] GetBytesWithLength(); + byte PeekByte(); + sbyte PeekSByte(); + bool PeekBool(); + char PeekChar(); + ushort PeekUShort(); + short PeekShort(); + long PeekLong(); + ulong PeekULong(); + int PeekInt(); + uint PeekUInt(); + float PeekFloat(); + double PeekDouble(); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + string PeekString(int maxLength); + + string PeekString(); + bool TryGetByte(out byte result); + bool TryGetSByte(out sbyte result); + bool TryGetBool(out bool result); + bool TryGetChar(out char result); + bool TryGetShort(out short result); + bool TryGetUShort(out ushort result); + bool TryGetInt(out int result); + bool TryGetUInt(out uint result); + bool TryGetLong(out long result); + bool TryGetULong(out ulong result); + bool TryGetFloat(out float result); + bool TryGetDouble(out double result); + bool TryGetString(out string result); + bool TryGetStringArray(out string[] result); + bool TryGetBytesWithLength(out byte[] result); + void Clear(); +} diff --git a/NebulaAPI/Interfaces/INetDataWriter.cs b/NebulaAPI/Interfaces/INetDataWriter.cs new file mode 100644 index 000000000..f92789ed4 --- /dev/null +++ b/NebulaAPI/Interfaces/INetDataWriter.cs @@ -0,0 +1,85 @@ +// #pragma once +// #ifndef INetDataWriter.cs_H_ +// #define INetDataWriter.cs_H_ +// +// #endif + +using System; +using System.Net; +using System.Runtime.CompilerServices; + +namespace NebulaAPI.Interfaces; + +public interface INetDataWriter +{ + int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + byte[] Data + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + void ResizeIfNeed(int newSize); + void EnsureFit(int additionalSize); + void Reset(int size); + void Reset(); + byte[] CopyData(); + + /// + /// Sets position of NetDataWriter to rewrite previous values + /// + /// new byte position + /// previous position of data writer + int SetPosition(int position); + + void Put(float value); + void Put(double value); + void Put(long value); + void Put(ulong value); + void Put(int value); + void Put(uint value); + void Put(char value); + void Put(ushort value); + void Put(short value); + void Put(sbyte value); + void Put(byte value); + void Put(byte[] data, int offset, int length); + void Put(byte[] data); + void Put(bool value); + void Put(IPEndPoint endPoint); + void Put(string value); + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + void Put(string value, int maxLength); + + void Put(T obj) where T : INetSerializable; + void PutSBytesWithLength(sbyte[] data, int offset, int length); + void PutSBytesWithLength(sbyte[] data); + void PutBytesWithLength(byte[] data, int offset, int length); + void PutBytesWithLength(byte[] data); + void PutArray(Array arr, int sz); + void PutArray(float[] value); + void PutArray(double[] value); + void PutArray(long[] value); + void PutArray(ulong[] value); + void PutArray(int[] value); + void PutArray(uint[] value); + void PutArray(ushort[] value); + void PutArray(short[] value); + void PutArray(bool[] value); + void PutArray(string[] value); + void PutArray(string[] value, int strMaxLength); +} diff --git a/NebulaAPI/Interfaces/INetSerializable.cs b/NebulaAPI/Interfaces/INetSerializable.cs index 777597aaa..0c55052df 100644 --- a/NebulaAPI/Interfaces/INetSerializable.cs +++ b/NebulaAPI/Interfaces/INetSerializable.cs @@ -13,141 +13,3 @@ public interface INetSerializable void Deserialize(INetDataReader reader); } - -public interface INetDataWriter -{ - void Put(float value); - - void Put(double value); - - void Put(long value); - - void Put(ulong value); - - void Put(int value); - - void Put(uint value); - - void Put(char value); - - void Put(ushort value); - - void Put(short value); - - void Put(sbyte value); - - void Put(byte value); - - void Put(byte[] data, int offset, int length); - - void Put(byte[] data); - - void PutSBytesWithLength(sbyte[] data, int offset, int length); - - void PutSBytesWithLength(sbyte[] data); - - void PutBytesWithLength(byte[] data, int offset, int length); - - void PutBytesWithLength(byte[] data); - - void Put(bool value); - - void PutArray(float[] value); - - void PutArray(double[] value); - - void PutArray(long[] value); - - void PutArray(ulong[] value); - - void PutArray(int[] value); - - void PutArray(uint[] value); - - void PutArray(ushort[] value); - - void PutArray(short[] value); - - void PutArray(bool[] value); - - void PutArray(string[] value); - - void PutArray(string[] value, int maxLength); - - void Put(IPEndPoint endPoint); - - void Put(string value); - - void Put(string value, int maxLength); - - void Put(T obj) where T : INetSerializable; -} - -public interface INetDataReader -{ - IPEndPoint GetNetEndPoint(); - - byte GetByte(); - - sbyte GetSByte(); - - bool[] GetBoolArray(); - - ushort[] GetUShortArray(); - - short[] GetShortArray(); - - long[] GetLongArray(); - - ulong[] GetULongArray(); - - int[] GetIntArray(); - - uint[] GetUIntArray(); - - float[] GetFloatArray(); - - double[] GetDoubleArray(); - - string[] GetStringArray(); - - string[] GetStringArray(int maxStringLength); - - bool GetBool(); - - char GetChar(); - - ushort GetUShort(); - - short GetShort(); - - long GetLong(); - - ulong GetULong(); - - int GetInt(); - - uint GetUInt(); - - float GetFloat(); - - double GetDouble(); - - string GetString(int maxLength); - - string GetString(); - - ArraySegment GetRemainingBytesSegment(); - - T Get() where T : INetSerializable, new(); - - byte[] GetRemainingBytes(); - - void GetBytes(byte[] destination, int start, int count); - - void GetBytes(byte[] destination, int count); - - sbyte[] GetSBytesWithLength(); - - byte[] GetBytesWithLength(); -} diff --git a/NebulaModel/DataStructures/MechaData.cs b/NebulaModel/DataStructures/MechaData.cs index 117d05c04..64e162491 100644 --- a/NebulaModel/DataStructures/MechaData.cs +++ b/NebulaModel/DataStructures/MechaData.cs @@ -5,6 +5,7 @@ using NebulaAPI.Interfaces; using NebulaAPI.Packets; using NebulaModel.Packets.Players; +using static NebulaModel.Networking.BinaryUtils; #endregion @@ -15,27 +16,28 @@ public class MechaData : IMechaData { public MechaData() { - //This is needed for the serialization and deserialization + // This is needed for the serialization and deserialization Forge = new MechaForge { tasks = [] }; TechBonuses = new PlayerTechBonuses(); } - public MechaData(int sandCount, double coreEnergy, double reactorEnergy, StorageComponent inventory, - DeliveryPackage deliveryPackage, StorageComponent reactorStorage, StorageComponent warpStorage, MechaForge forge) + public MechaData(Player player) { - SandCount = sandCount; - CoreEnergy = coreEnergy; - ReactorEnergy = reactorEnergy; - ReactorStorage = reactorStorage; - WarpStorage = warpStorage; - Forge = forge; - Inventory = inventory; - DeliveryPackage = deliveryPackage; + SandCount = player.sandCount; + CoreEnergy = player.mecha.coreEnergy; + ReactorEnergy = player.mecha.reactorEnergy; + ReactorStorage = player.mecha.reactorStorage; + WarpStorage = player.mecha.warpStorage; + Forge = player.mecha.forge; + Inventory = player.package; + DeliveryPackage = player.deliveryPackage; + ConstructionModule = player.mecha.constructionModule; + FightData = new MechaFightData(player); TechBonuses = new PlayerTechBonuses(); } public PlayerTechBonuses TechBonuses { get; set; } - public int SandCount { get; set; } + public long SandCount { get; set; } public double CoreEnergy { get; set; } public double ReactorEnergy { get; set; } public StorageComponent Inventory { get; set; } @@ -43,6 +45,8 @@ public MechaData(int sandCount, double coreEnergy, double reactorEnergy, Storage public StorageComponent ReactorStorage { get; set; } public StorageComponent WarpStorage { get; set; } public MechaForge Forge { get; set; } + public ConstructionModuleComponent ConstructionModule { get; set; } + public IMechaFightData FightData { get; set; } public void Serialize(INetDataWriter writer) { @@ -55,6 +59,7 @@ public void Serialize(INetDataWriter writer) { return; } + FightData.Serialize(writer); using var ms = new MemoryStream(); using (var wr = new BinaryWriter(ms)) { @@ -63,6 +68,7 @@ public void Serialize(INetDataWriter writer) ReactorStorage.Export(wr); WarpStorage.Export(wr); Forge.Export(wr); + ConstructionModule.Export(wr); } var export = ms.ToArray(); writer.Put(export.Length); @@ -72,14 +78,16 @@ public void Serialize(INetDataWriter writer) public void Deserialize(INetDataReader reader) { TechBonuses = new PlayerTechBonuses(); + FightData = new MechaFightData(); Inventory = new StorageComponent(4); DeliveryPackage = new DeliveryPackage(); DeliveryPackage.Init(); ReactorStorage = new StorageComponent(4); WarpStorage = new StorageComponent(1); Forge = new MechaForge { tasks = [], extraItems = new ItemBundle() }; + ConstructionModule = new ConstructionModuleComponent(); TechBonuses.Deserialize(reader); - SandCount = reader.GetInt(); + SandCount = reader.GetLong(); CoreEnergy = reader.GetDouble(); ReactorEnergy = reader.GetDouble(); var isPayloadPresent = reader.GetBool(); @@ -87,6 +95,7 @@ public void Deserialize(INetDataReader reader) { return; } + FightData.Deserialize(reader); var mechaLength = reader.GetInt(); var mechaBytes = new byte[mechaLength]; reader.GetBytes(mechaBytes, mechaLength); @@ -97,6 +106,29 @@ public void Deserialize(INetDataReader reader) ReactorStorage.Import(br); WarpStorage.Import(br); Forge.Import(br); + ConstructionModule.Import(br); + } + + public void UpdateMech(Player destination) + { + destination.package = Inventory; + using (var ms = new MemoryStream()) + { + var bw = new BinaryWriter(ms); + DeliveryPackage.Export(bw); + ms.Seek(0, SeekOrigin.Begin); + var br = new BinaryReader(ms); + destination.deliveryPackage.Import(br); + DeliveryPackage = destination.deliveryPackage; + } + destination.mecha.coreEnergy = CoreEnergy; + destination.mecha.reactorEnergy = ReactorEnergy; + destination.mecha.forge = Forge; + destination.mecha.reactorStorage = ReactorStorage; + destination.mecha.warpStorage = WarpStorage; + destination.mecha.constructionModule = ConstructionModule; + FightData.UpdateMech(destination); + destination.SetSandCount(SandCount); } public void Import(INetDataReader reader, int revision) @@ -108,6 +140,7 @@ public void Import(INetDataReader reader, int revision) ReactorStorage = new StorageComponent(4); WarpStorage = new StorageComponent(1); Forge = new MechaForge { tasks = [], extraItems = new ItemBundle() }; + ConstructionModule = new ConstructionModuleComponent(); TechBonuses.Import(reader, revision); SandCount = reader.GetInt(); CoreEnergy = reader.GetDouble(); @@ -117,6 +150,10 @@ public void Import(INetDataReader reader, int revision) { return; } + if (revision >= 8) + { + FightData.Deserialize(reader); + } var mechaLength = reader.GetInt(); var mechaBytes = new byte[mechaLength]; reader.GetBytes(mechaBytes, mechaLength); @@ -130,5 +167,9 @@ public void Import(INetDataReader reader, int revision) ReactorStorage.Import(br); WarpStorage.Import(br); Forge.Import(br); + if (revision >= 8) + { + ConstructionModule.Import(br); + } } } diff --git a/NebulaModel/DataStructures/MechaFightData.cs b/NebulaModel/DataStructures/MechaFightData.cs new file mode 100644 index 000000000..58659b637 --- /dev/null +++ b/NebulaModel/DataStructures/MechaFightData.cs @@ -0,0 +1,190 @@ +#region +using System.IO; +using NebulaAPI.DataStructures; +using NebulaAPI.Interfaces; +#endregion + +namespace NebulaModel.DataStructures +{ + internal class MechaFightData : IMechaFightData + { + public MechaFightData() + { + // This is needed for the serialization and deserialization + AmmoStorage = new StorageComponent(3); + BombStorage = new StorageComponent(1); + FighterStorage = new StorageComponent(5); + GroundCombatModule = new CombatModuleComponent(); + SpaceCombatModule = new CombatModuleComponent(); + + GroundCombatModule.Reset(); + SpaceCombatModule.Reset(); + GroundCombatModule.Init(GameMain.data); + SpaceCombatModule.Init(GameMain.data); + GroundCombatModule.Setup(1, GameMain.data); + SpaceCombatModule.Setup(3, GameMain.data); + } + public MechaFightData(Player player) + { + AutoReplenishFuel = player.mecha.autoReplenishFuel; + AutoReplenishAmmo = player.mecha.autoReplenishAmmo; + AutoReplenishHangar = player.mecha.autoReplenishHangar; + Hp = player.mecha.hp; + EnergyShieldEnergy = player.mecha.energyShieldEnergy; + AmmoItemId = player.mecha.ammoItemId; + AmmoInc = player.mecha.ammoInc; + AmmoBulletCount = player.mecha.ammoBulletCount; + AmmoSelectSlot = player.mecha.ammoSelectSlot; + AmmoMuzzleFire = player.mecha.ammoMuzzleFire; + AmmoRoundFire = player.mecha.ammoRoundFire; + AmmoMuzzleIndex = player.mecha.ammoMuzzleIndex; + LaserActive = player.mecha.laserActive; + LaserRecharging = player.mecha.laserRecharging; + LaserEnergy = player.mecha.laserEnergy; + LaserFire = player.mecha.laserFire; + BombFire = player.mecha.bombFire; + AmmoStorage = player.mecha.ammoStorage; + BombStorage = player.mecha.bombStorage; + AmmoHatredTarget = player.mecha.ammoHatredTarget; + LaserHatredTarget = player.mecha.laserHatredTarget; + FighterStorage = player.mecha.fighterStorage; + GroundCombatModule = player.mecha.groundCombatModule; + SpaceCombatModule = player.mecha.spaceCombatModule; + } + public bool AutoReplenishFuel { get; set; } + public bool AutoReplenishAmmo { get; set; } + public bool AutoReplenishHangar { get; set; } + public int Hp { get; set; } + public long EnergyShieldEnergy { get; set; } + public int AmmoItemId { get; set; } + public int AmmoInc { get; set; } + public int AmmoBulletCount { get; set; } + public int AmmoSelectSlot { get; set; } + public int AmmoMuzzleFire { get; set; } + public int AmmoRoundFire { get; set; } + public int AmmoMuzzleIndex { get; set; } + public bool LaserActive { get; set; } + public bool LaserRecharging { get; set; } + public long LaserEnergy { get; set; } + public int LaserFire { get; set; } + public int BombFire { get; set; } + public StorageComponent AmmoStorage { get; set; } + public StorageComponent BombStorage { get; set; } + public EnemyHatredTarget AmmoHatredTarget { get; set; } + public EnemyHatredTarget LaserHatredTarget { get; set; } + public StorageComponent FighterStorage { get; set; } + public CombatModuleComponent GroundCombatModule { get; set; } + public CombatModuleComponent SpaceCombatModule { get; set; } + public void Serialize(INetDataWriter writer) + { + writer.Put(AutoReplenishFuel); + writer.Put(AutoReplenishAmmo); + writer.Put(AutoReplenishHangar); + writer.Put(Hp); + writer.Put(EnergyShieldEnergy); + writer.Put(AmmoItemId); + writer.Put(AmmoInc); + writer.Put(AmmoBulletCount); + writer.Put(AmmoSelectSlot); + writer.Put(AmmoMuzzleFire); + writer.Put(AmmoRoundFire); + writer.Put(AmmoMuzzleIndex); + writer.Put(LaserActive); + writer.Put(LaserRecharging); + writer.Put(LaserEnergy); + writer.Put(LaserFire); + writer.Put(BombFire); + + using var ms = new MemoryStream(); + using (var wr = new BinaryWriter(ms)) + { + AmmoStorage.Export(wr); + BombStorage.Export(wr); + AmmoHatredTarget.Export(wr); + LaserHatredTarget.Export(wr); + FighterStorage.Export(wr); + GroundCombatModule.Export(wr); + SpaceCombatModule.Export(wr); + } + var export = ms.ToArray(); + writer.Put(export.Length); + writer.Put(export); + } + public void Deserialize(INetDataReader reader) + { + AmmoStorage = new StorageComponent(3); + BombStorage = new StorageComponent(1); + AmmoHatredTarget = default(EnemyHatredTarget); + LaserHatredTarget = default(EnemyHatredTarget); + FighterStorage = new StorageComponent(5); + GroundCombatModule = new CombatModuleComponent(); + SpaceCombatModule = new CombatModuleComponent(); + + AutoReplenishFuel = reader.GetBool(); + AutoReplenishAmmo = reader.GetBool(); + AutoReplenishHangar = reader.GetBool(); + Hp = reader.GetInt(); + EnergyShieldEnergy = reader.GetLong(); + AmmoItemId = reader.GetInt(); + AmmoInc = reader.GetInt(); + AmmoBulletCount = reader.GetInt(); + AmmoSelectSlot = reader.GetInt(); + AmmoMuzzleFire = reader.GetInt(); + AmmoRoundFire = reader.GetInt(); + AmmoMuzzleIndex = reader.GetInt(); + LaserActive = reader.GetBool(); + LaserRecharging = reader.GetBool(); + LaserEnergy = reader.GetLong(); + LaserFire = reader.GetInt(); + BombFire = reader.GetInt(); + + if (Hp == 0) + { + // prevent instant death, which can happen when a player joins for the first time and then exits again before sending the first mecha data update. + // when the host saves in this situation, the Hp would be set to 0 and on every next join the client would be insta killed. lol + Hp = GameMain.mainPlayer.mecha.hpMaxApplied; + } + + var fightLength = reader.GetInt(); + var fightBytes = new byte[fightLength]; + reader.GetBytes(fightBytes, fightLength); + using var ms = new MemoryStream(fightBytes); + using var br = new BinaryReader(ms); + + AmmoStorage.Import(br); + BombStorage.Import(br); + AmmoHatredTarget.Import(br); + LaserHatredTarget.Import(br); + FighterStorage.Import(br); + GroundCombatModule.Import(br); + SpaceCombatModule.Import(br); + } + public void UpdateMech(Player destination) + { + destination.mecha.autoReplenishFuel = AutoReplenishFuel; + destination.mecha.autoReplenishAmmo = AutoReplenishAmmo; + destination.mecha.autoReplenishHangar = AutoReplenishHangar; + destination.mecha.hp = Hp; + destination.mecha.energyShieldEnergy = EnergyShieldEnergy; + destination.mecha.ammoItemId = AmmoItemId; + destination.mecha.ammoInc = AmmoInc; + destination.mecha.ammoBulletCount = AmmoBulletCount; + destination.mecha.ammoSelectSlot = AmmoSelectSlot; + destination.mecha.ammoMuzzleFire = AmmoMuzzleFire; + destination.mecha.ammoRoundFire = AmmoRoundFire; + destination.mecha.ammoMuzzleIndex = AmmoMuzzleIndex; + destination.mecha.laserActive = LaserActive; + destination.mecha.laserRecharging = LaserRecharging; + destination.mecha.laserEnergy = LaserEnergy; + destination.mecha.laserFire = LaserFire; + destination.mecha.bombFire = BombFire; + destination.mecha.ammoStorage = AmmoStorage; + destination.mecha.bombStorage = BombStorage; + destination.mecha.ammoHatredTarget = AmmoHatredTarget; + destination.mecha.laserHatredTarget = LaserHatredTarget; + destination.mecha.fighterStorage = FighterStorage; + destination.mecha.groundCombatModule = GroundCombatModule; + destination.mecha.spaceCombatModule = SpaceCombatModule; + } + } +} diff --git a/NebulaModel/DataStructures/PlayerPosition.cs b/NebulaModel/DataStructures/PlayerPosition.cs new file mode 100644 index 000000000..a65eb02c3 --- /dev/null +++ b/NebulaModel/DataStructures/PlayerPosition.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace NebulaModel.DataStructures; + +public class PlayerPosition +{ + public PlayerPosition(Vector3 position, int planetId) + { + Position = position; + PlanetId = planetId; + } + + public Vector3 Position { get; set; } + public int PlanetId { get; set; } +} diff --git a/NebulaModel/NebulaModel.csproj b/NebulaModel/NebulaModel.csproj index 9a118cda4..52902530e 100644 --- a/NebulaModel/NebulaModel.csproj +++ b/NebulaModel/NebulaModel.csproj @@ -1,8 +1,8 @@  - - + + @@ -15,6 +15,6 @@ - + \ No newline at end of file diff --git a/NebulaModel/NetworkProvider.cs b/NebulaModel/NetworkProvider.cs index 3748650fc..bf7f68ebd 100644 --- a/NebulaModel/NetworkProvider.cs +++ b/NebulaModel/NetworkProvider.cs @@ -12,11 +12,11 @@ public abstract class NetworkProvider : INetworkProvider { protected NetworkProvider(IPlayerManager playerManager) { - PacketProcessor = new NetPacketProcessor(); + PacketProcessor = new NebulaNetPacketProcessor(); PlayerManager = playerManager; } - public NetPacketProcessor PacketProcessor { get; set; } + public NebulaNetPacketProcessor PacketProcessor { get; set; } public abstract void Dispose(); diff --git a/NebulaModel/Networking/NebulaConnection.cs b/NebulaModel/Networking/NebulaConnection.cs index c769f1495..a7f21b5dc 100644 --- a/NebulaModel/Networking/NebulaConnection.cs +++ b/NebulaModel/Networking/NebulaConnection.cs @@ -15,13 +15,13 @@ namespace NebulaModel.Networking; public class NebulaConnection : INebulaConnection { - private readonly NetPacketProcessor packetProcessor; + private readonly NebulaNetPacketProcessor packetProcessor; private readonly EndPoint peerEndpoint; private readonly WebSocket peerSocket; private readonly Queue pendingPackets = new(); private bool enable = true; - public NebulaConnection(WebSocket peerSocket, EndPoint peerEndpoint, NetPacketProcessor packetProcessor) + public NebulaConnection(WebSocket peerSocket, EndPoint peerEndpoint, NebulaNetPacketProcessor packetProcessor) { this.peerEndpoint = peerEndpoint; this.peerSocket = peerSocket; diff --git a/NebulaModel/Networking/PacketUtils.cs b/NebulaModel/Networking/PacketUtils.cs index 32cf724fe..1837b57c8 100644 --- a/NebulaModel/Networking/PacketUtils.cs +++ b/NebulaModel/Networking/PacketUtils.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using NebulaAPI; using NebulaAPI.Packets; @@ -29,19 +30,27 @@ public static void RegisterAllPacketNestedTypesInAssembly(Assembly assembly, Net { Log.Debug($"Registering Nested Type: {type.Name}"); } + if (type.IsClass) { var registerMethod = packetProcessor.GetType().GetMethods() .Where(m => m.Name == nameof(NetPacketProcessor.RegisterNestedType)) .FirstOrDefault(m => - m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Name.Equals(typeof(Func<>).Name)) + m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.Name.Equals(typeof(Func<>).Name))! .MakeGenericMethod(type); - var delegateMethod = packetProcessor.GetType().GetMethod(nameof(NetPacketProcessor.CreateNestedClassInstance)) + var constructorMethod = typeof(Activator) + .GetMethods() + .Where((info => info.GetParameters().Length == 0)) + .FirstOrDefault()! .MakeGenericMethod(type); + + // create a Func delegate from the object's constructor to pass into NetPacketProcessor.RegisterNestedType var funcType = typeof(Func<>).MakeGenericType(type); - var callback = Delegate.CreateDelegate(funcType, packetProcessor, delegateMethod); - registerMethod.Invoke(packetProcessor, new object[] { callback }); + var constructorDelegate = Delegate.CreateDelegate(funcType, constructorMethod); + + // Invoke NetPacketProcessor.RegisterNestedType(Func constructor) + registerMethod.Invoke(packetProcessor, new object[] { constructorDelegate }); } else if (type.IsValueType) { @@ -66,8 +75,10 @@ private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) { return true; } + toCheck = toCheck.BaseType; } + return false; } diff --git a/NebulaModel/Networking/Serialization/FastBitConverter.cs b/NebulaModel/Networking/Serialization/FastBitConverter.cs deleted file mode 100644 index 3081d4d7c..000000000 --- a/NebulaModel/Networking/Serialization/FastBitConverter.cs +++ /dev/null @@ -1,118 +0,0 @@ -#region - -using System.Collections.Generic; -using System.Runtime.InteropServices; - -#endregion - -namespace NebulaModel.Networking.Serialization; - -public static class FastBitConverter -{ - private static void WriteLittleEndian(IList buffer, int offset, ulong data) - { -#if BIGENDIAN - buffer[offset + 7] = (byte)(data); - buffer[offset + 6] = (byte)(data >> 8); - buffer[offset + 5] = (byte)(data >> 16); - buffer[offset + 4] = (byte)(data >> 24); - buffer[offset + 3] = (byte)(data >> 32); - buffer[offset + 2] = (byte)(data >> 40); - buffer[offset + 1] = (byte)(data >> 48); - buffer[offset] = (byte)(data >> 56); -#else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); - buffer[offset + 4] = (byte)(data >> 32); - buffer[offset + 5] = (byte)(data >> 40); - buffer[offset + 6] = (byte)(data >> 48); - buffer[offset + 7] = (byte)(data >> 56); -#endif - } - - private static void WriteLittleEndian(IList buffer, int offset, int data) - { -#if BIGENDIAN - buffer[offset + 3] = (byte)(data); - buffer[offset + 2] = (byte)(data >> 8); - buffer[offset + 1] = (byte)(data >> 16); - buffer[offset] = (byte)(data >> 24); -#else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); - buffer[offset + 2] = (byte)(data >> 16); - buffer[offset + 3] = (byte)(data >> 24); -#endif - } - - private static void WriteLittleEndian(IList buffer, int offset, short data) - { -#if BIGENDIAN - buffer[offset + 1] = (byte)(data); - buffer[offset] = (byte)(data >> 8); -#else - buffer[offset] = (byte)data; - buffer[offset + 1] = (byte)(data >> 8); -#endif - } - - public static void GetBytes(byte[] bytes, int startIndex, double value) - { - var ch = new ConverterHelperDouble { Adouble = value }; - WriteLittleEndian(bytes, startIndex, ch.Along); - } - - public static void GetBytes(byte[] bytes, int startIndex, float value) - { - var ch = new ConverterHelperFloat { Afloat = value }; - WriteLittleEndian(bytes, startIndex, ch.Aint); - } - - public static void GetBytes(IEnumerable bytes, int startIndex, short value) - { - WriteLittleEndian(bytes as IList, startIndex, value); - } - - public static void GetBytes(IEnumerable bytes, int startIndex, ushort value) - { - WriteLittleEndian(bytes as IList, startIndex, (short)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, int value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - public static void GetBytes(byte[] bytes, int startIndex, uint value) - { - WriteLittleEndian(bytes, startIndex, (int)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, long value) - { - WriteLittleEndian(bytes, startIndex, (ulong)value); - } - - public static void GetBytes(byte[] bytes, int startIndex, ulong value) - { - WriteLittleEndian(bytes, startIndex, value); - } - - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperDouble - { - [FieldOffset(0)] public ulong Along; - - [FieldOffset(0)] public double Adouble; - } - - [StructLayout(LayoutKind.Explicit)] - private struct ConverterHelperFloat - { - [FieldOffset(0)] public int Aint; - - [FieldOffset(0)] public float Afloat; - } -} diff --git a/NebulaModel/Networking/Serialization/LiteNetLib/FastBitConverter.cs b/NebulaModel/Networking/Serialization/LiteNetLib/FastBitConverter.cs new file mode 100644 index 000000000..7bf53c652 --- /dev/null +++ b/NebulaModel/Networking/Serialization/LiteNetLib/FastBitConverter.cs @@ -0,0 +1,174 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace NebulaModel.Networking.Serialization +{ + public static class FastBitConverter + { +#if (LITENETLIB_UNSAFE || LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER) && !BIGENDIAN +#if LITENETLIB_UNSAFE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + int size = sizeof(T); + if (bytes.Length < startIndex + size) + ThrowIndexOutOfRangeException(); +#if LITENETLIB_UNSAFELIB || NETCOREAPP3_1 || NET5_0 || NETCOREAPP3_0_OR_GREATER + Unsafe.As(ref bytes[startIndex]) = value; +#else + fixed (byte* ptr = &bytes[startIndex]) + { +#if UNITY_ANDROID + // On some android systems, assigning *(T*)ptr throws a NRE if + // the ptr isn't aligned (i.e. if Position is 1,2,3,5, etc.). + // Here we have to use memcpy. + // + // => we can't get a pointer of a struct in C# without + // marshalling allocations + // => instead, we stack allocate an array of type T and use that + // => stackalloc avoids GC and is very fast. it only works for + // value types, but all blittable types are anyway. + T* valueBuffer = stackalloc T[1] { value }; + UnsafeUtility.MemCpy(ptr, valueBuffer, size); +#else + *(T*)ptr = value; +#endif + } +#endif + } +#else + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, T value) where T : unmanaged + { + if (bytes.Length < startIndex + Unsafe.SizeOf()) + ThrowIndexOutOfRangeException(); + Unsafe.As(ref bytes[startIndex]) = value; + } +#endif + + private static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException(); +#else + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperDouble + { + [FieldOffset(0)] + public ulong Along; + + [FieldOffset(0)] + public double Adouble; + } + + [StructLayout(LayoutKind.Explicit)] + private struct ConverterHelperFloat + { + [FieldOffset(0)] + public int Aint; + + [FieldOffset(0)] + public float Afloat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, ulong data) + { +#if BIGENDIAN + buffer[offset + 7] = (byte)(data); + buffer[offset + 6] = (byte)(data >> 8); + buffer[offset + 5] = (byte)(data >> 16); + buffer[offset + 4] = (byte)(data >> 24); + buffer[offset + 3] = (byte)(data >> 32); + buffer[offset + 2] = (byte)(data >> 40); + buffer[offset + 1] = (byte)(data >> 48); + buffer[offset ] = (byte)(data >> 56); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); + buffer[offset + 4] = (byte)(data >> 32); + buffer[offset + 5] = (byte)(data >> 40); + buffer[offset + 6] = (byte)(data >> 48); + buffer[offset + 7] = (byte)(data >> 56); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteLittleEndian(byte[] buffer, int offset, int data) + { +#if BIGENDIAN + buffer[offset + 3] = (byte)(data); + buffer[offset + 2] = (byte)(data >> 8); + buffer[offset + 1] = (byte)(data >> 16); + buffer[offset ] = (byte)(data >> 24); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); + buffer[offset + 2] = (byte)(data >> 16); + buffer[offset + 3] = (byte)(data >> 24); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleEndian(byte[] buffer, int offset, short data) + { +#if BIGENDIAN + buffer[offset + 1] = (byte)(data); + buffer[offset ] = (byte)(data >> 8); +#else + buffer[offset] = (byte)(data); + buffer[offset + 1] = (byte)(data >> 8); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, double value) + { + ConverterHelperDouble ch = new ConverterHelperDouble { Adouble = value }; + WriteLittleEndian(bytes, startIndex, ch.Along); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, float value) + { + ConverterHelperFloat ch = new ConverterHelperFloat { Afloat = value }; + WriteLittleEndian(bytes, startIndex, ch.Aint); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, short value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ushort value) + { + WriteLittleEndian(bytes, startIndex, (short)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, int value) + { + WriteLittleEndian(bytes, startIndex, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, uint value) + { + WriteLittleEndian(bytes, startIndex, (int)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, long value) + { + WriteLittleEndian(bytes, startIndex, (ulong)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(byte[] bytes, int startIndex, ulong value) + { + WriteLittleEndian(bytes, startIndex, value); + } +#endif + } +} diff --git a/NebulaModel/Networking/Serialization/LiteNetLib/NetDataReader.cs b/NebulaModel/Networking/Serialization/LiteNetLib/NetDataReader.cs new file mode 100644 index 000000000..3427b072e --- /dev/null +++ b/NebulaModel/Networking/Serialization/LiteNetLib/NetDataReader.cs @@ -0,0 +1,674 @@ +using System; +using System.Net; +using System.Runtime.CompilerServices; +using NebulaAPI.Interfaces; + +namespace NebulaModel.Networking.Serialization +{ + public class NetDataReader : INetDataReader + { + protected byte[] _data; + protected int _position; + protected int _dataSize; + private int _offset; + + public byte[] RawData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + public int RawDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize; + } + public int UserDataOffset + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _offset; + } + public int UserDataSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _offset; + } + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data == null; + } + public int Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + public bool EndOfData + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position == _dataSize; + } + public int AvailableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dataSize - _position; + } + + public void SkipBytes(int count) + { + _position += count; + } + + public void SetPosition(int position) + { + _position = position; + } + + public void SetSource(INetDataWriter dataWriter) + { + _data = dataWriter.Data; + _position = 0; + _offset = 0; + _dataSize = dataWriter.Length; + } + + public void SetSource(byte[] source) + { + _data = source; + _position = 0; + _offset = 0; + _dataSize = source.Length; + } + + public void SetSource(byte[] source, int offset, int maxSize) + { + _data = source; + _position = offset; + _offset = offset; + _dataSize = maxSize; + } + + public NetDataReader() + { + + } + + public NetDataReader(INetDataWriter writer) + { + SetSource(writer); + } + + public NetDataReader(byte[] source) + { + SetSource(source); + } + + public NetDataReader(byte[] source, int offset, int maxSize) + { + SetSource(source, offset, maxSize); + } + + #region GetMethods + public IPEndPoint GetNetEndPoint() + { + string host = GetString(1000); + int port = GetInt(); + return NetUtils.MakeEndPoint(host, port); + } + + public byte GetByte() + { + byte res = _data[_position]; + _position++; + return res; + } + + public sbyte GetSByte() + { + return (sbyte)GetByte(); + } + + public T[] GetArray(int size) + { + int length = BitConverter.ToInt32(_data, _position); + _position += 4; + T[] result = new T[length]; + length *= size; + Buffer.BlockCopy(_data, _position, result, 0, length); + _position += length; + return result; + } + + public bool[] GetBoolArray() + { + return GetArray(1); + } + + public ushort[] GetUShortArray() + { + return GetArray(2); + } + + public short[] GetShortArray() + { + return GetArray(2); + } + + public int[] GetIntArray() + { + return GetArray(4); + } + + public uint[] GetUIntArray() + { + return GetArray(4); + } + + public float[] GetFloatArray() + { + return GetArray(4); + } + + public double[] GetDoubleArray() + { + return GetArray(8); + } + + public long[] GetLongArray() + { + return GetArray(8); + } + + public ulong[] GetULongArray() + { + return GetArray(8); + } + + public string[] GetStringArray() + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(); + } + return arr; + } + + /// + /// Note that "maxStringLength" only limits the number of characters in a string, not its size in bytes. + /// Strings that exceed this parameter are returned as empty + /// + public string[] GetStringArray(int maxStringLength) + { + ushort length = GetUShort(); + string[] arr = new string[length]; + for (int i = 0; i < length; i++) + { + arr[i] = GetString(maxStringLength); + } + return arr; + } + + public bool GetBool() + { + return GetByte() == 1; + } + + public char GetChar() + { + return (char)GetUShort(); + } + + public ushort GetUShort() + { + ushort result = BitConverter.ToUInt16(_data, _position); + _position += 2; + return result; + } + + public short GetShort() + { + short result = BitConverter.ToInt16(_data, _position); + _position += 2; + return result; + } + + public long GetLong() + { + long result = BitConverter.ToInt64(_data, _position); + _position += 8; + return result; + } + + public ulong GetULong() + { + ulong result = BitConverter.ToUInt64(_data, _position); + _position += 8; + return result; + } + + public int GetInt() + { + int result = BitConverter.ToInt32(_data, _position); + _position += 4; + return result; + } + + public uint GetUInt() + { + uint result = BitConverter.ToUInt32(_data, _position); + _position += 4; + return result; + } + + public float GetFloat() + { + float result = BitConverter.ToSingle(_data, _position); + _position += 4; + return result; + } + + public double GetDouble() + { + double result = BitConverter.ToDouble(_data, _position); + _position += 8; + return result; + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + /// "string.Empty" if value > "maxLength" + public string GetString(int maxLength) + { + ushort size = GetUShort(); + if (size == 0) + { + return string.Empty; + } + + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } + + ArraySegment data = GetBytesSegment(actualSize); + + return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(data.Array, data.Offset, data.Count) > maxLength) ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(data.Array, data.Offset, data.Count); + } + + public string GetString() + { + ushort size = GetUShort(); + if (size == 0) + { + return string.Empty; + } + + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } + + ArraySegment data = GetBytesSegment(actualSize); + + return NetDataWriter.uTF8Encoding.Value.GetString(data.Array, data.Offset, data.Count); + } + + public ArraySegment GetBytesSegment(int count) + { + ArraySegment segment = new ArraySegment(_data, _position, count); + _position += count; + return segment; + } + + public ArraySegment GetRemainingBytesSegment() + { + ArraySegment segment = new ArraySegment(_data, _position, AvailableBytes); + _position = _data.Length; + return segment; + } + + public T Get() where T : struct, INetSerializable + { + var obj = default(T); + obj.Deserialize(this); + return obj; + } + + public T Get(Func constructor) where T : class, INetSerializable + { + var obj = constructor(); + obj.Deserialize(this); + return obj; + } + + public byte[] GetRemainingBytes() + { + byte[] outgoingData = new byte[AvailableBytes]; + Buffer.BlockCopy(_data, _position, outgoingData, 0, AvailableBytes); + _position = _data.Length; + return outgoingData; + } + + public void GetBytes(byte[] destination, int start, int count) + { + Buffer.BlockCopy(_data, _position, destination, start, count); + _position += count; + } + + public void GetBytes(byte[] destination, int count) + { + Buffer.BlockCopy(_data, _position, destination, 0, count); + _position += count; + } + + public sbyte[] GetSBytesWithLength() + { + return GetArray(1); + } + + public byte[] GetBytesWithLength() + { + return GetArray(1); + } + #endregion + + #region PeekMethods + + public byte PeekByte() + { + return _data[_position]; + } + + public sbyte PeekSByte() + { + return (sbyte)_data[_position]; + } + + public bool PeekBool() + { + return _data[_position] == 1; + } + + public char PeekChar() + { + return (char)PeekUShort(); + } + + public ushort PeekUShort() + { + return BitConverter.ToUInt16(_data, _position); + } + + public short PeekShort() + { + return BitConverter.ToInt16(_data, _position); + } + + public long PeekLong() + { + return BitConverter.ToInt64(_data, _position); + } + + public ulong PeekULong() + { + return BitConverter.ToUInt64(_data, _position); + } + + public int PeekInt() + { + return BitConverter.ToInt32(_data, _position); + } + + public uint PeekUInt() + { + return BitConverter.ToUInt32(_data, _position); + } + + public float PeekFloat() + { + return BitConverter.ToSingle(_data, _position); + } + + public double PeekDouble() + { + return BitConverter.ToDouble(_data, _position); + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public string PeekString(int maxLength) + { + ushort size = PeekUShort(); + if (size == 0) + { + return string.Empty; + } + + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } + + return (maxLength > 0 && NetDataWriter.uTF8Encoding.Value.GetCharCount(_data, _position + 2, actualSize) > maxLength) ? + string.Empty : + NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); + } + + public string PeekString() + { + ushort size = PeekUShort(); + if (size == 0) + { + return string.Empty; + } + + int actualSize = size - 1; + if (actualSize >= NetDataWriter.StringBufferMaxLength) + { + return null; + } + + return NetDataWriter.uTF8Encoding.Value.GetString(_data, _position + 2, actualSize); + } + #endregion + + #region TryGetMethods + public bool TryGetByte(out byte result) + { + if (AvailableBytes >= 1) + { + result = GetByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetSByte(out sbyte result) + { + if (AvailableBytes >= 1) + { + result = GetSByte(); + return true; + } + result = 0; + return false; + } + + public bool TryGetBool(out bool result) + { + if (AvailableBytes >= 1) + { + result = GetBool(); + return true; + } + result = false; + return false; + } + + public bool TryGetChar(out char result) + { + if (!TryGetUShort(out ushort uShortValue)) + { + result = '\0'; + return false; + } + result = (char)uShortValue; + return true; + } + + public bool TryGetShort(out short result) + { + if (AvailableBytes >= 2) + { + result = GetShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUShort(out ushort result) + { + if (AvailableBytes >= 2) + { + result = GetUShort(); + return true; + } + result = 0; + return false; + } + + public bool TryGetInt(out int result) + { + if (AvailableBytes >= 4) + { + result = GetInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetUInt(out uint result) + { + if (AvailableBytes >= 4) + { + result = GetUInt(); + return true; + } + result = 0; + return false; + } + + public bool TryGetLong(out long result) + { + if (AvailableBytes >= 8) + { + result = GetLong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetULong(out ulong result) + { + if (AvailableBytes >= 8) + { + result = GetULong(); + return true; + } + result = 0; + return false; + } + + public bool TryGetFloat(out float result) + { + if (AvailableBytes >= 4) + { + result = GetFloat(); + return true; + } + result = 0; + return false; + } + + public bool TryGetDouble(out double result) + { + if (AvailableBytes >= 8) + { + result = GetDouble(); + return true; + } + result = 0; + return false; + } + + public bool TryGetString(out string result) + { + if (AvailableBytes >= 2) + { + ushort strSize = PeekUShort(); + if (AvailableBytes >= strSize + 1) + { + result = GetString(); + return true; + } + } + result = null; + return false; + } + + public bool TryGetStringArray(out string[] result) + { + if (!TryGetUShort(out ushort strArrayLength)) { + result = null; + return false; + } + + result = new string[strArrayLength]; + for (int i = 0; i < strArrayLength; i++) + { + if (!TryGetString(out result[i])) + { + result = null; + return false; + } + } + + return true; + } + + public bool TryGetBytesWithLength(out byte[] result) + { + if (AvailableBytes >= 4) + { + int length = PeekInt(); + if (length >= 0 && AvailableBytes >= 4 + length) + { + result = GetBytesWithLength(); + return true; + } + } + result = null; + return false; + } + #endregion + + public void Clear() + { + _position = 0; + _dataSize = 0; + _data = null; + } + } +} diff --git a/NebulaModel/Networking/Serialization/LiteNetLib/NetDataWriter.cs b/NebulaModel/Networking/Serialization/LiteNetLib/NetDataWriter.cs new file mode 100644 index 000000000..13fb1e67d --- /dev/null +++ b/NebulaModel/Networking/Serialization/LiteNetLib/NetDataWriter.cs @@ -0,0 +1,386 @@ +using System; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using NebulaAPI.Interfaces; + +namespace NebulaModel.Networking.Serialization +{ + public class NetDataWriter : INetDataWriter + { + protected byte[] _data; + protected int _position; + private const int InitialSize = 64; + private readonly bool _autoResize; + + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data.Length; + } + + public byte[] Data + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data; + } + + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _position; + } + + public static readonly ThreadLocal uTF8Encoding = new ThreadLocal(() => new UTF8Encoding(false, true)); + public const int StringBufferMaxLength = 65535; + private readonly byte[] _stringBuffer = new byte[StringBufferMaxLength]; + + public NetDataWriter() : this(true, InitialSize) + { + } + + public NetDataWriter(bool autoResize) : this(autoResize, InitialSize) + { + } + + public NetDataWriter(bool autoResize, int initialSize) + { + _data = new byte[initialSize]; + _autoResize = autoResize; + } + + /// + /// Creates NetDataWriter from existing ByteArray + /// + /// Source byte array + /// Copy array to new location or use existing + public static NetDataWriter FromBytes(byte[] bytes, bool copy) + { + if (copy) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes); + return netDataWriter; + } + return new NetDataWriter(true, 0) {_data = bytes, _position = bytes.Length}; + } + + /// + /// Creates NetDataWriter from existing ByteArray (always copied data) + /// + /// Source byte array + /// Offset of array + /// Length of array + public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) + { + var netDataWriter = new NetDataWriter(true, bytes.Length); + netDataWriter.Put(bytes, offset, length); + return netDataWriter; + } + + public static NetDataWriter FromString(string value) + { + var netDataWriter = new NetDataWriter(); + netDataWriter.Put(value); + return netDataWriter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResizeIfNeed(int newSize) + { + if (_data.Length < newSize) + { + Array.Resize(ref _data, Math.Max(newSize, _data.Length * 2)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureFit(int additionalSize) + { + if (_data.Length < _position + additionalSize) + { + Array.Resize(ref _data, Math.Max(_position + additionalSize, _data.Length * 2)); + } + } + + public void Reset(int size) + { + ResizeIfNeed(size); + _position = 0; + } + + public void Reset() + { + _position = 0; + } + + public byte[] CopyData() + { + byte[] resultData = new byte[_position]; + Buffer.BlockCopy(_data, 0, resultData, 0, _position); + return resultData; + } + + /// + /// Sets position of NetDataWriter to rewrite previous values + /// + /// new byte position + /// previous position of data writer + public int SetPosition(int position) + { + int prevPosition = _position; + _position = position; + return prevPosition; + } + + public void Put(float value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(double value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(long value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(ulong value) + { + if (_autoResize) + ResizeIfNeed(_position + 8); + FastBitConverter.GetBytes(_data, _position, value); + _position += 8; + } + + public void Put(int value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(uint value) + { + if (_autoResize) + ResizeIfNeed(_position + 4); + FastBitConverter.GetBytes(_data, _position, value); + _position += 4; + } + + public void Put(char value) + { + Put((ushort)value); + } + + public void Put(ushort value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(short value) + { + if (_autoResize) + ResizeIfNeed(_position + 2); + FastBitConverter.GetBytes(_data, _position, value); + _position += 2; + } + + public void Put(sbyte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = (byte)value; + _position++; + } + + public void Put(byte value) + { + if (_autoResize) + ResizeIfNeed(_position + 1); + _data[_position] = value; + _position++; + } + + public void Put(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + length); + Buffer.BlockCopy(data, offset, _data, _position, length); + _position += length; + } + + public void Put(byte[] data) + { + if (_autoResize) + ResizeIfNeed(_position + data.Length); + Buffer.BlockCopy(data, 0, _data, _position, data.Length); + _position += data.Length; + } + + public void PutSBytesWithLength(sbyte[] data, int offset, int length) + { + const int intSize = 4; + if (_autoResize) + ResizeIfNeed(_position + intSize + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + intSize, length); + _position += intSize + length; + } + + public void PutSBytesWithLength(sbyte[] data) + { + PutArray(data, 1); + } + + public void PutBytesWithLength(byte[] data, int offset, int length) + { + if (_autoResize) + ResizeIfNeed(_position + 2 + length); + FastBitConverter.GetBytes(_data, _position, length); + Buffer.BlockCopy(data, offset, _data, _position + 2, length); + _position += 2 + length; + } + + public void PutBytesWithLength(byte[] data) + { + PutArray(data, 1); + } + + public void Put(bool value) + { + Put((byte)(value ? 1 : 0)); + } + + public void PutArray(Array arr, int sz) + { + const int intSize = 4; + int length = arr == null ? 0 : arr.Length; + sz *= length; + if (_autoResize) + ResizeIfNeed(_position + sz + intSize); + FastBitConverter.GetBytes(_data, _position, length); + if (arr != null) + Buffer.BlockCopy(arr, 0, _data, _position + intSize, sz); + _position += sz + intSize; + } + + public void PutArray(float[] value) + { + PutArray(value, 4); + } + + public void PutArray(double[] value) + { + PutArray(value, 8); + } + + public void PutArray(long[] value) + { + PutArray(value, 8); + } + + public void PutArray(ulong[] value) + { + PutArray(value, 8); + } + + public void PutArray(int[] value) + { + PutArray(value, 4); + } + + public void PutArray(uint[] value) + { + PutArray(value, 4); + } + + public void PutArray(ushort[] value) + { + PutArray(value, 2); + } + + public void PutArray(short[] value) + { + PutArray(value, 2); + } + + public void PutArray(bool[] value) + { + PutArray(value, 1); + } + + public void PutArray(string[] value) + { + int strArrayLength = value == null ? 0 : value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i]); + } + + public void PutArray(string[] value, int strMaxLength) + { + ushort strArrayLength = value == null ? (ushort)0 : (ushort)value.Length; + Put(strArrayLength); + for (int i = 0; i < strArrayLength; i++) + Put(value[i], strMaxLength); + } + + public void Put(IPEndPoint endPoint) + { + Put(endPoint.Address.ToString()); + Put(endPoint.Port); + } + + public void Put(string value) + { + Put(value, 0); + } + + /// + /// Note that "maxLength" only limits the number of characters in a string, not its size in bytes. + /// + public void Put(string value, int maxLength) + { + if (string.IsNullOrEmpty(value)) + { + Put((ushort)0); + return; + } + + int length = maxLength > 0 && value.Length > maxLength ? maxLength : value.Length; + int size = uTF8Encoding.Value.GetBytes(value, 0, length, _stringBuffer, 0); + + if (size == 0 || size >= StringBufferMaxLength) + { + Put((ushort)0); + return; + } + + Put(checked((ushort)(size + 1))); + Put(_stringBuffer, 0, size); + } + + public void Put(T obj) where T : INetSerializable + { + obj.Serialize(this); + } + } +} diff --git a/NebulaModel/Networking/Serialization/LiteNetLib/NetPacketProcessor.cs b/NebulaModel/Networking/Serialization/LiteNetLib/NetPacketProcessor.cs new file mode 100644 index 000000000..b1875cd0a --- /dev/null +++ b/NebulaModel/Networking/Serialization/LiteNetLib/NetPacketProcessor.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using NebulaAPI.Interfaces; +using NebulaAPI.Packets; +using NebulaModel.Logger; + +namespace NebulaModel.Networking.Serialization +{ + public class NetPacketProcessor + { + private static class HashCache + { + public static readonly ulong Id; + + //FNV-1 64 bit hash + static HashCache() + { + ulong hash = 14695981039346656037UL; //offset + string typeName = typeof(T).ToString(); + for (var i = 0; i < typeName.Length; i++) + { + hash ^= typeName[i]; + hash *= 1099511628211UL; //prime + } + Id = hash; + } + } + + protected delegate void SubscribeDelegate(NetDataReader reader, object userData); + + protected NetSerializer _netSerializer; + protected readonly Dictionary _callbacks = []; + private readonly Dictionary _callbacksDebugInfo = []; + + public NetPacketProcessor() + { + _netSerializer = new NetSerializer(); + } + + public NetPacketProcessor(int maxStringLength) + { + _netSerializer = new NetSerializer(maxStringLength); + } + + protected virtual ulong GetHash() + { + return HashCache.Id; + } + + protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) + { + ulong hash = reader.GetULong(); + if (!_callbacks.TryGetValue(hash, out var action)) + { + Log.Warn($"Unknown packet hash: {hash}"); + throw new ParseException("Undefined packet in NetDataReader"); + } +#if DEBUG + if (_callbacksDebugInfo.TryGetValue(hash, out var packetType)) + { + if (!packetType.IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) + { + Log.Debug($"Packet Recv >> {packetType.Name}, Size: {reader.UserDataSize}"); + } + } + else + { + Log.Debug($"Packet Recv >> Unregistered hash {hash}, Size: {reader.UserDataSize}"); + } +#endif + return action; + } + + protected virtual void WriteHash(NetDataWriter writer) + { + writer.Put(GetHash()); + } + + /// + /// Register nested property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable + { + _netSerializer.RegisterNestedType(); + } + + /// + /// Register nested property type + /// + /// + /// + public void RegisterNestedType(Action writeDelegate, Func readDelegate) + { + _netSerializer.RegisterNestedType(writeDelegate, readDelegate); + } + + /// + /// Register nested property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable + { + _netSerializer.RegisterNestedType(constructor); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + public void ReadAllPackets(NetDataReader reader) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader); + } + + /// + /// Reads all available data from NetDataReader and calls OnReceive delegates + /// + /// NetDataReader with packets data + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadAllPackets(NetDataReader reader, object userData) + { + while (reader.AvailableBytes > 0) + ReadPacket(reader, userData); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Malformed packet + public void ReadPacket(NetDataReader reader) + { + ReadPacket(reader, null); + } + + public void Write(NetDataWriter writer, T packet) where T : class, new() + { + WriteHash(writer); + _netSerializer.Serialize(writer, packet); + } + + public void WriteNetSerializable(NetDataWriter writer, ref T packet) where T : INetSerializable + { + WriteHash(writer); + packet.Serialize(writer); + } + + /// + /// Reads one packet from NetDataReader and calls OnReceive delegate + /// + /// NetDataReader with packet + /// Argument that passed to OnReceivedEvent + /// Malformed packet + public void ReadPacket(NetDataReader reader, object userData) + { + GetCallbackFromData(reader)(reader, userData); + } + + /// + /// Register and subscribe to packet receive event + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + /// + /// Register and subscribe to packet receive event (with userData) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// Method that constructs packet instead of slow Activator.CreateInstance + /// 's fields are not supported, or it has no fields + public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() + { + _netSerializer.Register(); + _callbacks[GetHash()] = (reader, userData) => + { + var reference = packetConstructor(); + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + /// + /// Register and subscribe to packet receive event + /// This method will overwrite last received packet class on receive (less garbage) + /// + /// event that will be called when packet deserialized with ReadPacket method + /// 's fields are not supported, or it has no fields + public void SubscribeReusable(Action onReceive) where T : class, new() + { + _netSerializer.Register(); + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + _netSerializer.Deserialize(reader, reference); + onReceive(reference, (TUserData)userData); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt, (TUserData)userData); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + public void SubscribeNetSerializable( + Action onReceive, + Func packetConstructor) where T : INetSerializable + { + _callbacks[GetHash()] = (reader, userData) => + { + var pkt = packetConstructor(); + pkt.Deserialize(reader); + onReceive(pkt); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference, (TUserData)userData); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + public void SubscribeNetSerializable( + Action onReceive) where T : INetSerializable, new() + { + var reference = new T(); + _callbacks[GetHash()] = (reader, userData) => + { + reference.Deserialize(reader); + onReceive(reference); + }; + _callbacksDebugInfo[GetHash()] = typeof(T); + } + + /// + /// Remove any subscriptions by type + /// + /// Packet type + /// true if remove is success + public bool RemoveSubscription() + { + _callbacksDebugInfo.Remove(GetHash()); + return _callbacks.Remove(GetHash()); + } + } +} diff --git a/NebulaModel/Networking/Serialization/LiteNetLib/NetSerializer.cs b/NebulaModel/Networking/Serialization/LiteNetLib/NetSerializer.cs new file mode 100644 index 000000000..946fe044d --- /dev/null +++ b/NebulaModel/Networking/Serialization/LiteNetLib/NetSerializer.cs @@ -0,0 +1,739 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Runtime.Serialization; +using NebulaAPI.Interfaces; + +namespace NebulaModel.Networking.Serialization +{ + public class InvalidTypeException : ArgumentException + { + public InvalidTypeException(string message) : base(message) { } + } + + public class ParseException : Exception + { + public ParseException(string message) : base(message) { } + } + + public class NetSerializer + { + public enum CallType + { + Basic, + Array, + List + } + + public abstract class FastCall + { + public CallType Type; + public virtual void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) { Type = type; } + public abstract void Read(T inf, NetDataReader r); + public abstract void Write(T inf, NetDataWriter w); + public abstract void ReadArray(T inf, NetDataReader r); + public abstract void WriteArray(T inf, NetDataWriter w); + public abstract void ReadList(T inf, NetDataReader r); + public abstract void WriteList(T inf, NetDataWriter w); + } + + protected abstract class FastCallSpecific : FastCall + { + protected Func Getter; + protected Action Setter; + protected Func GetterArr; + protected Action SetterArr; + protected Func> GetterList; + protected Action> SetterList; + + public override void ReadArray(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void WriteArray(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); } + public override void ReadList(TClass inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } + public override void WriteList(TClass inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); } + + protected TProperty[] ReadArrayHelper(TClass inf, NetDataReader r) + { + ushort count = r.GetUShort(); + var arr = GetterArr(inf); + arr = arr == null || arr.Length != count ? new TProperty[count] : arr; + SetterArr(inf, arr); + return arr; + } + + protected TProperty[] WriteArrayHelper(TClass inf, NetDataWriter w) + { + var arr = GetterArr(inf); + w.Put((ushort)arr.Length); + return arr; + } + + protected List ReadListHelper(TClass inf, NetDataReader r, out int len) + { + len = r.GetUShort(); + var list = GetterList(inf); + if (list == null) + { + list = new List(len); + SetterList(inf, list); + } + return list; + } + + protected List WriteListHelper(TClass inf, NetDataWriter w, out int len) + { + var list = GetterList(inf); + if (list == null) + { + len = 0; + w.Put(0); + return null; + } + len = list.Count; + w.Put((ushort)len); + return list; + } + + public override void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) + { + base.Init(getMethod, setMethod, type); + switch (type) + { + case CallType.Array: + GetterArr = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + SetterArr = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; + case CallType.List: + GetterList = (Func>)Delegate.CreateDelegate(typeof(Func>), getMethod); + SetterList = (Action>)Delegate.CreateDelegate(typeof(Action>), setMethod); + break; + default: + Getter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); + Setter = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); + break; + } + } + } + + protected abstract class FastCallSpecificAuto : FastCallSpecific + { + protected abstract void ElementRead(NetDataReader r, out TProperty prop); + protected abstract void ElementWrite(NetDataWriter w, ref TProperty prop); + + public override void Read(TClass inf, NetDataReader r) + { + ElementRead(r, out var elem); + Setter(inf, elem); + } + + public override void Write(TClass inf, NetDataWriter w) + { + var elem = Getter(inf); + ElementWrite(w, ref elem); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + for (int i = 0; i < arr.Length; i++) + ElementRead(r, out arr[i]); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + for (int i = 0; i < arr.Length; i++) + ElementWrite(w, ref arr[i]); + } + } + + private sealed class FastCallStatic : FastCallSpecific + { + private readonly Action _writer; + private readonly Func _reader; + + public FastCallStatic(Action write, Func read) + { + _writer = write; + _reader = read; + } + + public override void Read(TClass inf, NetDataReader r) { Setter(inf, _reader(r)); } + public override void Write(TClass inf, NetDataWriter w) { _writer(w, Getter(inf)); } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + if (i < listCount) + list[i] = _reader(r); + else + list.Add(_reader(r)); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + _writer(w, list[i]); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i] = _reader(r); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + _writer(w, arr[i]); + } + } + + private sealed class FastCallStruct : FastCallSpecific where TProperty : struct, INetSerializable + { + private TProperty _p; + + public override void Read(TClass inf, NetDataReader r) + { + _p.Deserialize(r); + Setter(inf, _p); + } + + public override void Write(TClass inf, NetDataWriter w) + { + _p = Getter(inf); + _p.Serialize(w); + } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + var itm = default(TProperty); + itm.Deserialize(r); + if(i < listCount) + list[i] = itm; + else + list.Add(itm); + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Deserialize(r); + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); + } + } + + private sealed class FastCallClass : FastCallSpecific where TProperty : class, INetSerializable + { + private readonly Func _constructor; + public FastCallClass(Func constructor) { _constructor = constructor; } + + public override void Read(TClass inf, NetDataReader r) + { + var p = _constructor(); + p.Deserialize(r); + Setter(inf, p); + } + + public override void Write(TClass inf, NetDataWriter w) + { + var p = Getter(inf); + p?.Serialize(w); + } + + public override void ReadList(TClass inf, NetDataReader r) + { + var list = ReadListHelper(inf, r, out int len); + int listCount = list.Count; + for (int i = 0; i < len; i++) + { + if (i < listCount) + { + list[i].Deserialize(r); + } + else + { + var itm = _constructor(); + itm.Deserialize(r); + list.Add(itm); + } + } + if (len < listCount) + list.RemoveRange(len, listCount - len); + } + + public override void WriteList(TClass inf, NetDataWriter w) + { + var list = WriteListHelper(inf, w, out int len); + for (int i = 0; i < len; i++) + list[i].Serialize(w); + } + + public override void ReadArray(TClass inf, NetDataReader r) + { + var arr = ReadArrayHelper(inf, r); + int len = arr.Length; + for (int i = 0; i < len; i++) + { + arr[i] = _constructor(); + arr[i].Deserialize(r); + } + } + + public override void WriteArray(TClass inf, NetDataWriter w) + { + var arr = WriteArrayHelper(inf, w); + int len = arr.Length; + for (int i = 0; i < len; i++) + arr[i].Serialize(w); + } + } + + protected class IntSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class UIntSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUInt()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUIntArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class ShortSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class UShortSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUShort()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUShortArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class LongSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetLong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetLongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class ULongSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetULong()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetULongArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class ByteSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutBytesWithLength(GetterArr(inf)); } + } + + protected class SByteSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetSByte()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetSBytesWithLength()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutSBytesWithLength(GetterArr(inf)); } + } + + protected class FloatSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetFloat()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetFloatArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class DoubleSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetDouble()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetDoubleArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class BoolSerializer : FastCallSpecific + { + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetBool()); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBoolArray()); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } + } + + protected class CharSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref char prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out char prop) { prop = r.GetChar(); } + } + + protected class IPEndPointSerializer : FastCallSpecificAuto + { + protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } + protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } + } + + protected class StringSerializer : FastCallSpecific + { + private readonly int _maxLength; + public StringSerializer(int maxLength) { _maxLength = maxLength > 0 ? maxLength : short.MaxValue; } + public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetString(_maxLength)); } + public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf), _maxLength); } + public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetStringArray(_maxLength)); } + public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf), _maxLength); } + } + + protected class EnumByteSerializer : FastCall + { + protected readonly PropertyInfo Property; + protected readonly Type PropertyType; + public EnumByteSerializer(PropertyInfo property, Type propertyType) + { + Property = property; + PropertyType = propertyType; + } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetByte()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((byte)Property.GetValue(inf, null)); } + public override void ReadArray(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void WriteArray(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: Enum[]"); } + public override void ReadList(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: List"); } + public override void WriteList(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: List"); } + } + + protected class EnumIntSerializer : EnumByteSerializer + { + public EnumIntSerializer(PropertyInfo property, Type propertyType) : base(property, propertyType) { } + public override void Read(T inf, NetDataReader r) { Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetInt()), null); } + public override void Write(T inf, NetDataWriter w) { w.Put((int)Property.GetValue(inf, null)); } + } + + public sealed class ClassInfo + { + public static ClassInfo Instance; + private readonly FastCall[] _serializers; + private readonly int _membersCount; + + public ClassInfo(List> serializers) + { + _membersCount = serializers.Count; + _serializers = serializers.ToArray(); + } + + public void Write(T obj, NetDataWriter writer) + { + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Write(obj, writer); + else if (s.Type == CallType.Array) + s.WriteArray(obj, writer); + else + s.WriteList(obj, writer); + } + } + + public void Read(T obj, NetDataReader reader) + { + for (int i = 0; i < _membersCount; i++) + { + var s = _serializers[i]; + if (s.Type == CallType.Basic) + s.Read(obj, reader); + else if(s.Type == CallType.Array) + s.ReadArray(obj, reader); + else + s.ReadList(obj, reader); + } + } + } + + protected abstract class CustomType + { + public abstract FastCall Get(); + } + + private sealed class CustomTypeStruct : CustomType where TProperty : struct, INetSerializable + { + public override FastCall Get() { return new FastCallStruct(); } + } + + private sealed class CustomTypeClass : CustomType where TProperty : class, INetSerializable + { + private readonly Func _constructor; + public CustomTypeClass(Func constructor) { _constructor = constructor; } + public override FastCall Get() { return new FastCallClass(_constructor); } + } + + private sealed class CustomTypeStatic : CustomType + { + private readonly Action _writer; + private readonly Func _reader; + public CustomTypeStatic(Action writer, Func reader) + { + _writer = writer; + _reader = reader; + } + public override FastCall Get() { return new FastCallStatic(_writer, _reader); } + } + + /// + /// Register custom property type + /// + /// INetSerializable structure + public void RegisterNestedType() where T : struct, INetSerializable + { + _registeredTypes.Add(typeof(T), new CustomTypeStruct()); + } + + /// + /// Register custom property type + /// + /// INetSerializable class + public void RegisterNestedType(Func constructor) where T : class, INetSerializable + { + _registeredTypes.Add(typeof(T), new CustomTypeClass(constructor)); + } + + /// + /// Register custom property type + /// + /// Any packet + /// custom type writer + /// custom type reader + public void RegisterNestedType(Action writer, Func reader) + { + _registeredTypes.Add(typeof(T), new CustomTypeStatic(writer, reader)); + } + + private NetDataWriter _writer; + protected readonly int _maxStringLength; + protected readonly Dictionary _registeredTypes = new Dictionary(); + + public NetSerializer() : this(0) + { + } + + public NetSerializer(int maxStringLength) + { + _maxStringLength = maxStringLength; + } + + protected virtual ClassInfo RegisterInternal() + { + if (ClassInfo.Instance != null) + return ClassInfo.Instance; + + Type t = typeof(T); + var props = t.GetProperties( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.GetProperty | + BindingFlags.SetProperty); + var serializers = new List>(); + for (int i = 0; i < props.Length; i++) + { + var property = props[i]; + var propertyType = property.PropertyType; + + var elementType = propertyType.IsArray ? propertyType.GetElementType() : propertyType; + var callType = propertyType.IsArray ? CallType.Array : CallType.Basic; + + if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)) + { + elementType = propertyType.GetGenericArguments()[0]; + callType = CallType.List; + } + + if (Attribute.IsDefined(property, typeof(IgnoreDataMemberAttribute))) + continue; + + var getMethod = property.GetGetMethod(); + var setMethod = property.GetSetMethod(); + if (getMethod == null || setMethod == null) + continue; + + FastCall serialzer = null; + if (propertyType.IsEnum) + { + var underlyingType = Enum.GetUnderlyingType(propertyType); + if (underlyingType == typeof(byte)) + serialzer = new EnumByteSerializer(property, propertyType); + else if (underlyingType == typeof(int)) + serialzer = new EnumIntSerializer(property, propertyType); + else + throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); + } + else if (elementType == typeof(string)) + serialzer = new StringSerializer(_maxStringLength); + else if (elementType == typeof(bool)) + serialzer = new BoolSerializer(); + else if (elementType == typeof(byte)) + serialzer = new ByteSerializer(); + else if (elementType == typeof(sbyte)) + serialzer = new SByteSerializer(); + else if (elementType == typeof(short)) + serialzer = new ShortSerializer(); + else if (elementType == typeof(ushort)) + serialzer = new UShortSerializer(); + else if (elementType == typeof(int)) + serialzer = new IntSerializer(); + else if (elementType == typeof(uint)) + serialzer = new UIntSerializer(); + else if (elementType == typeof(long)) + serialzer = new LongSerializer(); + else if (elementType == typeof(ulong)) + serialzer = new ULongSerializer(); + else if (elementType == typeof(float)) + serialzer = new FloatSerializer(); + else if (elementType == typeof(double)) + serialzer = new DoubleSerializer(); + else if (elementType == typeof(char)) + serialzer = new CharSerializer(); + else if (elementType == typeof(IPEndPoint)) + serialzer = new IPEndPointSerializer(); + else + { + _registeredTypes.TryGetValue(elementType, out var customType); + if (customType != null) + serialzer = customType.Get(); + } + + if (serialzer != null) + { + serialzer.Init(getMethod, setMethod, callType); + serializers.Add(serialzer); + } + else + { + throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); + } + } + ClassInfo.Instance = new ClassInfo(serializers); + return ClassInfo.Instance; + } + + /// 's fields are not supported, or it has no fields + public virtual void Register() + { + RegisterInternal(); + } + + /// + /// Reads packet with known type + /// + /// NetDataReader with packet + /// Returns packet if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public T Deserialize(NetDataReader reader) where T : class, new() + { + var info = RegisterInternal(); + var result = new T(); + try + { + info.Read(result, reader); + } + catch + { + return null; + } + return result; + } + + /// + /// Reads packet with known type (non alloc variant) + /// + /// NetDataReader with packet + /// Deserialization target + /// Returns true if packet in reader is matched type + /// 's fields are not supported, or it has no fields + public bool Deserialize(NetDataReader reader, T target) where T : class, new() + { + var info = RegisterInternal(); + try + { + info.Read(target, reader); + } + catch + { + return false; + } + return true; + } + + /// + /// Serialize object to NetDataWriter (fast) + /// + /// Serialization target NetDataWriter + /// Object to serialize + /// 's fields are not supported, or it has no fields + public void Serialize(NetDataWriter writer, T obj) where T : class, new() + { + RegisterInternal().Write(obj, writer); + } + + /// + /// Serialize object to byte array + /// + /// Object to serialize + /// byte array with serialized data + public byte[] Serialize(T obj) where T : class, new() + { + if (_writer == null) + _writer = new NetDataWriter(); + _writer.Reset(); + Serialize(_writer, obj); + return _writer.CopyData(); + } + } +} diff --git a/NebulaModel/Networking/Serialization/NebulaNetPacketProcessor.cs b/NebulaModel/Networking/Serialization/NebulaNetPacketProcessor.cs new file mode 100644 index 000000000..cc3f3b27a --- /dev/null +++ b/NebulaModel/Networking/Serialization/NebulaNetPacketProcessor.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using NebulaAPI.Interfaces; +using NebulaAPI.Packets; +using NebulaModel.Logger; + +namespace NebulaModel.Networking.Serialization; + +public class NebulaNetPacketProcessor : NetPacketProcessor +{ + // Packet simulation stuff + private readonly Dictionary _callbacksDebugInfo = []; + private readonly NetDataWriter writer = new(); + private readonly List delayedPackets = []; + private readonly Queue pendingPackets = new(); + + private readonly Random simulationRandom = new(); + private readonly int SimulatedMaxLatency = 50; + private readonly int SimulatedMinLatency = 20; + + public bool SimulateLatency { get; set; } = false; + + /// + /// Whether or not packet processing is enabled + /// + public bool Enable { get; set; } = true; + + public NebulaNetPacketProcessor() + { + _netSerializer = new NebulaNetSerializer(); + } + + /// + /// Adds back some functionality that nebula relied on before the update. + /// This method was removed from LiteNetLib as it was not thread-safe, and is still not thread safe in below implementation. + /// @TODO: Optimize & move into `NebulaConnection.cs` + /// + public byte[] Write(T packet) where T : class, new() + { + writer.Reset(); + Write(writer, packet); + +#if DEBUG + if (!typeof(T).IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) + { + Log.Debug($"Packet Sent << {packet.GetType().Name}, Size: {writer.Length}"); + } +#endif + + return writer.CopyData(); + } + + #region DEBUG_PACKET_DELAY + + public void ProcessPacketQueue() + { + lock (pendingPackets) + { + ProcessDelayedPackets(); + + while (pendingPackets.Count > 0 && Enable) + { + var packet = pendingPackets.Dequeue(); + ReadPacket(new NetDataReader(packet.Data), packet.UserData); + } + } + } + + [Conditional("DEBUG")] + private void ProcessDelayedPackets() + { + lock (delayedPackets) + { + var now = DateTime.UtcNow; + var deleteCount = 0; + + for (var i = 0; i < delayedPackets.Count; ++i) + { + if (now >= delayedPackets[i].DueTime) + { + pendingPackets.Enqueue(delayedPackets[i].Packet); + deleteCount = i + 1; + } + else + { + // We need to break to avoid messing up the order of the packets. + break; + } + } + + if (deleteCount > 0) + { + delayedPackets.RemoveRange(0, deleteCount); + } + } + } + + public void EnqueuePacketForProcessing(byte[] rawData, object userData) + { +#if DEBUG + if (SimulateLatency) + { + lock (delayedPackets) + { + var packet = new PendingPacket(rawData, userData); + var dueTime = DateTime.UtcNow.AddMilliseconds(simulationRandom.Next(SimulatedMinLatency, SimulatedMaxLatency)); + delayedPackets.Add(new DelayedPacket(packet, dueTime)); + return; + } + } +#endif + lock (pendingPackets) + { + pendingPackets.Enqueue(new PendingPacket(rawData, userData)); + Log.Debug($"Received packet of size: {rawData.Length}"); + } + } + + #endregion +} diff --git a/NebulaModel/Networking/Serialization/NebulaNetSerializer.cs b/NebulaModel/Networking/Serialization/NebulaNetSerializer.cs new file mode 100644 index 000000000..1e980c8c5 --- /dev/null +++ b/NebulaModel/Networking/Serialization/NebulaNetSerializer.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Net; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using System.Runtime.Serialization; +using NebulaAPI.Interfaces; + + +namespace NebulaModel.Networking.Serialization; + +public class NebulaNetSerializer : NetSerializer +{ + public override void Register() + { + RegisterInternal(); + } + + protected override ClassInfo RegisterInternal() + { + if (ClassInfo.Instance != null) + return ClassInfo.Instance; + + // if type has dictionary + // Generate the reader and writer for the dictionary + // call RegisterNestedType on dictionary type + // then call base.RegisterInternal() + + Type type = typeof(T); + var props = type.GetProperties( + BindingFlags.Instance | + BindingFlags.Public | + // BindingFlags.NonPublic | + BindingFlags.GetProperty | + BindingFlags.SetProperty); + + foreach (var prop in props) + { + // Not using the interface as that's implementing by other types that don't necessarily provide the same functionality, or may be thread unsafe. + if (!prop.PropertyType.IsGenericType) + { + continue; + } + + if (prop.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + var genericArguments = prop.PropertyType.GenericTypeArguments; + + // call RegisterDictionary>() + // on the dictionary to generate a serializer for it. + typeof(NebulaNetSerializer) + .GetMethod(nameof(NebulaNetSerializer.RegisterDictionary), BindingFlags.NonPublic | BindingFlags.Instance)! + .MakeGenericMethod([prop.PropertyType, genericArguments[0], genericArguments[1]]) + .Invoke(this, null); + } + } + + return base.RegisterInternal(); + } + + ///This can potentially be moved to its own class, + /// and be registered through an attribute, when/if we'd like to add more custom containers. + protected void RegisterDictionary() where TDict : Dictionary where TKey : new() where TValue : new() + { + var readKeyDelegate = GetTypeGetter(); + var writeKeyDelegate = GetTypePutter(); + + var readValueDelegate = GetTypeGetter(); + var writeValueDelegate = GetTypePutter(); + + // Logic for writing a dictionary + Action writerDelegate = (writer, dict) => + { + writer.Put(dict.Count); + foreach (var kvp in dict) + { + writeKeyDelegate(writer, kvp.Key); + writeValueDelegate(writer, kvp.Value); + } + }; + + // Logic for reading a dictionary + Func readerDelegate = (reader) => + { + var dict = new Dictionary(); + + int count = reader.GetInt(); + + for (int i = 0; i < count; i++) + { + // Could arguably make this also use the library's fastcall but that's too much boilerplate for now. + var key = readKeyDelegate(reader); + var value = readValueDelegate(reader); + + dict.Add(key, value); + } + + return (TDict)dict; + }; + + RegisterNestedType(writerDelegate, readerDelegate); + } + + protected Func GetTypeGetter() where TValue : new() + { + var valueType = typeof(TValue); + + // INetSerializable implementers are treated as valueTypes in this case, since they get their own direct Get or Put call. + if (valueType.GetInterface(nameof(INetSerializable)) != null) + return GetNetSerializableTypeGetter(); + + if (valueType.IsValueType) + return GetValueTypeGetter(); + + if (valueType.IsArray && valueType.GetElementType()!.IsValueType) + return GetValueTypeGetter(); + + // Otherwise if it's a custom type, get a reader for that. + return GetCustomTypeGetter(); + } + + private Func GetNetSerializableTypeGetter() where TValue : new() + { + var valueType = typeof(TValue); + var readerType = typeof(NetDataReader); + + var getterMethods = readerType.GetMethods().Where(info => info.Name == "Get"); + MethodInfo getterMethod; + if (valueType.IsClass) + getterMethod = getterMethods.FirstOrDefault(info => info.GetParameters().Length == 1)?.MakeGenericMethod(valueType); + else + getterMethod = getterMethods.FirstOrDefault(info => info.GetParameters().Length == 0)?.MakeGenericMethod(valueType); + + if (getterMethod == null) + throw new NullReferenceException($"Could not find a valid 'get' serializer for type {valueType.Name}"); + + var instanceParam = Expression.Parameter(readerType, "reader"); + var constructorParam = Expression.Parameter(typeof(Func), "constructor"); + + // Class type call, you have to pass a constructor delegate to this one + MethodCallExpression getCall; + if (valueType.IsClass) + { + var constructorDelegate = () => new TValue(); + getCall = Expression.Call(instanceParam, getterMethod, constructorParam); + var lambda = Expression.Lambda, TValue>>( + getCall, [instanceParam, constructorParam]).Compile(); + + // Just calling the constructor version in an internal lambda so the calling code doesn't have to worry about it. + return (reader) => lambda(reader, constructorDelegate); + } + + // non-class call + getCall = Expression.Call(instanceParam, getterMethod!); + return Expression.Lambda>(getCall, [instanceParam]).Compile(); + } + + protected Func GetCustomTypeGetter() where TValue : new() + { + // If it's not a value type, get the serializer's fastcall instance for that special type + var fastCallReader = RegisterInternal().Read; + + // // Our read function + return (reader) => + { + TValue value = new(); + fastCallReader(value, reader); + return value; + }; + } + + protected Func GetValueTypeGetter() + { + var valueType = typeof(TValue); + var readerType = typeof(NetDataReader); + var getterMethods = readerType + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(info => info.Name.StartsWith("Get")); + + var getterMethod = getterMethods?.FirstOrDefault(info => info.ReturnType == valueType); + + if (getterMethod == null) + throw new NullReferenceException($"Could not find a valid 'get' serializer for type {valueType.Name}"); + + // You can view what ExpressionTrees generate by breaking onto them. + var instanceParam = Expression.Parameter(readerType, "reader"); + var call = Expression.Call(instanceParam, getterMethod!); + return Expression.Lambda>(call, instanceParam).Compile(); + } + + protected Action GetTypePutter() + { + var valueType = typeof(TValue); + + // INetSerializable implementers are treated as valueTypes in this case, since they get their own direct Put call. + if (valueType.GetInterface(nameof(INetSerializable)) != null) + return GetValueTypePutter(); + + if (valueType.IsValueType) + return GetValueTypePutter(); + + if (valueType.IsArray && valueType.GetElementType()!.IsValueType) + return GetValueTypePutter(); + + // Otherwise if it's a custom type, get a reader for that. + return GetCustomTypePutter(); + } + + private Action GetCustomTypePutter() + { + var fastCallWriter = RegisterInternal().Write; + + // // Our read function + return (writer, value) => + { + fastCallWriter(value, writer); + }; + } + + protected Action GetValueTypePutter() + { + var valueType = typeof(TValue); + var writerType = typeof(NetDataWriter); + var putterMethods = writerType + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(info => info.Name.StartsWith("Put")).ToArray(); + + var putterMethod = (MethodInfo)Type.DefaultBinder!.SelectMethod(BindingFlags.Default, putterMethods, [valueType], null); + + // If we don't find a direct put, try the generic one + if (putterMethod == null) + putterMethod = putterMethods.FirstOrDefault(info => info.IsGenericMethod)?.MakeGenericMethod([valueType]); + + if (putterMethod == null) + throw new NullReferenceException($"Could not find a valid 'put' serializer for type {valueType.Name}"); + + // You can view what ExpressionTrees generate by breaking onto them. + var instanceParam = Expression.Parameter(writerType, "reader"); + var valueParam = Expression.Parameter(valueType, "value"); + var call = Expression.Call(instanceParam, putterMethod, [valueParam]); + return Expression.Lambda>(call, instanceParam, valueParam).Compile(); + } +} diff --git a/NebulaModel/Networking/Serialization/NetDataReader.cs b/NebulaModel/Networking/Serialization/NetDataReader.cs deleted file mode 100644 index 3d0af0d39..000000000 --- a/NebulaModel/Networking/Serialization/NetDataReader.cs +++ /dev/null @@ -1,672 +0,0 @@ -#region - -using System; -using System.Net; -using System.Text; -using NebulaAPI.Interfaces; - -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class NetDataReader : INetDataReader -{ - public NetDataReader() - { - } - - public NetDataReader(NetDataWriter writer) - { - SetSource(writer); - } - - public NetDataReader(byte[] source) - { - SetSource(source); - } - - public NetDataReader(byte[] source, int offset) - { - SetSource(source, offset); - } - - public NetDataReader(byte[] source, int offset, int maxSize) - { - SetSource(source, offset, maxSize); - } - - private byte[] RawData { get; set; } - - private int RawDataSize { get; set; } - - private int UserDataOffset { get; set; } - - public int UserDataSize => RawDataSize - UserDataOffset; - - public bool IsNull => RawData == null; - - private int Position { get; set; } - - public bool EndOfData => Position == RawDataSize; - - public int AvailableBytes => RawDataSize - Position; - - public void SkipBytes(int count) - { - Position += count; - } - - private void SetSource(NetDataWriter dataWriter) - { - RawData = dataWriter.Data; - Position = 0; - UserDataOffset = 0; - RawDataSize = dataWriter.Length; - } - - private void SetSource(byte[] source) - { - RawData = source; - Position = 0; - UserDataOffset = 0; - RawDataSize = source.Length; - } - - private void SetSource(byte[] source, int offset) - { - RawData = source; - Position = offset; - UserDataOffset = offset; - RawDataSize = source.Length; - } - - private void SetSource(byte[] source, int offset, int maxSize) - { - RawData = source; - Position = offset; - UserDataOffset = offset; - RawDataSize = maxSize; - } - - public void Clear() - { - Position = 0; - RawDataSize = 0; - RawData = null; - } - - #region GetMethods - - public IPEndPoint GetNetEndPoint() - { - var host = GetString(1000); - var port = GetInt(); - return NetUtils.MakeEndPoint(host, port); - } - - public byte GetByte() - { - var res = RawData[Position]; - Position += 1; - return res; - } - - public sbyte GetSByte() - { - var b = (sbyte)RawData[Position]; - Position++; - return b; - } - - public bool[] GetBoolArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new bool[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size); - Position += size; - return arr; - } - - public ushort[] GetUShortArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new ushort[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 2); - Position += size * 2; - return arr; - } - - public short[] GetShortArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new short[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 2); - Position += size * 2; - return arr; - } - - public long[] GetLongArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new long[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } - - public ulong[] GetULongArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new ulong[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } - - public int[] GetIntArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new int[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } - - public uint[] GetUIntArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new uint[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } - - public float[] GetFloatArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new float[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 4); - Position += size * 4; - return arr; - } - - public double[] GetDoubleArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new double[size]; - Buffer.BlockCopy(RawData, Position, arr, 0, size * 8); - Position += size * 8; - return arr; - } - - public string[] GetStringArray() - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new string[size]; - for (var i = 0; i < size; i++) - { - arr[i] = GetString(); - } - return arr; - } - - public string[] GetStringArray(int maxStringLength) - { - var size = BitConverter.ToUInt16(RawData, Position); - Position += 2; - var arr = new string[size]; - for (var i = 0; i < size; i++) - { - arr[i] = GetString(maxStringLength); - } - return arr; - } - - public bool GetBool() - { - var res = RawData[Position] > 0; - Position += 1; - return res; - } - - public char GetChar() - { - var result = BitConverter.ToChar(RawData, Position); - Position += 2; - return result; - } - - public ushort GetUShort() - { - var result = BitConverter.ToUInt16(RawData, Position); - Position += 2; - return result; - } - - public short GetShort() - { - var result = BitConverter.ToInt16(RawData, Position); - Position += 2; - return result; - } - - public long GetLong() - { - var result = BitConverter.ToInt64(RawData, Position); - Position += 8; - return result; - } - - public ulong GetULong() - { - var result = BitConverter.ToUInt64(RawData, Position); - Position += 8; - return result; - } - - public int GetInt() - { - var result = BitConverter.ToInt32(RawData, Position); - Position += 4; - return result; - } - - public uint GetUInt() - { - var result = BitConverter.ToUInt32(RawData, Position); - Position += 4; - return result; - } - - public float GetFloat() - { - var result = BitConverter.ToSingle(RawData, Position); - Position += 4; - return result; - } - - public double GetDouble() - { - var result = BitConverter.ToDouble(RawData, Position); - Position += 8; - return result; - } - - public string GetString(int maxLength) - { - var bytesCount = GetInt(); - if (bytesCount <= 0 || bytesCount > maxLength * 2) - { - return string.Empty; - } - - var charCount = Encoding.UTF8.GetCharCount(RawData, Position, bytesCount); - if (charCount > maxLength) - { - return string.Empty; - } - - var result = Encoding.UTF8.GetString(RawData, Position, bytesCount); - Position += bytesCount; - return result; - } - - public string GetString() - { - var bytesCount = GetInt(); - if (bytesCount <= 0) - { - return string.Empty; - } - - var result = Encoding.UTF8.GetString(RawData, Position, bytesCount); - Position += bytesCount; - return result; - } - - public ArraySegment GetRemainingBytesSegment() - { - var segment = new ArraySegment(RawData, Position, AvailableBytes); - Position = RawData.Length; - return segment; - } - - public T Get() where T : INetSerializable, new() - { - var obj = new T(); - obj.Deserialize(this); - return obj; - } - - public byte[] GetRemainingBytes() - { - var outgoingData = new byte[AvailableBytes]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, AvailableBytes); - Position = RawData.Length; - return outgoingData; - } - - public void GetBytes(byte[] destination, int start, int count) - { - Buffer.BlockCopy(RawData, Position, destination, start, count); - Position += count; - } - - public void GetBytes(byte[] destination, int count) - { - Buffer.BlockCopy(RawData, Position, destination, 0, count); - Position += count; - } - - public sbyte[] GetSBytesWithLength() - { - var length = GetInt(); - var outgoingData = new sbyte[length]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, length); - Position += length; - return outgoingData; - } - - public byte[] GetBytesWithLength() - { - var length = GetInt(); - var outgoingData = new byte[length]; - Buffer.BlockCopy(RawData, Position, outgoingData, 0, length); - Position += length; - return outgoingData; - } - - #endregion - - #region PeekMethods - - public byte PeekByte() - { - return RawData[Position]; - } - - public sbyte PeekSByte() - { - return (sbyte)RawData[Position]; - } - - public bool PeekBool() - { - return RawData[Position] > 0; - } - - public char PeekChar() - { - return BitConverter.ToChar(RawData, Position); - } - - public ushort PeekUShort() - { - return BitConverter.ToUInt16(RawData, Position); - } - - public short PeekShort() - { - return BitConverter.ToInt16(RawData, Position); - } - - public long PeekLong() - { - return BitConverter.ToInt64(RawData, Position); - } - - public ulong PeekULong() - { - return BitConverter.ToUInt64(RawData, Position); - } - - private int PeekInt() - { - return BitConverter.ToInt32(RawData, Position); - } - - public uint PeekUInt() - { - return BitConverter.ToUInt32(RawData, Position); - } - - public float PeekFloat() - { - return BitConverter.ToSingle(RawData, Position); - } - - public double PeekDouble() - { - return BitConverter.ToDouble(RawData, Position); - } - - public string PeekString(int maxLength) - { - var bytesCount = BitConverter.ToInt32(RawData, Position); - if (bytesCount <= 0 || bytesCount > maxLength * 2) - { - return string.Empty; - } - - var charCount = Encoding.UTF8.GetCharCount(RawData, Position + 4, bytesCount); - if (charCount > maxLength) - { - return string.Empty; - } - - var result = Encoding.UTF8.GetString(RawData, Position + 4, bytesCount); - return result; - } - - public string PeekString() - { - var bytesCount = BitConverter.ToInt32(RawData, Position); - if (bytesCount <= 0) - { - return string.Empty; - } - - var result = Encoding.UTF8.GetString(RawData, Position + 4, bytesCount); - return result; - } - - #endregion - - #region TryGetMethods - - public bool TryGetByte(out byte result) - { - if (AvailableBytes >= 1) - { - result = GetByte(); - return true; - } - result = 0; - return false; - } - - public bool TryGetSByte(out sbyte result) - { - if (AvailableBytes >= 1) - { - result = GetSByte(); - return true; - } - result = 0; - return false; - } - - public bool TryGetBool(out bool result) - { - if (AvailableBytes >= 1) - { - result = GetBool(); - return true; - } - result = false; - return false; - } - - public bool TryGetChar(out char result) - { - if (AvailableBytes >= 2) - { - result = GetChar(); - return true; - } - result = '\0'; - return false; - } - - public bool TryGetShort(out short result) - { - if (AvailableBytes >= 2) - { - result = GetShort(); - return true; - } - result = 0; - return false; - } - - private bool TryGetUShort(out ushort result) - { - if (AvailableBytes >= 2) - { - result = GetUShort(); - return true; - } - result = 0; - return false; - } - - public bool TryGetInt(out int result) - { - if (AvailableBytes >= 4) - { - result = GetInt(); - return true; - } - result = 0; - return false; - } - - public bool TryGetUInt(out uint result) - { - if (AvailableBytes >= 4) - { - result = GetUInt(); - return true; - } - result = 0; - return false; - } - - public bool TryGetLong(out long result) - { - if (AvailableBytes >= 8) - { - result = GetLong(); - return true; - } - result = 0; - return false; - } - - public bool TryGetULong(out ulong result) - { - if (AvailableBytes >= 8) - { - result = GetULong(); - return true; - } - result = 0; - return false; - } - - public bool TryGetFloat(out float result) - { - if (AvailableBytes >= 4) - { - result = GetFloat(); - return true; - } - result = 0; - return false; - } - - public bool TryGetDouble(out double result) - { - if (AvailableBytes >= 8) - { - result = GetDouble(); - return true; - } - result = 0; - return false; - } - - private bool TryGetString(out string result) - { - if (AvailableBytes >= 4) - { - var bytesCount = PeekInt(); - if (AvailableBytes >= bytesCount + 4) - { - result = GetString(); - return true; - } - } - result = null; - return false; - } - - public bool TryGetStringArray(out string[] result) - { - if (!TryGetUShort(out var size)) - { - result = null; - return false; - } - - result = new string[size]; - for (var i = 0; i < size; i++) - { - if (TryGetString(out result[i])) - { - continue; - } - result = null; - return false; - } - - return true; - } - - public bool TryGetBytesWithLength(out byte[] result) - { - if (AvailableBytes >= 4) - { - var length = PeekInt(); - if (length >= 0 && AvailableBytes >= length + 4) - { - result = GetBytesWithLength(); - return true; - } - } - result = null; - return false; - } - - #endregion -} diff --git a/NebulaModel/Networking/Serialization/NetDataWriter.cs b/NebulaModel/Networking/Serialization/NetDataWriter.cs deleted file mode 100644 index 74af34c20..000000000 --- a/NebulaModel/Networking/Serialization/NetDataWriter.cs +++ /dev/null @@ -1,464 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using NebulaAPI.Interfaces; - -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class NetDataWriter : INetDataWriter -{ - private const int InitialSize = 64; - private readonly bool _autoResize; - private byte[] _data; - - public NetDataWriter() : this(true) - { - } - - private NetDataWriter(bool autoResize, int initialSize = InitialSize) - { - _data = new byte[initialSize]; - _autoResize = autoResize; - } - - public int Capacity => _data.Length; - - public byte[] Data => _data; - - public int Length { get; set; } - - public void Put(float value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 4); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(double value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 8); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } - - public void Put(long value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 8); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } - - public void Put(ulong value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 8); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 8; - } - - public void Put(int value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 4); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(uint value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 4); - } - - FastBitConverter.GetBytes(_data, Length, value); - Length += 4; - } - - public void Put(char value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 2); - } - - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(ushort value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 2); - } - - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(short value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 2); - } - - FastBitConverter.GetBytes(_data as IEnumerable, Length, value); - Length += 2; - } - - public void Put(sbyte value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 1); - } - - _data[Length] = (byte)value; - Length++; - } - - public void Put(byte value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 1); - } - - _data[Length] = value; - Length++; - } - - public void Put(byte[] data, int offset, int length) - { - if (_autoResize) - { - ResizeIfNeed(Length + length); - } - - Buffer.BlockCopy(data, offset, _data, Length, length); - Length += length; - } - - public void Put(byte[] data) - { - if (_autoResize) - { - ResizeIfNeed(Length + data.Length); - } - - Buffer.BlockCopy(data, 0, _data, Length, data.Length); - Length += data.Length; - } - - public void PutSBytesWithLength(sbyte[] data, int offset, int length) - { - if (_autoResize) - { - ResizeIfNeed(Length + length + 4); - } - - FastBitConverter.GetBytes(_data, Length, length); - Buffer.BlockCopy(data, offset, _data, Length + 4, length); - Length += length + 4; - } - - public void PutSBytesWithLength(sbyte[] data) - { - if (_autoResize) - { - ResizeIfNeed(Length + data.Length + 4); - } - - FastBitConverter.GetBytes(_data, Length, data.Length); - Buffer.BlockCopy(data, 0, _data, Length + 4, data.Length); - Length += data.Length + 4; - } - - public void PutBytesWithLength(byte[] data, int offset, int length) - { - if (_autoResize) - { - ResizeIfNeed(Length + length + 4); - } - - FastBitConverter.GetBytes(_data, Length, length); - Buffer.BlockCopy(data, offset, _data, Length + 4, length); - Length += length + 4; - } - - public void PutBytesWithLength(byte[] data) - { - if (_autoResize) - { - ResizeIfNeed(Length + data.Length + 4); - } - - FastBitConverter.GetBytes(_data, Length, data.Length); - Buffer.BlockCopy(data, 0, _data, Length + 4, data.Length); - Length += data.Length + 4; - } - - public void Put(bool value) - { - if (_autoResize) - { - ResizeIfNeed(Length + 1); - } - - _data[Length] = (byte)(value ? 1 : 0); - Length++; - } - - public void PutArray(float[] value) - { - PutArray(value, 4); - } - - public void PutArray(double[] value) - { - PutArray(value, 8); - } - - public void PutArray(long[] value) - { - PutArray(value, 8); - } - - public void PutArray(ulong[] value) - { - PutArray(value, 8); - } - - public void PutArray(int[] value) - { - PutArray(value, 4); - } - - public void PutArray(uint[] value) - { - PutArray(value, 4); - } - - public void PutArray(ushort[] value) - { - PutArray(value, 2); - } - - public void PutArray(short[] value) - { - PutArray(value, 2); - } - - public void PutArray(bool[] value) - { - PutArray(value, 1); - } - - public void PutArray(string[] value) - { - var len = value == null ? (ushort)0 : (ushort)value.Length; - Put(len); - for (var i = 0; i < len; i++) - { - if (value != null) - { - Put(value[i]); - } - } - } - - public void PutArray(string[] value, int maxLength) - { - var len = value == null ? (ushort)0 : (ushort)value.Length; - Put(len); - for (var i = 0; i < len; i++) - { - if (value != null) - { - Put(value[i], maxLength); - } - } - } - - public void Put(IPEndPoint endPoint) - { - Put(endPoint.Address.ToString()); - Put(endPoint.Port); - } - - public void Put(string value) - { - if (string.IsNullOrEmpty(value)) - { - Put(0); - return; - } - - //put bytes count - var bytesCount = Encoding.UTF8.GetByteCount(value); - if (_autoResize) - { - ResizeIfNeed(Length + bytesCount + 4); - } - - Put(bytesCount); - - //put string - Encoding.UTF8.GetBytes(value, 0, value.Length, _data, Length); - Length += bytesCount; - } - - public void Put(string value, int maxLength) - { - if (string.IsNullOrEmpty(value)) - { - Put(0); - return; - } - - var length = value.Length > maxLength ? maxLength : value.Length; - //calculate max count - var bytesCount = Encoding.UTF8.GetByteCount(value); - if (_autoResize) - { - ResizeIfNeed(Length + bytesCount + 4); - } - - //put bytes count - Put(bytesCount); - - //put string - Encoding.UTF8.GetBytes(value, 0, length, _data, Length); - - Length += bytesCount; - } - - public void Put(T obj) where T : INetSerializable - { - obj.Serialize(this); - } - - /// - /// Creates NetDataWriter from existing ByteArray - /// - /// Source byte array - /// Copy array to new location or use existing - public static NetDataWriter FromBytes(byte[] bytes, bool copy) - { - if (!copy) - { - return new NetDataWriter(true, 0) { _data = bytes, Length = bytes.Length }; - } - var netDataWriter = new NetDataWriter(true, bytes.Length); - netDataWriter.Put(bytes); - return netDataWriter; - } - - /// - /// Creates NetDataWriter from existing ByteArray (always copied data) - /// - /// Source byte array - /// Offset of array - /// Length of array - public static NetDataWriter FromBytes(byte[] bytes, int offset, int length) - { - var netDataWriter = new NetDataWriter(true, bytes.Length); - netDataWriter.Put(bytes, offset, length); - return netDataWriter; - } - - public static NetDataWriter FromString(string value) - { - var netDataWriter = new NetDataWriter(); - netDataWriter.Put(value); - return netDataWriter; - } - - private void ResizeIfNeed(int newSize) - { - var len = _data.Length; - if (len >= newSize) - { - return; - } - while (len < newSize) - { - len *= 2; - } - - Array.Resize(ref _data, len); - } - - public void Reset(int size) - { - ResizeIfNeed(size); - Length = 0; - } - - public void Reset() - { - Length = 0; - } - - public byte[] CopyData() - { - var resultData = new byte[Length]; - Buffer.BlockCopy(_data, 0, resultData, 0, Length); - return resultData; - } - - /// - /// Sets position of NetDataWriter to rewrite previous values - /// - /// new byte position - /// previous position of data writer - public int SetPosition(int position) - { - var prevPosition = Length; - Length = position; - return prevPosition; - } - - private void PutArray(Array arr, int sz) - { - var length = arr == null ? (ushort)0 : (ushort)arr.Length; - sz *= length; - if (_autoResize) - { - ResizeIfNeed(Length + sz + 2); - } - - FastBitConverter.GetBytes(_data as IEnumerable, Length, length); - if (arr != null) - { - Buffer.BlockCopy(arr, 0, _data, Length + 2, sz); - } - - Length += sz + 2; - } -} diff --git a/NebulaModel/Networking/Serialization/NetPacketProcessor.cs b/NebulaModel/Networking/Serialization/NetPacketProcessor.cs deleted file mode 100644 index 327a66d67..000000000 --- a/NebulaModel/Networking/Serialization/NetPacketProcessor.cs +++ /dev/null @@ -1,434 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using NebulaAPI.Interfaces; -using NebulaAPI.Packets; -using NebulaModel.Logger; - -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class NetPacketProcessor -{ - private readonly Dictionary _callbacks = []; - private readonly Dictionary _callbacksDebugInfo = []; - private readonly NetDataWriter _netDataWriter = new(); - private readonly NetSerializer _netSerializer; - private readonly List delayedPackets = []; - private readonly Queue pendingPackets = new(); - - private readonly Random simulationRandom = new(); - private readonly int SimulatedMaxLatency = 50; - private readonly int SimulatedMinLatency = 20; - - public bool SimulateLatency = false; - - public NetPacketProcessor() - { - _netSerializer = new NetSerializer(); - } - - public NetPacketProcessor(int maxStringLength) - { - _netSerializer = new NetSerializer(maxStringLength); - } - - public bool Enable { get; set; } = true; - - public void EnqueuePacketForProcessing(byte[] rawData, object userData) - { -#if DEBUG - if (SimulateLatency) - { - lock (delayedPackets) - { - var packet = new PendingPacket(rawData, userData); - var dueTime = DateTime.UtcNow.AddMilliseconds(simulationRandom.Next(SimulatedMinLatency, SimulatedMaxLatency)); - delayedPackets.Add(new DelayedPacket(packet, dueTime)); - } - } - else - { - lock (pendingPackets) - { - pendingPackets.Enqueue(new PendingPacket(rawData, userData)); - } - } -#else - lock (pendingPackets) - { - pendingPackets.Enqueue(new PendingPacket(rawData, userData)); - } -#endif - } - - public void ProcessPacketQueue() - { - lock (pendingPackets) - { - ProcessDelayedPackets(); - - while (pendingPackets.Count > 0 && Enable) - { - var packet = pendingPackets.Dequeue(); - ReadPacket(new NetDataReader(packet.Data), packet.UserData); - } - } - } - - [Conditional("DEBUG")] - private void ProcessDelayedPackets() - { - lock (delayedPackets) - { - var now = DateTime.UtcNow; - var deleteCount = 0; - - for (var i = 0; i < delayedPackets.Count; ++i) - { - if (now >= delayedPackets[i].DueTime) - { - pendingPackets.Enqueue(delayedPackets[i].Packet); - deleteCount = i + 1; - } - else - { - // We need to break to avoid messing up the order of the packets. - break; - } - } - - if (deleteCount > 0) - { - delayedPackets.RemoveRange(0, deleteCount); - } - } - } - - //FNV-1 64 bit hash - protected virtual ulong GetHash() - { - if (HashCache.Initialized) - { - return HashCache.Id; - } - - var hash = 14695981039346656037UL; //offset - var typeName = typeof(T).FullName; - if (typeName != null) - { - foreach (var t in typeName) - { - hash ^= t; - hash *= 1099511628211UL; //prime - } - } - HashCache.Initialized = true; - HashCache.Id = hash; - return hash; - } - - protected virtual SubscribeDelegate GetCallbackFromData(NetDataReader reader) - { - var hash = reader.GetULong(); - if (!_callbacks.TryGetValue(hash, out var action)) - { - Log.Warn($"Unknown packet hash: {hash}"); - throw new Exception("Undefined packet in NetDataReader"); - } - -#if DEBUG - if (_callbacksDebugInfo.TryGetValue(hash, out var packetType)) - { - if (!packetType.IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) - { - Log.Debug($"Packet Received: {packetType.Name}"); - } - } - else - { - Log.Warn($"Packet not registered: {hash}"); - } -#endif - - return action; - } - - protected virtual void WriteHash(NetDataWriter writer) - { - writer.Put(GetHash()); - } - - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Produces error when static")] - public T CreateNestedClassInstance() where T : class, INetSerializable, new() - { - return new T(); - } - - /// - /// Register nested property type - /// - /// INetSerializable structure - public void RegisterNestedType() where T : struct, INetSerializable - { - _netSerializer.RegisterNestedType(); - } - - /// - /// Register nested property type - /// - /// - /// - public void RegisterNestedType(Action writeDelegate, Func readDelegate) - { - _netSerializer.RegisterNestedType(writeDelegate, readDelegate); - } - - /// - /// Register nested property type - /// - /// INetSerializable class - public void RegisterNestedType(Func constructor) where T : class, INetSerializable - { - _netSerializer.RegisterNestedType(constructor); - } - - /// - /// Reads all available data from NetDataReader and calls OnReceive delegates - /// - /// NetDataReader with packets data - public void ReadAllPackets(NetDataReader reader) - { - while (reader.AvailableBytes > 0) - { - ReadPacket(reader); - } - } - - /// - /// Reads all available data from NetDataReader and calls OnReceive delegates - /// - /// NetDataReader with packets data - /// Argument that passed to OnReceivedEvent - /// Malformed packet - public void ReadAllPackets(NetDataReader reader, object userData) - { - while (reader.AvailableBytes > 0) - { - ReadPacket(reader, userData); - } - } - - /* public void Send(NetPeer peer, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - peer.Send(_netDataWriter, options); - } - - public void SendNetSerializable(NetPeer peer, T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, packet); - peer.Send(_netDataWriter, options); - } - - public void Send(NetManager manager, T packet, DeliveryMethod options) where T : class, new() - { - _netDataWriter.Reset(); - Write(_netDataWriter, packet); - manager.SendToAll(_netDataWriter, options); - } - - public void SendNetSerializable(NetManager manager, T packet, DeliveryMethod options) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteNetSerializable(_netDataWriter, packet); - manager.SendToAll(_netDataWriter, options); - }*/ - - public void Write(NetDataWriter writer, T packet) where T : class, new() - { - WriteHash(writer); - _netSerializer.Serialize(writer, packet); - } - - public void WriteNetSerializable(NetDataWriter writer, T packet) where T : INetSerializable - { - WriteHash(writer); - packet.Serialize(writer); - } - - public byte[] Write(T packet) where T : class, new() - { -#if DEBUG - if (!typeof(T).IsDefined(typeof(HidePacketInDebugLogsAttribute), false)) - { - Log.Debug($"Packet Sent: {packet.GetType().Name}"); - } -#endif - _netDataWriter.Reset(); - WriteHash(_netDataWriter); - _netSerializer.Serialize(_netDataWriter, packet); - return _netDataWriter.CopyData(); - } - - public byte[] WriteNetSerializable(T packet) where T : INetSerializable - { - _netDataWriter.Reset(); - WriteHash(_netDataWriter); - packet.Serialize(_netDataWriter); - return _netDataWriter.CopyData(); - } - - /// - /// Reads one packet from NetDataReader and calls OnReceive delegate - /// - /// NetDataReader with packet - /// Argument that passed to OnReceivedEvent - /// Malformed packet - private void ReadPacket(NetDataReader reader, object userData = null) - { - GetCallbackFromData(reader)(reader, userData); - } - - /// - /// Register and subscribe to packet receive event - /// - /// event that will be called when packet deserialized with ReadPacket method - /// Method that constructs packet instead of slow Activator.CreateInstance - /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() - { - _netSerializer.Register(); - _callbacks[GetHash()] = (reader, userData) => - { - var reference = packetConstructor(); - _netSerializer.Deserialize(reader, reference); - onReceive(reference); - }; - } - - /// - /// Register and subscribe to packet receive event (with userData) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// Method that constructs packet instead of slow Activator.CreateInstance - /// 's fields are not supported, or it has no fields - public void Subscribe(Action onReceive, Func packetConstructor) where T : class, new() - { - _netSerializer.Register(); - _callbacks[GetHash()] = (reader, userData) => - { - var reference = packetConstructor(); - _netSerializer.Deserialize(reader, reference); - onReceive(reference, (TUserData)userData); - }; - } - - /// - /// Register and subscribe to packet receive event - /// This method will overwrite last received packet class on receive (less garbage) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() - { - _netSerializer.Register(); - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => - { - _netSerializer.Deserialize(reader, reference); - onReceive(reference); - }; - } - - /// - /// Register and subscribe to packet receive event - /// This method will overwrite last received packet class on receive (less garbage) - /// - /// event that will be called when packet deserialized with ReadPacket method - /// 's fields are not supported, or it has no fields - public void SubscribeReusable(Action onReceive) where T : class, new() - { - _netSerializer.Register(); - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => - { - _netSerializer.Deserialize(reader, reference); - onReceive(reference, (TUserData)userData); - }; - -#if DEBUG - _callbacksDebugInfo[GetHash()] = typeof(T); -#endif - } - - public void SubscribeNetSerializable( - Action onReceive, - Func packetConstructor) where T : INetSerializable - { - _callbacks[GetHash()] = (reader, userData) => - { - var pkt = packetConstructor(); - pkt.Deserialize(reader); - onReceive(pkt, (TUserData)userData); - }; - } - - public void SubscribeNetSerializable( - Action onReceive, - Func packetConstructor) where T : INetSerializable - { - _callbacks[GetHash()] = (reader, userData) => - { - var pkt = packetConstructor(); - pkt.Deserialize(reader); - onReceive(pkt); - }; - } - - public void SubscribeNetSerializable( - Action onReceive) where T : INetSerializable, new() - { - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => - { - reference.Deserialize(reader); - onReceive(reference, (TUserData)userData); - }; - } - - public void SubscribeNetSerializable( - Action onReceive) where T : INetSerializable, new() - { - var reference = new T(); - _callbacks[GetHash()] = (reader, userData) => - { - reference.Deserialize(reader); - onReceive(reference); - }; - } - - /// - /// Remove any subscriptions by type - /// - /// Packet type - /// true if remove is success - public bool RemoveSubscription() - { - return _callbacks.Remove(GetHash()); - } - - private static class HashCache - { - public static bool Initialized; - public static ulong Id; - } - - protected delegate void SubscribeDelegate(NetDataReader reader, object userData); -} diff --git a/NebulaModel/Networking/Serialization/NetSerializer.cs b/NebulaModel/Networking/Serialization/NetSerializer.cs deleted file mode 100644 index 2771371de..000000000 --- a/NebulaModel/Networking/Serialization/NetSerializer.cs +++ /dev/null @@ -1,932 +0,0 @@ -#region - -using System; -using System.Collections.Generic; -using System.Net; -using System.Reflection; -using NebulaAPI.Interfaces; - -#endregion - -namespace NebulaModel.Networking.Serialization; - -public class InvalidTypeException : ArgumentException -{ - public InvalidTypeException(string message) : base(message) { } -} - -public class ParseException : Exception -{ - public ParseException(string message) : base(message) { } -} - -public class NetSerializer -{ - private readonly int _maxStringLength; - private readonly Dictionary _registeredTypes = new(); - - private NetDataWriter _writer; - - public NetSerializer() : this(0) - { - } - - public NetSerializer(int maxStringLength) - { - _maxStringLength = maxStringLength; - } - - /// - /// Register custom property type - /// - /// INetSerializable structure - public void RegisterNestedType() where T : struct, INetSerializable - { - _registeredTypes.Add(typeof(T), new CustomTypeStruct()); - } - - /// - /// Register custom property type - /// - /// INetSerializable class - public void RegisterNestedType(Func constructor) where T : class, INetSerializable - { - _registeredTypes.Add(typeof(T), new CustomTypeClass(constructor)); - } - - /// - /// Register custom property type - /// - /// Any packet - /// custom type writer - /// custom type reader - public void RegisterNestedType(Action writer, Func reader) - { - _registeredTypes.Add(typeof(T), new CustomTypeStatic(writer, reader)); - } - - private ClassInfo RegisterInternal() - { - if (ClassInfo.Instance != null) - { - return ClassInfo.Instance; - } - - var t = typeof(T); - var props = t.GetProperties( - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.GetProperty | - BindingFlags.SetProperty); - var serializers = new List>(); - foreach (var property in props) - { - var propertyType = property.PropertyType; - - var elementType = propertyType.IsArray ? propertyType.GetElementType() : propertyType; - var callType = propertyType.IsArray ? CallType.Array : CallType.Basic; - - if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>)) - { - elementType = propertyType.GetGenericArguments()[0]; - callType = CallType.List; - } - - // Note from Cod: Required to get it to build - // TODO: Fix this - /*if (Attribute.IsDefined(property, typeof(IgnoreDataMemberAttribute))) - continue;*/ - - var getMethod = property.GetGetMethod(); - var setMethod = property.GetSetMethod(); - if (getMethod == null || setMethod == null) - { - continue; - } - - FastCall serialzer = null; - if (propertyType.IsEnum) - { - var underlyingType = Enum.GetUnderlyingType(propertyType); - if (underlyingType == typeof(byte)) - { - serialzer = new EnumByteSerializer(property, propertyType); - } - else if (underlyingType == typeof(int)) - { - serialzer = new EnumIntSerializer(property, propertyType); - } - else - { - throw new InvalidTypeException("Not supported enum underlying type: " + underlyingType.Name); - } - } - else if (elementType == typeof(string)) - { - serialzer = new StringSerializer(_maxStringLength); - } - else if (elementType == typeof(bool)) - { - serialzer = new BoolSerializer(); - } - else if (elementType == typeof(byte)) - { - serialzer = new ByteSerializer(); - } - else if (elementType == typeof(sbyte)) - { - serialzer = new SByteSerializer(); - } - else if (elementType == typeof(short)) - { - serialzer = new ShortSerializer(); - } - else if (elementType == typeof(ushort)) - { - serialzer = new UShortSerializer(); - } - else if (elementType == typeof(int)) - { - serialzer = new IntSerializer(); - } - else if (elementType == typeof(uint)) - { - serialzer = new UIntSerializer(); - } - else if (elementType == typeof(long)) - { - serialzer = new LongSerializer(); - } - else if (elementType == typeof(ulong)) - { - serialzer = new ULongSerializer(); - } - else if (elementType == typeof(float)) - { - serialzer = new FloatSerializer(); - } - else if (elementType == typeof(double)) - { - serialzer = new DoubleSerializer(); - } - else if (elementType == typeof(char)) - { - serialzer = new CharSerializer(); - } - else if (elementType == typeof(IPEndPoint)) - { - serialzer = new IPEndPointSerializer(); - } - else - { - if (elementType != null) - { - _registeredTypes.TryGetValue(elementType, out var customType); - if (customType != null) - { - serialzer = customType.Get(); - } - } - } - - if (serialzer != null) - { - serialzer.Init(getMethod, setMethod, callType); - serializers.Add(serialzer); - } - else - { - throw new InvalidTypeException("Unknown property type: " + propertyType.FullName); - } - } - ClassInfo.Instance = new ClassInfo(serializers); - return ClassInfo.Instance; - } - - /// 's fields are not supported, or it has no fields - public void Register() - { - RegisterInternal(); - } - - /// - /// Reads packet with known type - /// - /// NetDataReader with packet - /// Returns packet if packet in reader is matched type - /// 's fields are not supported, or it has no fields - public T Deserialize(NetDataReader reader) where T : class, new() - { - var info = RegisterInternal(); - var result = new T(); - try - { - info.Read(result, reader); - } - catch - { - return null; - } - return result; - } - - /// - /// Reads packet with known type (non alloc variant) - /// - /// NetDataReader with packet - /// Deserialization target - /// Returns true if packet in reader is matched type - /// 's fields are not supported, or it has no fields - public bool Deserialize(NetDataReader reader, T target) where T : class, new() - { - var info = RegisterInternal(); - try - { - info.Read(target, reader); - } - catch - { - return false; - } - return true; - } - - /// - /// Serialize object to NetDataWriter (fast) - /// - /// Serialization target NetDataWriter - /// Object to serialize - /// 's fields are not supported, or it has no fields - public void Serialize(NetDataWriter writer, T obj) where T : class, new() - { - RegisterInternal().Write(obj, writer); - } - - /// - /// Serialize object to byte array - /// - /// Object to serialize - /// byte array with serialized data - public byte[] Serialize(T obj) where T : class, new() - { - _writer ??= new NetDataWriter(); - - _writer.Reset(); - Serialize(_writer, obj); - return _writer.CopyData(); - } - - private enum CallType - { - Basic, - Array, - List - } - - private abstract class FastCall - { - public CallType Type; - - public virtual void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) { Type = type; } - - public abstract void Read(T inf, NetDataReader r); - - public abstract void Write(T inf, NetDataWriter w); - - public abstract void ReadArray(T inf, NetDataReader r); - - public abstract void WriteArray(T inf, NetDataWriter w); - - public abstract void ReadList(T inf, NetDataReader r); - - public abstract void WriteList(T inf, NetDataWriter w); - } - - private abstract class FastCallSpecific : FastCall - { - protected Func Getter; - protected Func GetterArr; - private Func> GetterList; - protected Action Setter; - protected Action SetterArr; - private Action> SetterList; - - public override void ReadArray(TClass inf, NetDataReader r) - { - throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - throw new InvalidTypeException("Unsupported type: " + typeof(TProperty) + "[]"); - } - - public override void ReadList(TClass inf, NetDataReader r) - { - throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); - } - - public override void WriteList(TClass inf, NetDataWriter w) - { - throw new InvalidTypeException("Unsupported type: List<" + typeof(TProperty) + ">"); - } - - protected TProperty[] ReadArrayHelper(TClass inf, NetDataReader r) - { - var count = r.GetUShort(); - var arr = GetterArr(inf); - arr = arr == null || arr.Length != count ? new TProperty[count] : arr; - SetterArr(inf, arr); - return arr; - } - - protected TProperty[] WriteArrayHelper(TClass inf, NetDataWriter w) - { - var arr = GetterArr(inf); - w.Put((ushort)arr.Length); - return arr; - } - - protected List ReadListHelper(TClass inf, NetDataReader r, out int len) - { - len = r.GetUShort(); - var list = GetterList(inf); - if (list != null) - { - return list; - } - list = new List(len); - SetterList(inf, list); - return list; - } - - protected List WriteListHelper(TClass inf, NetDataWriter w, out int len) - { - var list = GetterList(inf); - if (list == null) - { - len = 0; - w.Put(0); - return null; - } - len = list.Count; - w.Put((ushort)len); - return list; - } - - public override void Init(MethodInfo getMethod, MethodInfo setMethod, CallType type) - { - base.Init(getMethod, setMethod, type); - switch (type) - { - case CallType.Array: - GetterArr = (Func)Delegate.CreateDelegate(typeof(Func), - getMethod); - SetterArr = (Action)Delegate.CreateDelegate(typeof(Action), - setMethod); - break; - case CallType.List: - GetterList = (Func>)Delegate.CreateDelegate(typeof(Func>), - getMethod); - SetterList = (Action>)Delegate.CreateDelegate( - typeof(Action>), setMethod); - break; - case CallType.Basic: - default: - Getter = (Func)Delegate.CreateDelegate(typeof(Func), getMethod); - Setter = (Action)Delegate.CreateDelegate(typeof(Action), setMethod); - break; - } - } - } - - private abstract class FastCallSpecificAuto : FastCallSpecific - { - protected abstract void ElementRead(NetDataReader r, out TProperty prop); - - protected abstract void ElementWrite(NetDataWriter w, ref TProperty prop); - - public override void Read(TClass inf, NetDataReader r) - { - ElementRead(r, out var elem); - Setter(inf, elem); - } - - public override void Write(TClass inf, NetDataWriter w) - { - var elem = Getter(inf); - ElementWrite(w, ref elem); - } - - public override void ReadArray(TClass inf, NetDataReader r) - { - var arr = ReadArrayHelper(inf, r); - for (var i = 0; i < arr.Length; i++) - { - ElementRead(r, out arr[i]); - } - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - var arr = WriteArrayHelper(inf, w); - for (var i = 0; i < arr.Length; i++) - { - ElementWrite(w, ref arr[i]); - } - } - } - - private sealed class FastCallStatic : FastCallSpecific - { - private readonly Func _reader; - private readonly Action _writer; - - public FastCallStatic(Action write, Func read) - { - _writer = write; - _reader = read; - } - - public override void Read(TClass inf, NetDataReader r) { Setter(inf, _reader(r)); } - - public override void Write(TClass inf, NetDataWriter w) { _writer(w, Getter(inf)); } - - public override void ReadList(TClass inf, NetDataReader r) - { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) - { - if (i < listCount) - { - list[i] = _reader(r); - } - else - { - list.Add(_reader(r)); - } - } - if (len < listCount) - { - list.RemoveRange(len, listCount - len); - } - } - - public override void WriteList(TClass inf, NetDataWriter w) - { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) - { - _writer(w, list[i]); - } - } - - public override void ReadArray(TClass inf, NetDataReader r) - { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i] = _reader(r); - } - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - _writer(w, arr[i]); - } - } - } - - private sealed class FastCallStruct : FastCallSpecific - where TProperty : struct, INetSerializable - { - private TProperty _p; - - public override void Read(TClass inf, NetDataReader r) - { - _p.Deserialize(r); - Setter(inf, _p); - } - - public override void Write(TClass inf, NetDataWriter w) - { - _p = Getter(inf); - _p.Serialize(w); - } - - public override void ReadList(TClass inf, NetDataReader r) - { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) - { - var itm = default(TProperty); - itm.Deserialize(r); - if (i < listCount) - { - list[i] = itm; - } - else - { - list.Add(itm); - } - } - if (len < listCount) - { - list.RemoveRange(len, listCount - len); - } - } - - public override void WriteList(TClass inf, NetDataWriter w) - { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) - { - list[i].Serialize(w); - } - } - - public override void ReadArray(TClass inf, NetDataReader r) - { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i].Deserialize(r); - } - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i].Serialize(w); - } - } - } - - private sealed class FastCallClass : FastCallSpecific - where TProperty : class, INetSerializable - { - private readonly Func _constructor; - - public FastCallClass(Func constructor) { _constructor = constructor; } - - public override void Read(TClass inf, NetDataReader r) - { - var p = _constructor(); - p.Deserialize(r); - Setter(inf, p); - } - - public override void Write(TClass inf, NetDataWriter w) - { - var p = Getter(inf); - p?.Serialize(w); - } - - public override void ReadList(TClass inf, NetDataReader r) - { - var list = ReadListHelper(inf, r, out var len); - var listCount = list.Count; - for (var i = 0; i < len; i++) - { - if (i < listCount) - { - list[i].Deserialize(r); - } - else - { - var itm = _constructor(); - itm.Deserialize(r); - list.Add(itm); - } - } - if (len < listCount) - { - list.RemoveRange(len, listCount - len); - } - } - - public override void WriteList(TClass inf, NetDataWriter w) - { - var list = WriteListHelper(inf, w, out var len); - for (var i = 0; i < len; i++) - { - list[i].Serialize(w); - } - } - - public override void ReadArray(TClass inf, NetDataReader r) - { - var arr = ReadArrayHelper(inf, r); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i] = _constructor(); - arr[i].Deserialize(r); - } - } - - public override void WriteArray(TClass inf, NetDataWriter w) - { - var arr = WriteArrayHelper(inf, w); - var len = arr.Length; - for (var i = 0; i < len; i++) - { - arr[i].Serialize(w); - } - } - } - - private class IntSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetInt()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetIntArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class UIntSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUInt()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUIntArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ShortSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetShort()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetShortArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class UShortSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetUShort()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetUShortArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class LongSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetLong()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetLongArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ULongSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetULong()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetULongArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class ByteSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetByte()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBytesWithLength()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutBytesWithLength(GetterArr(inf)); } - } - - private class SByteSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetSByte()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetSBytesWithLength()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutSBytesWithLength(GetterArr(inf)); } - } - - private class FloatSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetFloat()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetFloatArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class DoubleSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetDouble()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetDoubleArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class BoolSerializer : FastCallSpecific - { - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetBool()); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf)); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetBoolArray()); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf)); } - } - - private class CharSerializer : FastCallSpecificAuto - { - protected override void ElementWrite(NetDataWriter w, ref char prop) { w.Put(prop); } - - protected override void ElementRead(NetDataReader r, out char prop) { prop = r.GetChar(); } - } - - private class IPEndPointSerializer : FastCallSpecificAuto - { - protected override void ElementWrite(NetDataWriter w, ref IPEndPoint prop) { w.Put(prop); } - - protected override void ElementRead(NetDataReader r, out IPEndPoint prop) { prop = r.GetNetEndPoint(); } - } - - private class StringSerializer : FastCallSpecific - { - private readonly int _maxLength; - - public StringSerializer(int maxLength) { _maxLength = maxLength > 0 ? maxLength : short.MaxValue; } - - public override void Read(T inf, NetDataReader r) { Setter(inf, r.GetString(_maxLength)); } - - public override void Write(T inf, NetDataWriter w) { w.Put(Getter(inf), _maxLength); } - - public override void ReadArray(T inf, NetDataReader r) { SetterArr(inf, r.GetStringArray(_maxLength)); } - - public override void WriteArray(T inf, NetDataWriter w) { w.PutArray(GetterArr(inf), _maxLength); } - } - - private class EnumByteSerializer : FastCall - { - protected readonly PropertyInfo Property; - protected readonly Type PropertyType; - - public EnumByteSerializer(PropertyInfo property, Type propertyType) - { - Property = property; - PropertyType = propertyType; - } - - public override void Read(T inf, NetDataReader r) - { - Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetByte()), null); - } - - public override void Write(T inf, NetDataWriter w) { w.Put((byte)Property.GetValue(inf, null)); } - - public override void ReadArray(T inf, NetDataReader r) { throw new InvalidTypeException("Unsupported type: Enum[]"); } - - public override void WriteArray(T inf, NetDataWriter w) { throw new InvalidTypeException("Unsupported type: Enum[]"); } - - public override void ReadList(T inf, NetDataReader r) - { - throw new InvalidTypeException("Unsupported type: List"); - } - - public override void WriteList(T inf, NetDataWriter w) - { - throw new InvalidTypeException("Unsupported type: List"); - } - } - - private class EnumIntSerializer : EnumByteSerializer - { - public EnumIntSerializer(PropertyInfo property, Type propertyType) : base(property, propertyType) { } - - public override void Read(T inf, NetDataReader r) - { - Property.SetValue(inf, Enum.ToObject(PropertyType, r.GetInt()), null); - } - - public override void Write(T inf, NetDataWriter w) { w.Put((int)Property.GetValue(inf, null)); } - } - - private sealed class ClassInfo - { - public static ClassInfo Instance; - private readonly int _membersCount; - private readonly FastCall[] _serializers; - - public ClassInfo(List> serializers) - { - _membersCount = serializers.Count; - _serializers = serializers.ToArray(); - } - - public void Write(T obj, NetDataWriter writer) - { - for (var i = 0; i < _membersCount; i++) - { - var s = _serializers[i]; - switch (s.Type) - { - case CallType.Basic: - s.Write(obj, writer); - break; - case CallType.Array: - s.WriteArray(obj, writer); - break; - case CallType.List: - default: - s.WriteList(obj, writer); - break; - } - } - } - - public void Read(T obj, NetDataReader reader) - { - for (var i = 0; i < _membersCount; i++) - { - var s = _serializers[i]; - switch (s.Type) - { - case CallType.Basic: - s.Read(obj, reader); - break; - case CallType.Array: - s.ReadArray(obj, reader); - break; - case CallType.List: - default: - s.ReadList(obj, reader); - break; - } - } - } - } - - private abstract class CustomType - { - public abstract FastCall Get(); - } - - private sealed class CustomTypeStruct : CustomType where TProperty : struct, INetSerializable - { - public override FastCall Get() { return new FastCallStruct(); } - } - - private sealed class CustomTypeClass : CustomType where TProperty : class, INetSerializable - { - private readonly Func _constructor; - - public CustomTypeClass(Func constructor) { _constructor = constructor; } - - public override FastCall Get() { return new FastCallClass(_constructor); } - } - - private sealed class CustomTypeStatic : CustomType - { - private readonly Func _reader; - private readonly Action _writer; - - public CustomTypeStatic(Action writer, Func reader) - { - _writer = writer; - _reader = reader; - } - - public override FastCall Get() { return new FastCallStatic(_writer, _reader); } - } -} diff --git a/NebulaModel/Packets/Factory/BattleBase/BattleBaseSettingUpdatePacket.cs b/NebulaModel/Packets/Factory/BattleBase/BattleBaseSettingUpdatePacket.cs new file mode 100644 index 000000000..639a89b86 --- /dev/null +++ b/NebulaModel/Packets/Factory/BattleBase/BattleBaseSettingUpdatePacket.cs @@ -0,0 +1,32 @@ +namespace NebulaModel.Packets.Factory.BattleBase; + +public class BattleBaseSettingUpdatePacket +{ + public BattleBaseSettingUpdatePacket() { } + + public BattleBaseSettingUpdatePacket(int planetId, int battleBaseId, BattleBaseSettingEvent settingEvent, float arg1) + { + PlanetId = planetId; + BattleBaseId = battleBaseId; + Event = settingEvent; + Arg1 = arg1; + } + + public int PlanetId { get; set; } + public int BattleBaseId { get; set; } + public BattleBaseSettingEvent Event { get; set; } + public float Arg1 { get; set; } +} + +public enum BattleBaseSettingEvent +{ + None = 0, + ChangeMaxChargePower = 1, //OnMaxChargePowerSliderChange + ToggleDroneEnabled = 2, //OnDroneButtonClick + ChangeDronesPriority = 3, //OnDroneButtonClick + ToggleCombatModuleEnabled = 4, //OnFleetButtonClick + ToggleAutoReconstruct = 5, //OnAutoReconstructButtonClick + ToggleAutoPickEnabled = 6, //OnAutoPickButtonClick + ChangeFleetConfig = 7, //OnFleetTypeButtonClick + ToggleAutoReplenishFleet = 8 //OnAutoReplenishButtonClick +} diff --git a/NebulaModel/Packets/Factory/BattleBase/NewBattleBaseDroneOrderPacket.cs b/NebulaModel/Packets/Factory/BattleBase/NewBattleBaseDroneOrderPacket.cs new file mode 100644 index 000000000..c6bf97943 --- /dev/null +++ b/NebulaModel/Packets/Factory/BattleBase/NewBattleBaseDroneOrderPacket.cs @@ -0,0 +1,19 @@ +namespace NebulaModel.Packets.Factory.BattleBase; + +public class NewBattleBaseDroneOrderPacket +{ + public NewBattleBaseDroneOrderPacket() { } + + public NewBattleBaseDroneOrderPacket(int planetId, int entityId, int owner, bool isConstruction) + { + PlanetId = planetId; + EntityId = entityId; + Owner = owner; + IsConstruction = isConstruction; + } + + public int PlanetId { get; set; } + public int EntityId { get; set; } + public int Owner { get; set; } + public bool IsConstruction { get; set; } +} diff --git a/NebulaModel/Packets/Factory/CreatePrebuildsRequest.cs b/NebulaModel/Packets/Factory/CreatePrebuildsRequest.cs index f205e5a5d..325590fb4 100644 --- a/NebulaModel/Packets/Factory/CreatePrebuildsRequest.cs +++ b/NebulaModel/Packets/Factory/CreatePrebuildsRequest.cs @@ -32,29 +32,37 @@ public CreatePrebuildsRequest(int planetId, List buildPreviews, in } public int PlanetId { get; set; } - private byte[] BuildPreviewData { get; set; } + public byte[] BuildPreviewData { get; set; } public int AuthorId { get; set; } public string BuildToolType { get; set; } public int PrebuildId { get; set; } - public List GetBuildPreviews() + public bool TryGetBuildPreviews(out List buildPreviews) { - var result = new List(); + buildPreviews = new(); - using var reader = new BinaryUtils.Reader(BuildPreviewData); - var previewCount = reader.BinaryReader.ReadInt32(); - for (var i = 0; i < previewCount; i++) + try { - result.Add(new BuildPreview()); + using var reader = new BinaryUtils.Reader(BuildPreviewData); + var previewCount = reader.BinaryReader.ReadInt32(); + for (var i = 0; i < previewCount; i++) + { + buildPreviews.Add(new BuildPreview()); + } + for (var i = 0; i < previewCount; i++) + { + DeserializeBuildPreview(buildPreviews[i], buildPreviews, reader.BinaryReader); + } + return true; } - for (var i = 0; i < previewCount; i++) + catch (System.Exception e) { - DeserializeBuildPreview(result[i], result, reader.BinaryReader); + Logger.Log.WarnInform("DeserializeBuildPreview parse error\n" + e); + return false; } - return result; } - private static void DeserializeBuildPreview(BuildPreview buildPreview, IReadOnlyList list, BinaryReader br) + public static void DeserializeBuildPreview(BuildPreview buildPreview, IReadOnlyList list, BinaryReader br) { var outputRef = br.ReadInt32(); buildPreview.output = outputRef == -1 ? null : list[outputRef]; @@ -88,70 +96,27 @@ private static void DeserializeBuildPreview(BuildPreview buildPreview, IReadOnly buildPreview.recipeId = br.ReadInt32(); buildPreview.filterId = br.ReadInt32(); buildPreview.isConnNode = br.ReadBoolean(); - buildPreview.desc = new PrefabDesc - { - //Import more data about the Prefab to properly validate the build condition on server-side - assemblerRecipeType = (ERecipeType)br.ReadInt32(), - cullingHeight = br.ReadSingle(), - gammaRayReceiver = br.ReadBoolean(), - inserterSTT = br.ReadInt32(), - isAccumulator = br.ReadBoolean(), - isAssembler = br.ReadBoolean(), - isBelt = br.ReadBoolean(), - isCollectStation = br.ReadBoolean(), - isDispenser = br.ReadBoolean(), - isEjector = br.ReadBoolean(), - isFractionator = br.ReadBoolean(), - isInserter = br.ReadBoolean(), - isLab = br.ReadBoolean(), - isPowerExchanger = br.ReadBoolean(), - isSplitter = br.ReadBoolean(), - isStation = br.ReadBoolean(), - isStellarStation = br.ReadBoolean(), - isStorage = br.ReadBoolean(), - isTank = br.ReadBoolean(), - isVeinCollector = br.ReadBoolean(), - minerType = (EMinerType)br.ReadInt32(), - modelIndex = br.ReadInt32(), - multiLevel = br.ReadBoolean(), - oilMiner = br.ReadBoolean(), - stationCollectSpeed = br.ReadInt32(), - veinMiner = br.ReadBoolean(), - windForcedPower = br.ReadBoolean(), - workEnergyPerTick = br.ReadInt64() - }; - //Import information about the position of build (land / sea) - num = br.ReadInt32(); - buildPreview.desc.landPoints = new Vector3[num]; - for (var i = 0; i < num; i++) + var modelIndex = br.ReadInt32(); + if (modelIndex >= 0 && modelIndex < LDB.models.modelArray.Length) { - buildPreview.desc.landPoints[i] = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); + var modelProto = LDB.models.modelArray[modelIndex]; + if (modelProto != null) + { + buildPreview.desc = modelProto.prefabDesc; + } } - num = br.ReadInt32(); - buildPreview.desc.waterPoints = new Vector3[num]; - for (var i = 0; i < num; i++) + if (buildPreview.desc == null) { - buildPreview.desc.waterPoints[i] = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); + throw new System.Exception("DeserializeBuildPreview: can't find modelIndx " + modelIndex); } - - //Import information about the collider to check the collisions - buildPreview.desc.hasBuildCollider = br.ReadBoolean(); - num = br.ReadInt32(); - buildPreview.desc.buildColliders = new ColliderData[num]; - for (var i = 0; i < num; i++) + var itemId = br.ReadInt32(); + buildPreview.item = LDB.items.Select(itemId); + if (buildPreview.item == null) { - buildPreview.desc.buildColliders[i].pos = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); - buildPreview.desc.buildColliders[i].ext = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); - buildPreview.desc.buildColliders[i].q = - new Quaternion(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); - buildPreview.desc.buildColliders[i].radius = br.ReadSingle(); - buildPreview.desc.buildColliders[i].idType = br.ReadInt32(); - buildPreview.desc.buildColliders[i].link = br.ReadInt32(); + throw new System.Exception("DeserializeBuildPreview: can't find itemId " + itemId); } - buildPreview.item = new ItemProto { ID = br.ReadInt32(), BuildMode = br.ReadInt32(), Grade = br.ReadInt32() }; - buildPreview.paramCount = br.ReadInt32(); buildPreview.parameters = new int[buildPreview.paramCount]; for (var i = 0; i < buildPreview.paramCount; i++) @@ -165,7 +130,7 @@ private static void DeserializeBuildPreview(BuildPreview buildPreview, IReadOnly buildPreview.condition = (EBuildCondition)br.ReadInt32(); } - private static void SerializeBuildPreview(BuildPreview buildPreview, IList list, BinaryWriter bw) + public static void SerializeBuildPreview(BuildPreview buildPreview, IList list, BinaryWriter bw) { bw.Write(list.IndexOf(buildPreview.output)); bw.Write(list.IndexOf(buildPreview.input)); @@ -195,77 +160,11 @@ private static void SerializeBuildPreview(BuildPreview buildPreview, IList destination.player.package.size) { destination.player.package.SetSize(inventorySize); @@ -181,6 +230,20 @@ public void UpdateMech(Mecha destination) destination.player.deliveryPackage.NotifySizeChange(); } destination.player.deliveryPackage.stackSizeMultiplier = deliveryPackageStackSizeMultiplier; + destination.instantBuildEnergy = instantBuildEnergy; + destination.hpMaxUpgrade = hpMaxUpgrade; + destination.energyShieldUnlocked = energyShieldUnlocked; + destination.energyShieldRadius = energyShieldRadius; + destination.energyShieldCapacity = energyShieldCapacity; + destination.laserEnergyCapacity = laserEnergyCapacity; + destination.laserLocalAttackRange = laserLocalAttackRange; + destination.laserSpaceAttackRange = laserSpaceAttackRange; + destination.laserLocalEnergyCost = laserLocalEnergyCost; + destination.laserSpaceEnergyCost = laserSpaceEnergyCost; + destination.laserLocalDamage = laserLocalDamage; + destination.laserSpaceDamage = laserSpaceDamage; + destination.groundCombatModule.fleetCount = groundFleetCount; + destination.spaceCombatModule.fleetCount = spaceFleetCount; } public void Import(INetDataReader reader, int revision) @@ -207,9 +270,10 @@ public void Import(INetDataReader reader, int revision) maxSailSpeed = reader.GetFloat(); maxWarpSpeed = reader.GetFloat(); buildArea = reader.GetFloat(); - droneCount = reader.GetInt(); - droneSpeed = reader.GetFloat(); - droneMovement = reader.GetInt(); + if (revision >= 8) + { + droneCount = reader.GetInt(); + } inventorySize = reader.GetInt(); if (revision < 7) { @@ -218,5 +282,23 @@ public void Import(INetDataReader reader, int revision) deliveryPackageUnlocked = reader.GetBool(); deliveryPackageColCount = reader.GetInt(); deliveryPackageStackSizeMultiplier = reader.GetInt(); + if (revision < 8) + { + return; + } + instantBuildEnergy = reader.GetDouble(); + hpMaxUpgrade = reader.GetInt(); + energyShieldUnlocked = reader.GetBool(); + energyShieldRadius = reader.GetFloat(); + energyShieldCapacity = reader.GetLong(); + laserEnergyCapacity = reader.GetLong(); + laserLocalAttackRange = reader.GetFloat(); + laserSpaceAttackRange = reader.GetFloat(); + laserLocalEnergyCost = reader.GetInt(); + laserSpaceEnergyCost = reader.GetInt(); + laserLocalDamage = reader.GetInt(); + laserSpaceDamage = reader.GetInt(); + groundFleetCount = reader.GetInt(); + spaceFleetCount = reader.GetInt(); } } diff --git a/NebulaModel/Packets/Players/RemoveDroneOrdersPacket.cs b/NebulaModel/Packets/Players/RemoveDroneOrdersPacket.cs index 8b6be099f..1e9517f87 100644 --- a/NebulaModel/Packets/Players/RemoveDroneOrdersPacket.cs +++ b/NebulaModel/Packets/Players/RemoveDroneOrdersPacket.cs @@ -4,10 +4,12 @@ public class RemoveDroneOrdersPacket { public RemoveDroneOrdersPacket() { } - public RemoveDroneOrdersPacket(int[] queuedEntityIds) + public RemoveDroneOrdersPacket(int[] queuedEntityIds, int planetId) { QueuedEntityIds = queuedEntityIds; + PlanetId = planetId; } public int[] QueuedEntityIds { get; set; } + public int PlanetId { get; set; } } diff --git a/NebulaModel/Packets/Session/LobbyUpdateCombatValues.cs b/NebulaModel/Packets/Session/LobbyUpdateCombatValues.cs new file mode 100644 index 000000000..aa04ba44f --- /dev/null +++ b/NebulaModel/Packets/Session/LobbyUpdateCombatValues.cs @@ -0,0 +1,30 @@ +namespace NebulaModel.Packets.Session +{ + public class LobbyUpdateCombatValues + { + public LobbyUpdateCombatValues() { } + + public LobbyUpdateCombatValues(CombatSettings combatSettings) + { + CombatAggressiveness = combatSettings.aggressiveness; + CombatInitialLevel = combatSettings.initialLevel; + CombatInitialGrowth = combatSettings.initialGrowth; + CombatInitialColonize = combatSettings.initialColonize; + CombatMaxDensity = combatSettings.maxDensity; + CombatGrowthSpeedFactor = combatSettings.growthSpeedFactor; + CombatPowerThreatFactor = combatSettings.powerThreatFactor; + CombatBattleThreatFactor = combatSettings.battleThreatFactor; + CombatBattleExpFactor = combatSettings.battleExpFactor; + } + + public float CombatAggressiveness { get; set; } + public float CombatInitialLevel { get; set; } + public float CombatInitialGrowth { get; set; } + public float CombatInitialColonize { get; set; } + public float CombatMaxDensity { get; set; } + public float CombatGrowthSpeedFactor { get; set; } + public float CombatPowerThreatFactor { get; set; } + public float CombatBattleThreatFactor { get; set; } + public float CombatBattleExpFactor { get; set; } + } +} diff --git a/NebulaModel/Packets/Session/LobbyUpdateValues.cs b/NebulaModel/Packets/Session/LobbyUpdateValues.cs index 3a70129a6..38bd2a825 100644 --- a/NebulaModel/Packets/Session/LobbyUpdateValues.cs +++ b/NebulaModel/Packets/Session/LobbyUpdateValues.cs @@ -4,13 +4,23 @@ public class LobbyUpdateValues { public LobbyUpdateValues() { } - public LobbyUpdateValues(int galaxyAlgo, int galaxySeed, int starCount, float resourceMultiplier, bool isSandboxMode) + public LobbyUpdateValues(int galaxyAlgo, int galaxySeed, int starCount, float resourceMultiplier, bool isSandboxMode, bool isPeaceMode, CombatSettings combatSettings) { GalaxyAlgo = galaxyAlgo; GalaxySeed = galaxySeed; StarCount = starCount; ResourceMultiplier = resourceMultiplier; IsSandboxMode = isSandboxMode; + IsPeaceMode = isPeaceMode; + CombatAggressiveness = combatSettings.aggressiveness; + CombatInitialLevel = combatSettings.initialLevel; + CombatInitialGrowth = combatSettings.initialGrowth; + CombatInitialColonize = combatSettings.initialColonize; + CombatMaxDensity = combatSettings.maxDensity; + CombatGrowthSpeedFactor = combatSettings.growthSpeedFactor; + CombatPowerThreatFactor = combatSettings.powerThreatFactor; + CombatBattleThreatFactor = combatSettings.battleThreatFactor; + CombatBattleExpFactor = combatSettings.battleExpFactor; } public int GalaxyAlgo { get; set; } @@ -18,4 +28,14 @@ public LobbyUpdateValues(int galaxyAlgo, int galaxySeed, int starCount, float re public int StarCount { get; set; } public float ResourceMultiplier { get; set; } public bool IsSandboxMode { get; set; } + public bool IsPeaceMode { get; set; } + public float CombatAggressiveness { get; set; } + public float CombatInitialLevel { get; set; } + public float CombatInitialGrowth { get; set; } + public float CombatInitialColonize { get; set; } + public float CombatMaxDensity { get; set; } + public float CombatGrowthSpeedFactor { get; set; } + public float CombatPowerThreatFactor { get; set; } + public float CombatBattleThreatFactor { get; set; } + public float CombatBattleExpFactor { get; set; } } diff --git a/NebulaModel/Packets/Trash/TrashSystemNewTrashCreatedPacket.cs b/NebulaModel/Packets/Trash/TrashSystemNewTrashCreatedPacket.cs index ef57e20ba..4440d5c86 100644 --- a/NebulaModel/Packets/Trash/TrashSystemNewTrashCreatedPacket.cs +++ b/NebulaModel/Packets/Trash/TrashSystemNewTrashCreatedPacket.cs @@ -31,9 +31,9 @@ public TrashSystemNewTrashCreatedPacket(int trashId, TrashObject trashObj, Trash } public int TrashId { get; set; } - private byte[] TrashObjectByte { get; set; } - private byte[] TrashDataByte { get; set; } - private int Count { get; set; } + public byte[] TrashObjectByte { get; set; } + public byte[] TrashDataByte { get; set; } + public int Count { get; set; } public ushort PlayerId { get; set; } public int LocalPlanetId { get; set; } diff --git a/NebulaNetwork/NebulaNetwork.csproj b/NebulaNetwork/NebulaNetwork.csproj index 380c11046..83dd83165 100644 --- a/NebulaNetwork/NebulaNetwork.csproj +++ b/NebulaNetwork/NebulaNetwork.csproj @@ -1,19 +1,19 @@ - + - + - - - - + + + + - - + + \ No newline at end of file diff --git a/NebulaNetwork/PacketProcessors/Chat/ChatCommandWhoProcessor.cs b/NebulaNetwork/PacketProcessors/Chat/ChatCommandWhoProcessor.cs index 93e6317cc..adf6cf674 100644 --- a/NebulaNetwork/PacketProcessors/Chat/ChatCommandWhoProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Chat/ChatCommandWhoProcessor.cs @@ -19,9 +19,9 @@ internal class ChatCommandWhoProcessor : PacketProcessor { protected override void ProcessPacket(ChatCommandWhoPacket packet, NebulaConnection conn) { - var recipient = Multiplayer.Session.Network.PlayerManager.GetPlayer(conn); if (IsHost) { + var recipient = Multiplayer.Session.Network.PlayerManager.GetPlayer(conn); var playerDatas = Multiplayer.Session.Network.PlayerManager.GetAllPlayerDataIncludingHost(); var hostPlayer = Multiplayer.Session.LocalPlayer; var resultPayload = WhoCommandHandler.BuildResultPayload(playerDatas, hostPlayer); diff --git a/NebulaNetwork/PacketProcessors/Factory/BattleBase/BattleBaseSettingUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/BattleBase/BattleBaseSettingUpdateProcessor.cs new file mode 100644 index 000000000..ad27ecb50 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/BattleBase/BattleBaseSettingUpdateProcessor.cs @@ -0,0 +1,109 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Logger; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.BattleBase; +using NebulaWorld; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.BattleBase; + +[RegisterPacketProcessor] +internal class BattleBaseSettingUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(BattleBaseSettingUpdatePacket packet, NebulaConnection conn) + { + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (IsHost && factory != null) + { + if (packet.Event != BattleBaseSettingEvent.ChangeFleetConfig) + { + // Broadcast to the star system where the event planet is located + Multiplayer.Session.Network.SendPacketToStar(packet, factory.planet.star.id); + } + else + { + // Don't send back packet to original author because ChangeFleetConfig has been called + Multiplayer.Session.Network.SendPacketToStarExclude(packet, factory.planet.star.id, conn); + } + } + + var pool = factory?.defenseSystem.battleBases; + if (pool == null || packet.BattleBaseId <= 0) + { + return; + } + var battleBase = pool[packet.BattleBaseId]; + + switch (packet.Event) + { + case BattleBaseSettingEvent.ChangeMaxChargePower: + factory.powerSystem.consumerPool[battleBase.pcId].workEnergyPerTick = (long)(5000.0 * packet.Arg1 + 0.5); + break; + + case BattleBaseSettingEvent.ToggleDroneEnabled: + battleBase.constructionModule.droneEnabled = packet.Arg1 != 0f; + break; + + case BattleBaseSettingEvent.ChangeDronesPriority: + battleBase.constructionModule.ChangeDronesPriority(factory, packet.Arg1); + break; + + case BattleBaseSettingEvent.ToggleCombatModuleEnabled: + battleBase.combatModule.moduleEnabled = packet.Arg1 != 0f; + break; + + case BattleBaseSettingEvent.ToggleAutoReconstruct: + battleBase.constructionModule.autoReconstruct = packet.Arg1 != 0f; + break; + + case BattleBaseSettingEvent.ToggleAutoPickEnabled: + battleBase.autoPickEnabled = packet.Arg1 != 0f; + break; + + case BattleBaseSettingEvent.ChangeFleetConfig: + // Copy ChangeFleetConfig without storage/inventory interactions + // This may change for future ModuleFleet syncing + const int FleetIndex = 0; + var newConfigId = (int)packet.Arg1; + ref var moduleFleet = ref battleBase.combatModule.moduleFleets[FleetIndex]; + for (var i = 0; i < moduleFleet.fighters.Length; i++) + { + if (moduleFleet.fighters[i].itemId == newConfigId) + { + continue; + } + if (moduleFleet.fighters[i].count > 0) + { + var itemId = 0; + var count = 0; + moduleFleet.TakeFighterFromPort(i, ref itemId, ref count); + } + moduleFleet.SetItemId(i, newConfigId); + } + break; + + case BattleBaseSettingEvent.ToggleAutoReplenishFleet: + battleBase.combatModule.autoReplenishFleet = packet.Arg1 != 0f; + break; + + case BattleBaseSettingEvent.None: + default: + Log.Warn($"BattleBaseSettingEvent: Unhandled BattleBaseSettingEvent {packet.Event}"); + break; + } + + //Update UI window too if the local is viewing the current Battle Base + var battleBaseWindow = UIRoot.instance.uiGame.battleBaseWindow; + if (battleBaseWindow.battleBaseId != packet.BattleBaseId || battleBaseWindow.factory?.planetId != packet.PlanetId) + { + return; + } + battleBaseWindow.eventLock = true; + battleBaseWindow.OnBattleBaseIdChange(); + battleBaseWindow.eventLock = false; + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/BattleBase/NewBattleBaseDroneOrderProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/BattleBase/NewBattleBaseDroneOrderProcessor.cs new file mode 100644 index 000000000..4fb8c9ec4 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/BattleBase/NewBattleBaseDroneOrderProcessor.cs @@ -0,0 +1,69 @@ +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.BattleBase; + +namespace NebulaNetwork.PacketProcessors.Factory.BattleBase; + +[RegisterPacketProcessor] +internal class NewBattleBaseDroneOrderProcessor : PacketProcessor +{ + protected override void ProcessPacket(NewBattleBaseDroneOrderPacket packet, NebulaConnection conn) + { + if (IsHost) + { + // Host knows about all factories and tells clients when to eject drones from battlebases + return; + } + if (packet.PlanetId != GameMain.mainPlayer.planetId) + { + return; + } + + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (factory == null) + { + return; + } + var battleBaseComponent = factory.defenseSystem.battleBases.buffer[packet.Owner]; + var droneEjectEnergy = Configs.freeMode.droneEjectEnergy; + + var dronesBuffer = factory.constructionSystem.drones.buffer; + var cursor = factory.constructionSystem.drones.cursor; + for (var i = 1; i < cursor; i++) + { + ref var drone = ref dronesBuffer[i]; + if (drone.id != i || drone.owner != packet.Owner || + drone.stage != 0) // checking for stage 0 is important to not reuse the same drone again and again. + { + continue; + } + ref var craftData = ref factory.craftPool[drone.craftId]; + + switch (packet.IsConstruction) + { + case true when + factory.constructionSystem.TakeEnoughItemsFromBase(battleBaseComponent, packet.EntityId): + battleBaseComponent.constructionModule.EjectBaseDrone(factory, ref drone, ref craftData, + packet.EntityId); + battleBaseComponent.energy -= (long)droneEjectEnergy; + factory.constructionSystem.constructServing.Add(packet.EntityId); + + return; + case false: + battleBaseComponent.constructionModule.EjectBaseDrone(factory, ref drone, ref craftData, + packet.EntityId); + battleBaseComponent.energy -= (long)droneEjectEnergy; + + /* TODO: needed? + ref EntityData ptr5 = ref factory.entityPool[packet.EntityId]; + CombatStat[] buffer4 = factory.skillSystem.combatStats.buffer; + int combatStatId3 = ptr5.combatStatId; + buffer4[combatStatId3].repairerCount = buffer4[combatStatId3].repairerCount + 1; + */ + break; + } + break; + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Entity/BuildEntityRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Entity/BuildEntityRequestProcessor.cs index 14ad44f47..49bb575a0 100644 --- a/NebulaNetwork/PacketProcessors/Factory/Entity/BuildEntityRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/Entity/BuildEntityRequestProcessor.cs @@ -43,9 +43,6 @@ protected override void ProcessPacket(BuildEntityRequest packet, NebulaConnectio Multiplayer.Session.Factories.AddPlanetTimer(packet.PlanetId); - //Remove building from drone queue - GameMain.mainPlayer.mecha.droneLogic.serving.Remove(-packet.PrebuildId); - // setting specifyPlanet here to avoid accessing a null object (see GPUInstancingManager activePlanet getter) var pData = GameMain.gpuiManager.specifyPlanet; @@ -62,11 +59,6 @@ protected override void ProcessPacket(BuildEntityRequest packet, NebulaConnectio planet.factory.BuildFinally(GameMain.mainPlayer, packet.PrebuildId); GameMain.gpuiManager.specifyPlanet = pData; - if (IsClient) - { - DroneManager.RemoveBuildRequest(-packet.PrebuildId); - } - Multiplayer.Session.Factories.EventFactory = null; Multiplayer.Session.Factories.PacketAuthor = NebulaModAPI.AUTHOR_NONE; Multiplayer.Session.Factories.TargetPlanet = NebulaModAPI.PLANET_NONE; diff --git a/NebulaNetwork/PacketProcessors/Factory/Entity/ExtraInfoUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Entity/ExtraInfoUpdateProcessor.cs new file mode 100644 index 000000000..d77910928 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Entity/ExtraInfoUpdateProcessor.cs @@ -0,0 +1,44 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory; +using NebulaWorld; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Entity; + +[RegisterPacketProcessor] +internal class ExtraInfoUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(ExtraInfoUpdatePacket packet, NebulaConnection conn) + { + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (factory == null) + { + return; + } + + using (Multiplayer.Session.Factories.IsIncomingRequest.On()) + { + if (packet.ObjId < 0) + { + var prebuildId = -packet.ObjId; + if (prebuildId > 0 && prebuildId < factory.prebuildCursor) + { + factory.WriteExtraInfoOnPrebuild(prebuildId, packet.Info); + } + } + else + { + var entityId = packet.ObjId; + if (entityId > 0 && entityId < factory.entityCursor) + { + factory.WriteExtraInfoOnEntity(entityId, packet.Info); + } + } + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/PowerExchanger/PowerExchangerStorageUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/PowerExchanger/PowerExchangerStorageUpdateProcessor.cs index 737141478..40f55d7a2 100644 --- a/NebulaNetwork/PacketProcessors/Factory/PowerExchanger/PowerExchangerStorageUpdateProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/PowerExchanger/PowerExchangerStorageUpdateProcessor.cs @@ -20,7 +20,7 @@ protected override void ProcessPacket(PowerExchangerStorageUpdatePacket packet, { return; } - pool[packet.PowerExchangerIndex].SetEmptyCount(packet.EmptyAccumulatorCount); - pool[packet.PowerExchangerIndex].SetFullCount(packet.FullAccumulatorCount); + pool[packet.PowerExchangerIndex].SetEmptyCount(packet.EmptyAccumulatorCount, packet.Inc); + pool[packet.PowerExchangerIndex].SetFullCount(packet.FullAccumulatorCount, packet.Inc); } } diff --git a/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerChargerUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerChargerUpdateProcessor.cs new file mode 100644 index 000000000..90243c461 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerChargerUpdateProcessor.cs @@ -0,0 +1,63 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.PowerTower; +using NebulaWorld; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.PowerTower; + +[RegisterPacketProcessor] +internal class PowerTowerChargerUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(PowerTowerChargerUpdate packet, NebulaConnection conn) + { + if (packet.PlanetId == -1) + { + // When a player connects, disconnects, or leaves planet, clear all records and restart + Multiplayer.Session.PowerTowers.LocalChargerIds.Clear(); + Multiplayer.Session.PowerTowers.RemoteChargerHashIds.Clear(); + if (IsHost) + { + // Broadcast the leave planet event to other players + Multiplayer.Session.Network.SendPacket(packet); + } + return; + } + + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (factory is not { powerSystem: not null }) + { + return; + } + var hashId = ((long)packet.PlanetId << 32) | (long)packet.NodeId; + if (packet.Charging) + { + if (Multiplayer.Session.PowerTowers.RemoteChargerHashIds.TryGetValue(hashId, out var playerCount)) + { + Multiplayer.Session.PowerTowers.RemoteChargerHashIds[hashId] = playerCount + 1; + } + else + { + Multiplayer.Session.PowerTowers.RemoteChargerHashIds.Add(hashId, 1); + } + NebulaModel.Logger.Log.Debug($"Add remote charger [{packet.PlanetId}-{packet.NodeId}]: {Multiplayer.Session.PowerTowers.RemoteChargerHashIds[hashId]}"); + } + else + { + if (!Multiplayer.Session.PowerTowers.RemoteChargerHashIds.TryGetValue(hashId, out var playerCount)) + { + return; + } + NebulaModel.Logger.Log.Debug($"Remove remote charger [{packet.PlanetId}-{packet.NodeId}]: {Multiplayer.Session.PowerTowers.RemoteChargerHashIds[hashId] - 1}"); + Multiplayer.Session.PowerTowers.RemoteChargerHashIds[hashId] = playerCount - 1; + if (playerCount <= 1) + { + Multiplayer.Session.PowerTowers.RemoteChargerHashIds.Remove(hashId); + } + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadRequestProcessor.cs deleted file mode 100644 index 2ea087d9d..000000000 --- a/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadRequestProcessor.cs +++ /dev/null @@ -1,53 +0,0 @@ -#region - -using NebulaAPI.Packets; -using NebulaModel.Networking; -using NebulaModel.Packets; -using NebulaModel.Packets.Factory.PowerTower; -using NebulaWorld; - -#endregion - -namespace NebulaNetwork.PacketProcessors.Factory.PowerTower; - -[RegisterPacketProcessor] -public class PowerTowerUserLoadingRequestProcessor : PacketProcessor -{ - protected override void ProcessPacket(PowerTowerUserLoadingRequest packet, NebulaConnection conn) - { - if (IsClient) - { - return; - } - - var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; - - if (factory?.powerSystem == null) - { - return; - } - var pNet = factory.powerSystem.netPool[packet.NetId]; - - if (packet.Charging) - { - Multiplayer.Session.PowerTowers.AddExtraDemand(packet.PlanetId, packet.NetId, packet.NodeId, - packet.PowerAmount); - } - else - { - Multiplayer.Session.PowerTowers.RemExtraDemand(packet.PlanetId, packet.NetId, packet.NodeId); - } - - Multiplayer.Session.Network.SendPacketToStar(new PowerTowerUserLoadingResponse(packet.PlanetId, - packet.NetId, - packet.NodeId, - packet.PowerAmount, - pNet.energyCapacity, - pNet.energyRequired, - pNet.energyServed, - pNet.energyAccumulated, - pNet.energyExchanged, - packet.Charging), - GameMain.galaxy.PlanetById(packet.PlanetId).star.id); - } -} diff --git a/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadResponseProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadResponseProcessor.cs deleted file mode 100644 index f5d66d993..000000000 --- a/NebulaNetwork/PacketProcessors/Factory/PowerTower/PowerTowerUserLoadResponseProcessor.cs +++ /dev/null @@ -1,68 +0,0 @@ -#region - -using NebulaAPI.Packets; -using NebulaModel.Networking; -using NebulaModel.Packets; -using NebulaModel.Packets.Factory.PowerTower; -using NebulaWorld; - -#endregion - -namespace NebulaNetwork.PacketProcessors.Factory.PowerTower; - -[RegisterPacketProcessor] -internal class PowerTowerUserLoadResponseProcessor : PacketProcessor -{ - protected override void ProcessPacket(PowerTowerUserLoadingResponse packet, NebulaConnection conn) - { - var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; - if (factory is not { powerSystem: not null }) - { - return; - } - var pNet = factory.powerSystem.netPool[packet.NetId]; - - if (packet.Charging) - { - Multiplayer.Session.PowerTowers.AddExtraDemand(packet.PlanetId, packet.NetId, packet.NodeId, - packet.PowerAmount); - if (IsClient) - { - if (Multiplayer.Session.PowerTowers.DidRequest(packet.PlanetId, packet.NetId, packet.NodeId)) - { - var baseDemand = factory.powerSystem.nodePool[packet.NodeId].workEnergyPerTick - - factory.powerSystem.nodePool[packet.NodeId].idleEnergyPerTick; - var mult = factory.powerSystem.networkServes[packet.NetId]; - Multiplayer.Session.PowerTowers.PlayerChargeAmount += (int)(mult * baseDemand); - } - } - } - else - { - Multiplayer.Session.PowerTowers.RemExtraDemand(packet.PlanetId, packet.NetId, packet.NodeId); - } - - if (IsHost) - { - Multiplayer.Session.Network.SendPacketToStar(new PowerTowerUserLoadingResponse(packet.PlanetId, - packet.NetId, - packet.NodeId, - packet.PowerAmount, - pNet.energyCapacity, - pNet.energyRequired, - pNet.energyServed, - pNet.energyAccumulated, - pNet.energyExchanged, - packet.Charging), - GameMain.galaxy.PlanetById(packet.PlanetId).star.id); - } - else - { - pNet.energyCapacity = packet.EnergyCapacity; - pNet.energyRequired = packet.EnergyRequired; - pNet.energyAccumulated = packet.EnergyAccumulated; - pNet.energyExchanged = packet.EnergyExchanged; - pNet.energyServed = packet.EnergyServed; - } - } -} diff --git a/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncRealtimeChangeProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncRealtimeChangeProcessor.cs index 327dd9289..1803452cf 100644 --- a/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncRealtimeChangeProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncRealtimeChangeProcessor.cs @@ -43,7 +43,8 @@ protected override void ProcessPacket(StorageSyncRealtimeChangePacket packet, Ne case StorageSyncRealtimeChangeEvent.TakeItemFromGrid: storage.TakeItemFromGrid(packet.Length, ref itemId, ref count, out _); break; - case StorageSyncRealtimeChangeEvent.AddItem1: + case StorageSyncRealtimeChangeEvent.AddItem1: //BattleBase AutoPickTrash + storage.AddItem(itemId, count, inc, out _, true); break; case StorageSyncRealtimeChangeEvent.TakeItem: break; diff --git a/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncSetFilterProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncSetFilterProcessor.cs new file mode 100644 index 000000000..d7fc9775b --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncSetFilterProcessor.cs @@ -0,0 +1,43 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Storage; +using NebulaWorld; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Storage; + +[RegisterPacketProcessor] +public class StorageSyncSetFilterProcessor : PacketProcessor +{ + protected override void ProcessPacket(StorageSyncSetFilterPacket packet, NebulaConnection conn) + { + StorageComponent storage = null; + var pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.factoryStorage?.storagePool; + + var poolIsInvalid = pool is null; + if (poolIsInvalid) + { + return; + } + + var storageIsValid = packet.StorageIndex != -1 && packet.StorageIndex < pool.Length; + if (storageIsValid) + { + storage = pool[packet.StorageIndex]; + } + + if (storage is null) + return; + + using (Multiplayer.Session.Storage.IsIncomingRequest.On()) + { + storage.type = packet.StorageType; + storage.SetFilter(packet.GridIndex, packet.FilterId); + storage.NotifyStorageChange(); + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Turret/TurretBurstUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretBurstUpdateProcessor.cs new file mode 100644 index 000000000..d84204c3f --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretBurstUpdateProcessor.cs @@ -0,0 +1,33 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Turret; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Turret; + +[RegisterPacketProcessor] +internal class TurretBurstUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(TurretBurstUpdatePacket packet, NebulaConnection conn) + { + var pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.defenseSystem.turrets; + if (pool == null || packet.TurretIndex == -1) + { + return; + } + UITurretWindow.burstModeIndex = packet.BurstIndex; + + //Update UI Panel too if it is viewing any turret window + var uiTurret = UIRoot.instance.uiGame.turretWindow; + if (uiTurret.factory == null || uiTurret.factory.planetId != packet.PlanetId) + { + return; + } + + uiTurret.RefreshBurstModeUI(); + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Turret/TurretGroupUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretGroupUpdateProcessor.cs new file mode 100644 index 000000000..475ad89b2 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretGroupUpdateProcessor.cs @@ -0,0 +1,28 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Turret; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Turret; + +[RegisterPacketProcessor] +internal class TurretGroupUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(TurretGroupUpdatePacket packet, NebulaConnection conn) + { + var pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.defenseSystem.turrets; + if (pool == null || packet.TurretIndex == -1 || packet.TurretIndex >= pool.buffer.Length) + { + return; + } + var turret = pool.buffer[packet.TurretIndex]; + if (turret.id != -1) + { + turret.SetGroup(packet.Group); + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Turret/TurretPriorityUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretPriorityUpdateProcessor.cs new file mode 100644 index 000000000..e17bb56d9 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretPriorityUpdateProcessor.cs @@ -0,0 +1,24 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Turret; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Turret; + +[RegisterPacketProcessor] +internal class TurretPriorityUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(TurretPriorityUpdatePacket packet, NebulaConnection conn) + { + var pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.defenseSystem.turrets; + if (pool != null && packet.TurretIndex != -1 && packet.TurretIndex < pool.buffer.Length && + pool.buffer[packet.TurretIndex].id != -1) + { + pool.buffer[packet.TurretIndex].vsSettings = packet.VSSettings; + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Turret/TurretStorageUpdateProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretStorageUpdateProcessor.cs new file mode 100644 index 000000000..ac21a7d3f --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretStorageUpdateProcessor.cs @@ -0,0 +1,39 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Turret; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Turret; + +[RegisterPacketProcessor] +internal class TurretStorageUpdateProcessor : PacketProcessor +{ + protected override void ProcessPacket(TurretStorageUpdatePacket packet, NebulaConnection conn) + { + var pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.defenseSystem.turrets; + if (pool == null || packet.TurretIndex == -1 || packet.TurretIndex > pool.buffer.Length || + pool.buffer[packet.TurretIndex].id == -1) + { + return; + } + + if (packet.ItemCount == 0 || packet.ItemId == 0) + { + pool.buffer[packet.TurretIndex].ClearItem(); + } + + if (pool.buffer[packet.TurretIndex].itemId != packet.ItemId) + { + pool.buffer[packet.TurretIndex].SetNewItem(packet.ItemId, packet.ItemCount, packet.ItemInc); + } + else + { + pool.buffer[packet.TurretIndex].itemCount = packet.ItemCount; + pool.buffer[packet.TurretIndex].bulletCount = packet.BulletCount; + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Turret/TurretSuperNovaProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretSuperNovaProcessor.cs new file mode 100644 index 000000000..5bd172743 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Turret/TurretSuperNovaProcessor.cs @@ -0,0 +1,64 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Factory.Turret; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Factory.Turret; + +[RegisterPacketProcessor] +internal class TurretSuperNovaProcessor : PacketProcessor +{ + protected override void ProcessPacket(TurretSuperNovaPacket packet, NebulaConnection conn) + { + var defenseSystem = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.defenseSystem; + var pool = defenseSystem?.turrets; + if (pool == null || packet.TurretIndex == -1 || packet.TurretIndex >= pool.buffer.Length || + pool.buffer[packet.TurretIndex].id == -1) + { + return; + } + //TODO: Evaluate in PR, should I count on other packet, or should I pass through? + var burstModeIndex = UITurretWindow.burstModeIndex; + var inSuperNova = packet.InSuperNova; + + var refTurret = pool.buffer[packet.TurretIndex]; + + switch (burstModeIndex) + { + case 1: + if (inSuperNova) + { + refTurret.SetSupernova(); + } + else + { + refTurret.CancelSupernova(); + } + break; + case 2: + if (inSuperNova) + { + defenseSystem.SetGroupTurretsSupernova(refTurret.group); + } + else + { + defenseSystem.CancelGroupTurretSupernova(refTurret.group); + } + break; + case 3: + if (inSuperNova) + { + defenseSystem.SetGlobalTurretsSupernova(); + } + else + { + defenseSystem.CancelGlobalTurretSupernova(); + } + break; + } + } +} diff --git a/NebulaNetwork/PacketProcessors/GameHistory/GameHistoryResearchContributionProcessor.cs b/NebulaNetwork/PacketProcessors/GameHistory/GameHistoryResearchContributionProcessor.cs index e2a28f757..337a1a3ed 100644 --- a/NebulaNetwork/PacketProcessors/GameHistory/GameHistoryResearchContributionProcessor.cs +++ b/NebulaNetwork/PacketProcessors/GameHistory/GameHistoryResearchContributionProcessor.cs @@ -5,7 +5,6 @@ using NebulaModel.Networking; using NebulaModel.Packets; using NebulaModel.Packets.GameHistory; -using NebulaWorld; #endregion @@ -25,9 +24,6 @@ protected override void ProcessPacket(GameHistoryResearchContributionPacket pack if (packet.TechId == GameMain.history.currentTech) { GameMain.history.AddTechHash(packet.Hashes); - var playerManager = Multiplayer.Session.Network.PlayerManager; - Log.Debug( - $"ProcessPacket researchContribution: playerid by: {playerManager.GetPlayer(conn).Id} - hashes {packet.Hashes}"); } else { diff --git a/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncProcessor.cs b/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncProcessor.cs index b8c0204e2..39457c514 100644 --- a/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncProcessor.cs @@ -38,8 +38,6 @@ protected override void ProcessPacket(ILSgStationPoolSync packet, NebulaConnecti gStationPool[packet.stationGId[i]].shipDockRot = packet.DockRot[i].ToQuaternion(); gStationPool[packet.stationGId[i]].id = packet.stationId[i]; - gStationPool[packet.stationGId[i]].name = - string.IsNullOrEmpty(packet.stationName[i]) ? null : packet.stationName[i]; gStationPool[packet.stationGId[i]].planetId = packet.planetId[i]; gStationPool[packet.stationGId[i]].workShipCount = packet.workShipCount[i]; gStationPool[packet.stationGId[i]].idleShipCount = packet.idleShipCount[i]; @@ -47,7 +45,7 @@ protected override void ProcessPacket(ILSgStationPoolSync packet, NebulaConnecti gStationPool[packet.stationGId[i]].idleShipIndices = packet.idleShipIndices[i]; gStationPool[packet.stationGId[i]].shipRenderers = new ShipRenderingData[packet.stationMaxShipCount[i]]; gStationPool[packet.stationGId[i]].shipUIRenderers = new ShipUIRenderingData[packet.stationMaxShipCount[i]]; - gStationPool[packet.stationGId[i]].storage = Array.Empty(); // zero-length array for mod compatibility + gStationPool[packet.stationGId[i]].storage = []; // zero-length array for mod compatibility gStationPool[packet.stationGId[i]].shipDiskPos = new Vector3[packet.stationMaxShipCount[i]]; gStationPool[packet.stationGId[i]].shipDiskRot = new Quaternion[packet.stationMaxShipCount[i]]; diff --git a/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncRequestProcessor.cs index 23d3bc991..8914dc3be 100644 --- a/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Logistics/ILSgStationPoolSyncRequestProcessor.cs @@ -84,7 +84,6 @@ protected override void ProcessPacket(ILSRequestgStationPoolSync packet, NebulaC stationGId[iter] = stationComponent.gid; stationMaxShipCount[iter] = stationComponent.workShipDatas.Length; stationId[iter] = stationComponent.id; - stationName[iter] = stationComponent.name; DockPos[iter] = new Float3(stationComponent.shipDockPos); DockRot[iter] = new Float4(stationComponent.shipDockRot); planetId[iter] = stationComponent.planetId; diff --git a/NebulaNetwork/PacketProcessors/Planet/FactoryLoadRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Planet/FactoryLoadRequestProcessor.cs index 868e3aecc..ccbf0d243 100644 --- a/NebulaNetwork/PacketProcessors/Planet/FactoryLoadRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Planet/FactoryLoadRequestProcessor.cs @@ -1,5 +1,6 @@ #region +using System.IO; using NebulaAPI.Packets; using NebulaModel.Logger; using NebulaModel.Networking; @@ -27,7 +28,7 @@ protected override void ProcessPacket(FactoryLoadRequest packet, NebulaConnectio using (var writer = new BinaryUtils.Writer()) { - factory.Export(writer.BinaryWriter); + factory.Export(writer.BinaryWriter.BaseStream, writer.BinaryWriter); var data = writer.CloseAndGetBytes(); Log.Info($"Sent {data.Length} bytes of data for PlanetFactory {planet.name} (ID: {planet.id})"); conn.SendPacket(new FragmentInfo(data.Length + planet.data.modData.Length)); diff --git a/NebulaNetwork/PacketProcessors/Players/NewDroneOrderProcessor.cs b/NebulaNetwork/PacketProcessors/Players/NewDroneOrderProcessor.cs index b3c451314..5ec3e98e5 100644 --- a/NebulaNetwork/PacketProcessors/Players/NewDroneOrderProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Players/NewDroneOrderProcessor.cs @@ -1,52 +1,183 @@ #region -using NebulaAPI.GameState; +using System.Net.Sockets; using NebulaAPI.Packets; using NebulaModel.Networking; using NebulaModel.Packets; using NebulaModel.Packets.Players; using NebulaWorld; using NebulaWorld.Player; +using UnityEngine; #endregion namespace NebulaNetwork.PacketProcessors.Players; [RegisterPacketProcessor] -internal class NewDroneOrderProcessor : PacketProcessor +internal class NewDroneOrderProcessor : PacketProcessor { - private readonly IPlayerManager playerManager; - - public NewDroneOrderProcessor() - { - playerManager = Multiplayer.Session.Network.PlayerManager; - } - - protected override void ProcessPacket(NewDroneOrderPacket packet, NebulaConnection conn) + protected override void ProcessPacket(NewMechaDroneOrderPacket packet, NebulaConnection conn) { - // Host does not need to know about flying drones of other players if he is not on the same planet if (IsHost) { - if (GameMain.mainPlayer.planetId != packet.PlanetId) + // Host needs to determine who is closest and who should send out drones. + // clients only send out construction drones in response to this packet. + + DroneManager.RefreshCachedPositions(); + Vector3 initialVector = getInitialVector(packet); + + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (factory == null) { return; } + var entityPos = factory.constructionSystem._obj_hpos(packet.EntityId, ref initialVector); + var closestPlayer = DroneManager.GetClosestPlayerTo(packet.PlanetId, ref entityPos); + var elected = closestPlayer == Multiplayer.Session.LocalPlayer.Id; - var player = playerManager.GetPlayer(conn); - if (player != null) + // only one drone per building allowed + if (DroneManager.IsPendingBuildRequest(packet.EntityId)) { - switch (packet.Stage) + return; + } + if (elected && + GameMain.mainPlayer.mecha.constructionModule.droneIdleCount > 0 && + GameMain.mainPlayer.mecha.constructionModule.droneEnabled && + (DroneManager.IsPrebuildGreen(factory, packet.EntityId) || DroneManager.PlayerHasEnoughItemsForConstruction(factory, packet.EntityId)) && + GameMain.mainPlayer.mecha.CheckEjectConstructionDroneCondition()) + { + informAndEjectLocalDrones(packet, factory, closestPlayer); + } + else if (elected) + { + // we are closest one but we do not have enough drones or they are disabled, or we just have not enough items to make the constructon green, so search next closest player + var nextClosestPlayer = + DroneManager.GetNextClosestPlayerToAfter(packet.PlanetId, closestPlayer, ref entityPos); + if (nextClosestPlayer == closestPlayer) { - case 1 or 2: - DroneManager.AddPlayerDronePlan(player.Id, packet.EntityId); + // there is no other one to ask so wait and do nothing. + return; + } + + informAndEjectRemoteDrones(packet, factory, nextClosestPlayer); + } + else if (!elected) + { + informAndEjectRemoteDrones(packet, factory, closestPlayer); + } + } + else + { + var elected = packet.PlayerId == Multiplayer.Session.LocalPlayer.Id; + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + var playerCanBuildIt = factory == null ? false : (DroneManager.IsPrebuildGreen(factory, packet.EntityId) || DroneManager.PlayerHasEnoughItemsForConstruction(factory, packet.EntityId)); + var ejectConstructionConditionsGiven = GameMain.mainPlayer.mecha.CheckEjectConstructionDroneCondition(); + + switch (elected) + { + case false: + { + // remove drone order request if not elected. + DroneManager.RemoveBuildRequest(packet.EntityId); + + // only render other drones when on same planet + if (packet.PlanetId != GameMain.mainPlayer.planetId) + { + return; + } + + // now spawn drones of other player, this is only visual to avoid having buildings popping up without any construction drone. + if (factory != null) + { + DroneManager.EjectDronesOfOtherPlayer(packet.PlayerId, packet.PlanetId, packet.EntityId); + factory.constructionSystem.constructServing.Add(packet.EntityId); + } break; - case 3: - DroneManager.RemovePlayerDronePlan(player.Id, packet.EntityId); + } + case true when GameMain.mainPlayer.mecha.constructionModule.droneIdleCount > 0 && GameMain.mainPlayer.mecha.constructionModule.droneEnabled && playerCanBuildIt && ejectConstructionConditionsGiven: + { + // we should send out drones, so do it. + + if (factory != null) + { + factory.constructionSystem.TakeEnoughItemsFromPlayer(packet.EntityId); + GameMain.mainPlayer.mecha.constructionModule.EjectMechaDrone(factory, GameMain.mainPlayer, + packet.EntityId, + packet.Priority); + factory.constructionSystem.constructServing.Add(packet.EntityId); + } break; - } + } + case true when GameMain.mainPlayer.mecha.constructionModule.droneIdleCount <= 0 || !GameMain.mainPlayer.mecha.constructionModule.droneEnabled || !playerCanBuildIt || !ejectConstructionConditionsGiven: + // remove drone order request if we cant handle it + DroneManager.RemoveBuildRequest(packet.EntityId); + + // others need to remove drones that are rendered for us. + Multiplayer.Session.Network.SendPacketToLocalPlanet(new RemoveDroneOrdersPacket([packet.EntityId], packet.PlanetId)); + break; } + + // TODO: what about these from IdleDroneProcedure() + + /* + ref EntityData ptr = ref factory.entityPool[num2]; + CombatStat[] buffer = factory.skillSystem.combatStats.buffer; + int combatStatId = ptr.combatStatId; + buffer[combatStatId].repairerCount = buffer[combatStatId].repairerCount + 1; + */ + } + } + + private Vector3 getInitialVector(NewMechaDroneOrderPacket packet) + { + Vector3 vector; + + if (GameMain.mainPlayer.planetId == packet.PlanetId) + { + vector = GameMain.mainPlayer.position.normalized * (GameMain.mainPlayer.position.magnitude + 2.8f); + } + else + { + var playerPos = DroneManager.GetPlayerPosition(packet.PlayerId); + + vector = playerPos.normalized * (playerPos.magnitude + 2.8f); } - Multiplayer.Session.World.UpdateRemotePlayerDrone(packet); + return vector; + } + + private void informAndEjectRemoteDrones(NewMechaDroneOrderPacket packet, PlanetFactory factory, ushort closestPlayerId) + { + DroneManager.AddBuildRequest(packet.EntityId); + DroneManager.AddPlayerDronePlan(closestPlayerId, packet.EntityId); + + // tell players to send out drones + Multiplayer.Session.Network.SendPacketToPlanet( + new NewMechaDroneOrderPacket(packet.PlanetId, packet.EntityId, closestPlayerId, packet.Priority), + packet.PlanetId); + factory.constructionSystem.constructServing.Add(packet.EntityId); + + // only render other drones when on same planet + if (packet.PlanetId == GameMain.mainPlayer.planetId) + { + // dont turn white prebuilds green here as drone plans can be rewoked by remote players and then we cant tell if it was a green or white prebuild. + DroneManager.EjectDronesOfOtherPlayer(closestPlayerId, packet.PlanetId, packet.EntityId); + } + } + + private void informAndEjectLocalDrones(NewMechaDroneOrderPacket packet, PlanetFactory factory, ushort closestPlayerId) + { + DroneManager.AddBuildRequest(packet.EntityId); + DroneManager.AddPlayerDronePlan(closestPlayerId, packet.EntityId); + + // tell players to send out drones + Multiplayer.Session.Network.SendPacketToPlanet( + new NewMechaDroneOrderPacket(packet.PlanetId, packet.EntityId, closestPlayerId, packet.Priority), + packet.PlanetId); + + factory.constructionSystem.TakeEnoughItemsFromPlayer(packet.EntityId); + GameMain.mainPlayer.mecha.constructionModule.EjectMechaDrone(factory, GameMain.mainPlayer, packet.EntityId, + packet.Priority); + factory.constructionSystem.constructServing.Add(packet.EntityId); } } diff --git a/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs b/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs index 215aa91e3..95c4b59b4 100644 --- a/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs @@ -16,24 +16,33 @@ protected override void ProcessPacket(PlayerSandCount packet, NebulaConnection c { if (IsHost) { - // when receive update request, host UpdateSyncedSandCount and send to other players - GameMain.mainPlayer.SetSandCount(packet.SandCount); + if (!packet.IsDelta) + { + // when receive update request, host UpdateSyncedSandCount and send to other players + GameMain.mainPlayer.SetSandCount(packet.SandCount); + } return; } // taken from Player.SetSandCount() - var sandCount = packet.SandCount; var player = GameMain.mainPlayer; - - if (sandCount > 1000000000) + var originalSandCount = player.sandCount; + if (packet.IsDelta) + { + player.sandCount += packet.SandCount; + } + else { - sandCount = 1000000000; + var sandCount = packet.SandCount; + if (sandCount > 1000000000) + { + sandCount = 1000000000; + } + player.sandCount = sandCount; } - var num = sandCount - player.sandCount; - player.sandCount = sandCount; - if (num != 0) + if (player.sandCount != originalSandCount) { - UIRoot.instance.uiGame.OnSandCountChanged(player.sandCount, num); + UIRoot.instance.uiGame.OnSandCountChanged(player.sandCount, player.sandCount - originalSandCount); } } } diff --git a/NebulaNetwork/PacketProcessors/Players/RemoveDroneOrdersProcessor.cs b/NebulaNetwork/PacketProcessors/Players/RemoveDroneOrdersProcessor.cs index 277b2532e..4add7b478 100644 --- a/NebulaNetwork/PacketProcessors/Players/RemoveDroneOrdersProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Players/RemoveDroneOrdersProcessor.cs @@ -1,9 +1,13 @@ #region +using System.Linq; using NebulaAPI.Packets; using NebulaModel.Networking; using NebulaModel.Packets; using NebulaModel.Packets.Players; +using NebulaWorld; +using NebulaWorld.Player; +using UnityEngine; #endregion @@ -18,9 +22,104 @@ protected override void ProcessPacket(RemoveDroneOrdersPacket packet, NebulaConn { return; } - foreach (var t in packet.QueuedEntityIds) + + if (IsHost) { - GameMain.mainPlayer.mecha.droneLogic.serving.Remove(t); + // host needs to remove targets from DroneManager + // but he also needs to RecycleDrone any rendered drone of this player + // and as clients only send this when they are unable to handle a NewDroneOrder the host should search for the next closest player to ask for construction. + var player = Multiplayer.Session.Network.PlayerManager.GetPlayer(conn); + var factory = GameMain.galaxy.PlanetById(player.Data.LocalPlanetId)?.factory; + Vector3 vector; + + DroneManager.RefreshCachedPositions(); // refresh position cache + if (GameMain.mainPlayer.planetId == player.Data.LocalPlanetId) + { + vector = GameMain.mainPlayer.position.normalized * (GameMain.mainPlayer.position.magnitude + 2.8f); + } + else + { + var playerPos = DroneManager.GetPlayerPosition(player.Id); + + vector = playerPos.normalized * (playerPos.magnitude + 2.8f); + } + + foreach (var targetObjectId in packet.QueuedEntityIds) + { + DroneManager.RemoveBuildRequest(targetObjectId); + DroneManager.RemovePlayerDronePlan(player.Id, targetObjectId); + if (factory == null) + { + continue; + } + factory.constructionSystem.constructServing.Remove(targetObjectId); // in case it was a construction drone. + + if (GameMain.mainPlayer.planetId == player.Data.LocalPlanetId) + { + for (var i = 1; i < factory.constructionSystem.drones.cursor; i++) + { + ref var drone = ref factory.constructionSystem.drones.buffer[i]; + if (drone.owner < 0 && packet.QueuedEntityIds.Contains(drone.targetObjectId)) + { + GameMain.mainPlayer.mecha.constructionModule.RecycleDrone(factory, ref drone); + } + } + } + + var entityPos = factory.constructionSystem._obj_hpos(targetObjectId, ref vector); + var nextClosestPlayer = + DroneManager.GetNextClosestPlayerToAfter(player.Data.LocalPlanetId, player.Id, ref entityPos); + + if (nextClosestPlayer == player.Id) + { + continue; + } + DroneManager.AddBuildRequest(targetObjectId); + DroneManager.AddPlayerDronePlan(nextClosestPlayer, targetObjectId); + + // tell players to send out drones + Multiplayer.Session.Network.SendPacketToPlanet( + new NewMechaDroneOrderPacket(player.Data.LocalPlanetId, targetObjectId, + nextClosestPlayer, /*TODO: rip*/true), player.Data.LocalPlanetId); + factory.constructionSystem.constructServing.Add(targetObjectId); + + // only render other drones when on same planet + if (player.Data.LocalPlanetId == GameMain.mainPlayer.planetId) + { + DroneManager.EjectDronesOfOtherPlayer(nextClosestPlayer, player.Data.LocalPlanetId, targetObjectId); + } + } + } + else + { + // check if there are any drones on the current planet and match the targets from this packet. + // if so recycle them. overflown drones are handled by RecycleDrone_Postfix + var factory = GameMain.mainPlayer.factory; + + if (factory == null || GameMain.mainPlayer.planetId != packet.PlanetId) + { + return; + } + for (var i = 1; i < factory.constructionSystem.drones.cursor; i++) + { + ref var drone = ref factory.constructionSystem.drones.buffer[i]; + switch (drone.owner) + { + case <= 0 when packet.QueuedEntityIds.Contains(drone.targetObjectId): + GameMain.mainPlayer.mecha.constructionModule.RecycleDrone(factory, ref drone); + break; + case > 0 when packet.QueuedEntityIds.Contains(drone.targetObjectId): + { + var battleBaseComponent = factory.defenseSystem.battleBases.buffer[drone.owner]; + battleBaseComponent.constructionModule.RecycleDrone(factory, ref drone); + break; + } + } + + DroneManager.RemoveBuildRequest(drone.targetObjectId); + factory.constructionSystem.constructServing + .Remove(drone.targetObjectId); // in case it was a construction drone. + } } } } diff --git a/NebulaNetwork/PacketProcessors/Session/LobbyUpdateCombatValuesProcessor.cs b/NebulaNetwork/PacketProcessors/Session/LobbyUpdateCombatValuesProcessor.cs new file mode 100644 index 000000000..ced45e03e --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Session/LobbyUpdateCombatValuesProcessor.cs @@ -0,0 +1,40 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Session; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Session +{ + [RegisterPacketProcessor] + internal class LobbyUpdateCombatValuesProcessor:PacketProcessor + { + protected override void ProcessPacket(LobbyUpdateCombatValues packet, NebulaConnection conn) + { + if (IsHost) + { + return; + } + + var gameDesc = UIRoot.instance.galaxySelect.gameDesc; + if (!gameDesc.isPeaceMode) + { + gameDesc.combatSettings.aggressiveness = packet.CombatAggressiveness; + gameDesc.combatSettings.initialLevel = packet.CombatInitialLevel; + gameDesc.combatSettings.initialGrowth = packet.CombatInitialGrowth; + gameDesc.combatSettings.initialColonize = packet.CombatInitialColonize; + gameDesc.combatSettings.maxDensity = packet.CombatMaxDensity; + gameDesc.combatSettings.growthSpeedFactor = packet.CombatGrowthSpeedFactor; + gameDesc.combatSettings.powerThreatFactor = packet.CombatPowerThreatFactor; + gameDesc.combatSettings.battleThreatFactor = packet.CombatBattleThreatFactor; + gameDesc.combatSettings.battleExpFactor = packet.CombatBattleExpFactor; + } + + UIRoot.instance.galaxySelect.gameDesc = gameDesc; + UIRoot.instance.galaxySelect.SetStarmapGalaxy(); + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Session/LobbyUpdateValuesProcessor.cs b/NebulaNetwork/PacketProcessors/Session/LobbyUpdateValuesProcessor.cs index 91fd8c5ae..5706507fd 100644 --- a/NebulaNetwork/PacketProcessors/Session/LobbyUpdateValuesProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Session/LobbyUpdateValuesProcessor.cs @@ -22,6 +22,19 @@ protected override void ProcessPacket(LobbyUpdateValues packet, NebulaConnection var gameDesc = new GameDesc(); gameDesc.SetForNewGame(packet.GalaxyAlgo, packet.GalaxySeed, packet.StarCount, 1, packet.ResourceMultiplier); gameDesc.isSandboxMode = packet.IsSandboxMode; + gameDesc.isPeaceMode = packet.IsPeaceMode; + if (!packet.IsPeaceMode) + { + gameDesc.combatSettings.aggressiveness = packet.CombatAggressiveness; + gameDesc.combatSettings.initialLevel = packet.CombatInitialLevel; + gameDesc.combatSettings.initialGrowth = packet.CombatInitialGrowth; + gameDesc.combatSettings.initialColonize = packet.CombatInitialColonize; + gameDesc.combatSettings.maxDensity = packet.CombatMaxDensity; + gameDesc.combatSettings.growthSpeedFactor = packet.CombatGrowthSpeedFactor; + gameDesc.combatSettings.powerThreatFactor = packet.CombatPowerThreatFactor; + gameDesc.combatSettings.battleThreatFactor = packet.CombatBattleThreatFactor; + gameDesc.combatSettings.battleExpFactor = packet.CombatBattleExpFactor; + } UIRoot.instance.galaxySelect.gameDesc = gameDesc; UIRoot.instance.galaxySelect.SetStarmapGalaxy(); diff --git a/NebulaNetwork/PacketProcessors/Statistics/StatisticsDataProcessor.cs b/NebulaNetwork/PacketProcessors/Statistics/StatisticsDataProcessor.cs index 9d962cf27..9de85c9c4 100644 --- a/NebulaNetwork/PacketProcessors/Statistics/StatisticsDataProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Statistics/StatisticsDataProcessor.cs @@ -1,5 +1,6 @@ #region +using System.IO; using NebulaAPI.Packets; using NebulaModel.Networking; using NebulaModel.Packets; diff --git a/NebulaNetwork/PacketProcessors/Statistics/StatisticsRequestEventProcessor.cs b/NebulaNetwork/PacketProcessors/Statistics/StatisticsRequestEventProcessor.cs index 0264a8471..a69ce6682 100644 --- a/NebulaNetwork/PacketProcessors/Statistics/StatisticsRequestEventProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Statistics/StatisticsRequestEventProcessor.cs @@ -1,6 +1,7 @@ #region using System; +using System.IO; using NebulaAPI.GameState; using NebulaAPI.Packets; using NebulaModel.Networking; diff --git a/NebulaNetwork/PlayerManager.cs b/NebulaNetwork/PlayerManager.cs index 8ccc35f1b..2ba6545c4 100644 --- a/NebulaNetwork/PlayerManager.cs +++ b/NebulaNetwork/PlayerManager.cs @@ -151,7 +151,8 @@ public INebulaPlayer GetSyncingPlayer(INebulaConnection conn) { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalStarId == GameMain.data.localStar?.id)) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => player.Data.LocalStarId == GameMain.data.localStar?.id)) { player.SendPacket(packet); } @@ -162,7 +163,8 @@ public INebulaPlayer GetSyncingPlayer(INebulaConnection conn) { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalPlanetId == GameMain.data.mainPlayer.planetId)) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => player.Data.LocalPlanetId == GameMain.data.mainPlayer.planetId)) { player.SendPacket(packet); } @@ -173,7 +175,8 @@ public INebulaPlayer GetSyncingPlayer(INebulaConnection conn) { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalPlanetId == planetId)) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => player.Data.LocalPlanetId == planetId)) { player.SendPacket(packet); } @@ -195,7 +198,8 @@ public INebulaPlayer GetSyncingPlayer(INebulaConnection conn) { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalStarId == starId && player != GetPlayer(exclude))) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => player.Data.LocalStarId == starId && player != GetPlayer(exclude))) { player.SendPacket(packet); } @@ -206,7 +210,8 @@ public void SendRawPacketToStar(byte[] rawPacket, int starId, INebulaConnection { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalStarId == starId && !player.Connection.Equals(sender))) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => player.Data.LocalStarId == starId && !player.Connection.Equals(sender))) { player.Connection.SendRawPacket(rawPacket); } @@ -217,7 +222,8 @@ public void SendRawPacketToPlanet(byte[] rawPacket, int planetId, INebulaConnect { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => player.Data.LocalPlanetId == planetId && !player.Connection.Equals(sender))) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => + player.Data.LocalPlanetId == planetId && !player.Connection.Equals(sender))) { player.Connection.SendRawPacket(rawPacket); } @@ -228,7 +234,8 @@ public void SendRawPacketToPlanet(byte[] rawPacket, int planetId, INebulaConnect { using (GetConnectedPlayers(out var connectedPlayers)) { - foreach (var player in connectedPlayers.Select(kvp => kvp.Value).Where(player => !player.Connection.Equals(exclude))) + foreach (var player in connectedPlayers.Select(kvp => kvp.Value) + .Where(player => !player.Connection.Equals(exclude))) { player.SendPacket(packet); } @@ -319,20 +326,30 @@ public void PlayerDisconnected(INebulaConnection conn) Multiplayer.Session.DysonSpheres.UnRegisterPlayer(conn); //Notify players about queued building plans for drones - var DronePlans = DroneManager.GetPlayerDronePlans(player.Id); - if (DronePlans is { Length: > 0 } && player.Data.LocalPlanetId > 0) + var dronePlans = DroneManager.GetPlayerDronePlans(player.Id); + if (dronePlans is { Length: > 0 } && player.Data.LocalPlanetId > 0) { - Multiplayer.Session.Network.SendPacketToPlanet(new RemoveDroneOrdersPacket(DronePlans), + Multiplayer.Session.Network.SendPacketToPlanet(new RemoveDroneOrdersPacket(dronePlans, player.Data.LocalPlanetId), player.Data.LocalPlanetId); //Remove it also from host queue, if host is on the same planet if (GameMain.mainPlayer.planetId == player.Data.LocalPlanetId) { - foreach (var t in DronePlans) + var factory = GameMain.galaxy.PlanetById(player.Data.LocalPlanetId).factory; + for (var i = 1; i < factory.constructionSystem.drones.cursor; i++) { - GameMain.mainPlayer.mecha.droneLogic.serving.Remove(t); + ref var drone = ref factory.constructionSystem.drones.buffer[i]; + if (!dronePlans.Contains(drone.targetObjectId)) + { + continue; + } + // recycle drones from other player, removing them visually + // RecycleDrone_Postfix takes care of removing drones from mecha that do not belong to us + DroneManager.RemoveBuildRequest(drone.targetObjectId); + GameMain.mainPlayer.mecha.constructionModule.RecycleDrone(factory, ref drone); } } } + DroneManager.RemovePlayerDronePlans(player.Id); if (!playerWasSyncing || syncCount != 0) { @@ -361,6 +378,13 @@ public void PlayerDisconnected(INebulaConnection conn) UIRoot.instance.uiGame.OnSandCountChanged(GameMain.mainPlayer.sandCount, GameMain.mainPlayer.sandCount - Multiplayer.Session.LocalPlayer.Data.Mecha.SandCount); Multiplayer.Session.Network.SendPacket(new PlayerSandCount(GameMain.mainPlayer.sandCount)); + + // and we need to fix the now invalid PlayerDronePlans + using (GetConnectedPlayers(out var connectedPlayers)) + { + var allPlayerIds = connectedPlayers.Select(entry => entry.Value.Id).ToList(); + DroneManager.RemoveOrphanDronePlans(allPlayerIds); + } } } @@ -421,9 +445,9 @@ public void UpdateSyncedSandCount(int deltaSandCount) private sealed class ThreadSafe { internal readonly Queue availablePlayerIds = new(); - internal readonly Dictionary connectedPlayers = new(); - internal readonly Dictionary pendingPlayers = new(); - internal readonly Dictionary savedPlayerData = new(); - internal readonly Dictionary syncingPlayers = new(); + internal readonly Dictionary connectedPlayers = []; + internal readonly Dictionary pendingPlayers = []; + internal readonly Dictionary savedPlayerData = []; + internal readonly Dictionary syncingPlayers = []; } } diff --git a/NebulaNetwork/SaveManager.cs b/NebulaNetwork/SaveManager.cs index 87aac7c5d..b0476e6e6 100644 --- a/NebulaNetwork/SaveManager.cs +++ b/NebulaNetwork/SaveManager.cs @@ -15,7 +15,7 @@ namespace NebulaNetwork; public static class SaveManager { private const string FILE_EXTENSION = ".server"; - private const ushort REVISION = 7; + private const ushort REVISION = 8; public static void SaveServerData(string saveName) { @@ -112,7 +112,7 @@ public static void LoadServerData() Log.Info($"Loading server data revision {revision} (Latest {REVISION})"); if (revision != REVISION) { - // Supported revision: 5~7 + // Supported revision: 5~8 if (revision is < 5 or > REVISION) { throw new Exception(); @@ -136,7 +136,7 @@ public static void LoadServerData() switch (revision) { case REVISION: - playerData = netDataReader.Get(); + playerData = netDataReader.Get(() => new PlayerData()); break; case >= 5: playerData = new PlayerData(); diff --git a/NebulaNetwork/Server.cs b/NebulaNetwork/Server.cs index 617276e47..9bd113d70 100644 --- a/NebulaNetwork/Server.cs +++ b/NebulaNetwork/Server.cs @@ -304,12 +304,12 @@ private static void DisableNagleAlgorithm(WebSocketServer socketServer) private class WebSocketService : WebSocketBehavior { public static IPlayerManager PlayerManager; - public static NetPacketProcessor PacketProcessor; + public static NebulaNetPacketProcessor PacketProcessor; private static readonly Dictionary ConnectionDictionary = new(); public WebSocketService() { } - public WebSocketService(IPlayerManager playerManager, NetPacketProcessor packetProcessor) + public WebSocketService(IPlayerManager playerManager, NebulaNetPacketProcessor packetProcessor) { PlayerManager = playerManager; PacketProcessor = packetProcessor; diff --git a/NebulaPatcher/NebulaPlugin.cs b/NebulaPatcher/NebulaPlugin.cs index c5bdce6fe..18379bbf4 100644 --- a/NebulaPatcher/NebulaPlugin.cs +++ b/NebulaPatcher/NebulaPlugin.cs @@ -156,7 +156,7 @@ private void Awake() { if (!batchmode) { - Log.Warn("Dedicate server should start with -batchmode argument"); + Log.Warn("Dedicated server should be started with -batchmode argument"); } } @@ -328,7 +328,7 @@ private static void InitPatches() Log.Error("Unhandled exception occurred while patching the game:", ex); // Show error in UIFatalErrorTip to inform normal users Harmony.CreateAndPatchAll(typeof(UIFatalErrorTip_Patch)); - Log.Error("Nebula Multiplayer Mod is incompatible\nUnhandled exception occurred while patching the game."); + Log.Error("Nebula Multiplayer Mod is incompatible with game version\nUnhandled exception occurred while patching the game."); } } diff --git a/NebulaPatcher/Patches/Dynamic/BuildTool_Common_Patch.cs b/NebulaPatcher/Patches/Dynamic/BuildTool_Common_Patch.cs index 0c688a387..6ddb09bcd 100644 --- a/NebulaPatcher/Patches/Dynamic/BuildTool_Common_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/BuildTool_Common_Patch.cs @@ -29,7 +29,6 @@ public static bool CreatePrebuilds_Prefix(BuildTool __instance) var previews = __instance switch { BuildTool_BlueprintPaste bpInstance => bpInstance.bpPool.Take(bpInstance.bpCursor).ToList(), - BuildTool_Addon addon => [addon.handbp], _ => __instance.buildPreviews }; diff --git a/NebulaPatcher/Patches/Dynamic/ConstructionModuleComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/ConstructionModuleComponent_Patch.cs new file mode 100644 index 000000000..8aed64676 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/ConstructionModuleComponent_Patch.cs @@ -0,0 +1,37 @@ +using HarmonyLib; +using NebulaWorld; + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(ConstructionModuleComponent))] +internal class ConstructionModuleComponent_Patch +{ + // dont give back idle construction drones to player if it was a drone owned by a remote player + [HarmonyPostfix] + [HarmonyPatch(nameof(ConstructionModuleComponent.RecycleDrone))] + public static void RecycleDrone_Postfix(ConstructionModuleComponent __instance, ref DroneComponent drone) + { + if (!Multiplayer.IsActive) + { + return; + } + + if (drone.owner < 0 && drone.owner * -1 != Multiplayer.Session.LocalPlayer.Id || __instance.droneIdleCount > __instance.droneCount) + { + __instance.droneIdleCount--; + } + } + + // clients should skip the procedure for BattleBases. The host will tell them when to eject drones. + [HarmonyPrefix] + [HarmonyPatch(nameof(ConstructionModuleComponent.IdleDroneProcedure))] + public static bool IdleDroneProcedure_Prefix(ConstructionModuleComponent __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + + return !(Multiplayer.Session.LocalPlayer.IsClient && __instance.entityId > 0); + } +} diff --git a/NebulaPatcher/Patches/Dynamic/DroneComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/DroneComponent_Patch.cs new file mode 100644 index 000000000..4c725e249 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/DroneComponent_Patch.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using HarmonyLib; +using NebulaWorld; +using NebulaWorld.Player; +using UnityEngine; + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch] +internal class DroneComponent_Patch +{ + [HarmonyPatch] + class Get_InternalUpdate + { + [HarmonyTargetMethod] + public static MethodBase GetTargetMethod() + { + return AccessTools.Method( + typeof(DroneComponent), + "InternalUpdate", + [ + typeof(CraftData).MakeByRefType(), + typeof(PlanetFactory), + typeof(Vector3).MakeByRefType(), + typeof(float), + typeof(float), + typeof(double).MakeByRefType(), + typeof(double).MakeByRefType(), + typeof(double), + typeof(double), + typeof(float).MakeByRefType() + ]); + } + + // Update the position of the start/return point of construction drones that are owned by other players. The game would default to the local player position if not patched. + [HarmonyPrefix] + public static void InternalUpdate(DroneComponent __instance, ref Vector3 ejectPos, out float energyRatio) + { + energyRatio = 1f; // original method does this at the beginning anyways. + + if (!Multiplayer.IsActive) + { + return; + } + + if (__instance.owner >= 0) + { + return; + } + // very inefficient, better update this in the background elsewhere + DroneManager.RefreshCachedPositions(); + ejectPos = DroneManager.GetPlayerPosition((ushort)(__instance.owner * -1)); // player id is stored in owner to retrieve the current position when drones are updated. + ejectPos = ejectPos.normalized * (ejectPos.magnitude + 2.8f); // drones return to the head of mecha not feet + } + } +} \ No newline at end of file diff --git a/NebulaPatcher/Patches/Dynamic/DysonSphere_Patch.cs b/NebulaPatcher/Patches/Dynamic/DysonSphere_Patch.cs index 2f6fd9564..a2af15054 100644 --- a/NebulaPatcher/Patches/Dynamic/DysonSphere_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/DysonSphere_Patch.cs @@ -41,7 +41,7 @@ public static bool BeforeGameTick_Prefix(DysonSphere __instance, long times) if (nodePool[j] != null && nodePool[j].id == j) { dysonSphereLayer.energyGenCurrentTick += nodePool[j] - .EnergyGenCurrentTick(__instance.energyGenPerNode, __instance.energyGenPerFrame, 0L); + .EnergyGenCurrentTick((int)__instance.energyGenPerNode, (int)__instance.energyGenPerFrame); } } for (var k = 1; k < dysonSphereLayer.shellCursor; k++) diff --git a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs index 78abb460b..d780b4a84 100644 --- a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs @@ -1,6 +1,7 @@ #region using System; +using System.IO; using System.Threading; using System.Threading.Tasks; using HarmonyLib; @@ -67,7 +68,7 @@ public static bool GetOrCreateFactory_Prefix(GameData __instance, ref PlanetFact __instance.factories[factoryIndex] = new PlanetFactory(); try { - __instance.factories[factoryIndex].Import(factoryIndex, __instance, reader.BinaryReader); + __instance.factories[factoryIndex].Import(factoryIndex, __instance, reader.BinaryReader.BaseStream, reader.BinaryReader); } catch (InvalidOperationException e) { @@ -81,7 +82,7 @@ public static bool GetOrCreateFactory_Prefix(GameData __instance, ref PlanetFact factoryIndex = planet.factoryIndex; try { - __instance.factories[factoryIndex].Import(factoryIndex, __instance, reader.BinaryReader); + __instance.factories[factoryIndex].Import(factoryIndex, __instance, reader.BinaryReader.BaseStream, reader.BinaryReader); } catch (InvalidOperationException e) { @@ -276,7 +277,7 @@ public static void SetForNewGame_Postfix(GameData __instance) var planet = __instance.galaxy.PlanetById(Multiplayer.Session.LocalPlayer.Data.LocalPlanetId); __instance.ArrivePlanet(planet); } - else if (UIVirtualStarmap_Transpiler.customBirthPlanet == -1) + else if (UIVirtualStarmap_Transpiler.CustomBirthPlanet == -1) { StarData nearestStar = null; PlanetData nearestPlanet = null; @@ -291,7 +292,7 @@ public static void SetForNewGame_Postfix(GameData __instance) } else { - var planet = __instance.galaxy.PlanetById(UIVirtualStarmap_Transpiler.customBirthPlanet); + var planet = __instance.galaxy.PlanetById(UIVirtualStarmap_Transpiler.CustomBirthPlanet); __instance.ArrivePlanet(planet); } } @@ -416,7 +417,9 @@ public static void LeavePlanet_Prefix() //Players should clear the list of drone orders of other players when they leave the planet if (Multiplayer.IsActive) { - GameMain.mainPlayer.mecha.droneLogic.serving.Clear(); + Multiplayer.Session.PowerTowers.ResetAndBroadcast(); + //todo:replace + //GameMain.mainPlayer.mecha.droneLogic.serving.Clear(); } } @@ -424,7 +427,7 @@ public static void LeavePlanet_Prefix() [HarmonyPatch(nameof(GameData.DetermineLocalPlanet))] public static bool DetermineLocalPlanet_Prefix(ref bool __result) { - if (UIVirtualStarmap_Transpiler.customBirthPlanet == -1 || !Multiplayer.IsActive || Multiplayer.Session.IsGameLoaded) + if (UIVirtualStarmap_Transpiler.CustomBirthPlanet == -1 || !Multiplayer.IsActive || Multiplayer.Session.IsGameLoaded) { return true; } diff --git a/NebulaPatcher/Patches/Dynamic/GameMain_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameMain_Patch.cs index 610adf3ac..7e755dc84 100644 --- a/NebulaPatcher/Patches/Dynamic/GameMain_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameMain_Patch.cs @@ -23,4 +23,15 @@ public static void HandleApplicationQuit_Prefix() } DiscordManager.Cleanup(); } + + [HarmonyPostfix] + [HarmonyPatch(nameof(GameMain.DetermineGameTickRate))] + public static void DetermineGameTickRate_Postfix(ref int __result) + { + // If in multiplayer game and Multiplayer.Session.CanPause is false, prevent the pause + if (Multiplayer.Session != null && Multiplayer.Session.CanPause == false) + { + __result = 1; + } + } } diff --git a/NebulaPatcher/Patches/Dynamic/MechaDroneLogic_Patch.cs b/NebulaPatcher/Patches/Dynamic/MechaDroneLogic_Patch.cs deleted file mode 100644 index 1ff4c4577..000000000 --- a/NebulaPatcher/Patches/Dynamic/MechaDroneLogic_Patch.cs +++ /dev/null @@ -1,23 +0,0 @@ -#region - -using HarmonyLib; -using NebulaWorld; -using NebulaWorld.Player; - -#endregion - -namespace NebulaPatcher.Patches.Dynamic; - -[HarmonyPatch(typeof(MechaDroneLogic))] -internal class MechaDroneLogic_Patch -{ - [HarmonyPrefix] - [HarmonyPatch(nameof(MechaDroneLogic.UpdateTargets))] - public static void UpdateTargets_Prefix() - { - if (Multiplayer.IsActive) - { - DroneManager.ClearCachedPositions(); - } - } -} diff --git a/NebulaPatcher/Patches/Dynamic/MonitorComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/MonitorComponent_Patch.cs index e72086812..f16fb7dec 100644 --- a/NebulaPatcher/Patches/Dynamic/MonitorComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/MonitorComponent_Patch.cs @@ -174,7 +174,7 @@ public static bool InternalUpdate_Prefix(MonitorComponent __instance, CargoTraff { return false; } - _traffic.factory.RemoveEntityWithComponents(__instance.entityId); + _traffic.factory.RemoveEntityWithComponents(__instance.entityId, false); WarningManager.DisplayTemporaryWarning( $"Broken Traffic Monitor detected on {_traffic.factory.planet.displayName}\nIt was removed, clients should reconnect!", 15000); diff --git a/NebulaPatcher/Patches/Dynamic/PlanetFactory_Patch.cs b/NebulaPatcher/Patches/Dynamic/PlanetFactory_Patch.cs index cce353e33..0ccb3f1bb 100644 --- a/NebulaPatcher/Patches/Dynamic/PlanetFactory_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/PlanetFactory_Patch.cs @@ -17,6 +17,7 @@ using NebulaModel.Packets.Factory.RayReceiver; using NebulaModel.Packets.Factory.Silo; using NebulaModel.Packets.Factory.Tank; +using NebulaModel.Packets.Factory.Turret; using NebulaModel.Packets.Logistics; using NebulaModel.Packets.Planet; using NebulaWorld; @@ -55,8 +56,11 @@ public static bool BuildFinally_Prefix(PlanetFactory __instance, int prebuildId) return true; } + DroneManager.RemoveBuildRequest(-prebuildId); + if (Multiplayer.Session.LocalPlayer.IsHost) { + DroneManager.RemovePlayerDronePlan(-prebuildId); if (!Multiplayer.Session.Factories.ContainsPrebuildRequest(__instance.planetId, prebuildId)) { // This prevents duplicating the entity when multiple players trigger the BuildFinally for the same entity at the same time. @@ -70,20 +74,15 @@ public static bool BuildFinally_Prefix(PlanetFactory __instance, int prebuildId) Multiplayer.Session.Factories.RemovePrebuildRequest(__instance.planetId, prebuildId); } - if (Multiplayer.Session.LocalPlayer.IsHost || !Multiplayer.Session.Factories.IsIncomingRequest.Value) + if (!Multiplayer.Session.LocalPlayer.IsHost && Multiplayer.Session.Factories.IsIncomingRequest.Value) { - var author = Multiplayer.Session.Factories.PacketAuthor == NebulaModAPI.AUTHOR_NONE - ? Multiplayer.Session.LocalPlayer.Id - : Multiplayer.Session.Factories.PacketAuthor; - var entityId = Multiplayer.Session.LocalPlayer.IsHost ? FactoryManager.GetNextEntityId(__instance) : -1; - Multiplayer.Session.Network.SendPacket(new BuildEntityRequest(__instance.planetId, prebuildId, author, entityId)); - } - - if (!Multiplayer.Session.LocalPlayer.IsHost && !Multiplayer.Session.Factories.IsIncomingRequest.Value && - !DroneManager.IsPendingBuildRequest(-prebuildId)) - { - DroneManager.AddBuildRequestSent(-prebuildId); + return Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.Factories.IsIncomingRequest.Value; } + var author = Multiplayer.Session.Factories.PacketAuthor == NebulaModAPI.AUTHOR_NONE + ? Multiplayer.Session.LocalPlayer.Id + : Multiplayer.Session.Factories.PacketAuthor; + var entityId = Multiplayer.Session.LocalPlayer.IsHost ? FactoryManager.GetNextEntityId(__instance) : -1; + Multiplayer.Session.Network.SendPacket(new BuildEntityRequest(__instance.planetId, prebuildId, author, entityId)); return Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.Factories.IsIncomingRequest.Value; } @@ -269,6 +268,30 @@ public static void RemoveVeinWithComponents_Postfix(PlanetFactory __instance, in } } + [HarmonyPostfix] + [HarmonyPatch(nameof(PlanetFactory.WriteExtraInfoOnEntity))] + public static void WriteExtraInfoOnEntity_Postfix(PlanetFactory __instance, int entityId, string info) + { + if (!Multiplayer.IsActive || Multiplayer.Session.Factories.IsIncomingRequest.Value) + { + return; + } + Multiplayer.Session.Network.SendPacketToLocalStar( + new ExtraInfoUpdatePacket(__instance.planetId, entityId, info)); + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(PlanetFactory.WriteExtraInfoOnPrebuild))] + public static void WriteExtraInfoOnPrebuild_Postfix(PlanetFactory __instance, int prebuildId, string info) + { + if (!Multiplayer.IsActive || Multiplayer.Session.Factories.IsIncomingRequest.Value) + { + return; + } + Multiplayer.Session.Network.SendPacketToLocalStar( + new ExtraInfoUpdatePacket(__instance.planetId, -prebuildId, info)); + } + [HarmonyPostfix] [HarmonyPatch(nameof(PlanetFactory.EnableEntityWarning))] public static void EnableEntityWarning_Postfix(PlanetFactory __instance, int entityId) @@ -279,7 +302,7 @@ public static void EnableEntityWarning_Postfix(PlanetFactory __instance, int ent } if (Multiplayer.Session.LocalPlayer.IsClient) { - //Becasue WarningSystem.NewWarningData is blocked on client, we give it a dummy warningId + //Because WarningSystem.NewWarningData is blocked on client, we give it a dummy warningId __instance.entityPool[entityId].warningId = 1; } Multiplayer.Session.Network.SendPacketToLocalStar( @@ -366,7 +389,6 @@ public static void EntityFastTakeOut_Postfix(PlanetFactory __instance, int entit if (entityData.minerId > 0) { var minerId = entityData.minerId; - var minerPool = __instance.factorySystem.minerPool; Multiplayer.Session.Network.SendPacketToLocalStar(new MinerStoragePickupPacket(minerId, __instance.planetId)); } if (entityData.powerExcId > 0) @@ -374,7 +396,8 @@ public static void EntityFastTakeOut_Postfix(PlanetFactory __instance, int entit var powerExcId = entityData.powerExcId; var excPool = __instance.powerSystem.excPool; Multiplayer.Session.Network.SendPacketToLocalStar(new PowerExchangerStorageUpdatePacket(powerExcId, - excPool[powerExcId].emptyCount, excPool[powerExcId].fullCount, __instance.planetId)); + excPool[powerExcId].emptyCount, excPool[powerExcId].fullCount, __instance.planetId, + excPool[powerExcId].fullInc)); } if (entityData.powerGenId > 0) { @@ -430,10 +453,18 @@ public static void EntityFastTakeOut_Postfix(PlanetFactory __instance, int entit Multiplayer.Session.Network.SendPacketToLocalStar(new SiloStorageUpdatePacket(siloId, siloPool[siloId].bulletCount, siloPool[siloId].bulletInc, __instance.planetId)); } + if (entityData.turretId > 0) + { + var turretId = entityData.turretId; + var turretPool = __instance.defenseSystem.turrets; + Multiplayer.Session.Network.SendPacketToLocalStar( + new TurretStorageUpdatePacket(in turretPool.buffer[turretId], __instance.planetId)); + } if (entityData.tankId <= 0) { return; } + var tankId = entityData.tankId; var tankPool = __instance.factoryStorage.tankPool; Multiplayer.Session.Network.SendPacketToLocalStar(new TankStorageUpdatePacket(in tankPool[tankId], @@ -569,7 +600,14 @@ public static void EntityFastFillIn_Postfix(PlanetFactory __instance, int entity var powerExcId = entityData.powerExcId; var excPool = __instance.powerSystem.excPool; Multiplayer.Session.Network.SendPacketToLocalStar(new PowerExchangerStorageUpdatePacket(powerExcId, - excPool[powerExcId].emptyCount, excPool[powerExcId].fullCount, __instance.planetId)); + excPool[powerExcId].emptyCount, excPool[powerExcId].fullCount, __instance.planetId, + excPool[powerExcId].fullInc)); + } + if (entityData.turretId > 0) + { + var turretId = entityData.turretId; + var turretPool = __instance.defenseSystem.turrets; + Multiplayer.Session.Network.SendPacketToLocalStar(new TurretStorageUpdatePacket(in turretPool.buffer[turretId], __instance.planetId)); } if (entityData.spraycoaterId <= 0) { diff --git a/NebulaPatcher/Patches/Dynamic/PlanetModelingManager_Patch.cs b/NebulaPatcher/Patches/Dynamic/PlanetModelingManager_Patch.cs index cfaa251b1..388ac5cfb 100644 --- a/NebulaPatcher/Patches/Dynamic/PlanetModelingManager_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/PlanetModelingManager_Patch.cs @@ -144,7 +144,7 @@ private static void InternalLoadPlanetsRequestGenerator(IEnumerable var localPlanetId = Multiplayer.Session.LocalPlayer?.Data?.LocalPlanetId ?? -1; if (localPlanetId == -1) { - localPlanetId = UIVirtualStarmap_Transpiler.customBirthPlanet; + localPlanetId = UIVirtualStarmap_Transpiler.CustomBirthPlanet; } if (planetsToRequest.Remove(localPlanetId)) diff --git a/NebulaPatcher/Patches/Dynamic/Player_Patch.cs b/NebulaPatcher/Patches/Dynamic/Player_Patch.cs index e670070d8..86e5b72c8 100644 --- a/NebulaPatcher/Patches/Dynamic/Player_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/Player_Patch.cs @@ -15,7 +15,7 @@ internal class Player_Patch { [HarmonyPrefix] [HarmonyPatch(nameof(Player.SetSandCount))] - public static bool SetSandCount_Prefix(int newSandCount) + public static bool SetSandCount_Prefix(long newSandCount) { if (!Config.Options.SyncSoil) { @@ -28,8 +28,12 @@ public static bool SetSandCount_Prefix(int newSandCount) { //Soil should be given in singleplayer or to the host who then syncs it back to all players. case true when Multiplayer.Session.LocalPlayer.IsHost: - Multiplayer.Session.Network.PlayerManager.UpdateSyncedSandCount(newSandCount - GameMain.mainPlayer.sandCount); - Multiplayer.Session.Network.SendPacket(new PlayerSandCount(newSandCount)); + var deltaSandCount = (int)(newSandCount - GameMain.mainPlayer.sandCount); + if (deltaSandCount != 0) + { + Multiplayer.Session.Network.PlayerManager.UpdateSyncedSandCount(deltaSandCount); + Multiplayer.Session.Network.SendPacket(new PlayerSandCount(newSandCount)); + } break; //Or client that use reform tool case true when GameMain.mainPlayer.controller.actionBuild.reformTool.drawing: diff --git a/NebulaPatcher/Patches/Dynamic/PowerSystem_Patch.cs b/NebulaPatcher/Patches/Dynamic/PowerSystem_Patch.cs index 80f4074aa..c6eb31792 100644 --- a/NebulaPatcher/Patches/Dynamic/PowerSystem_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/PowerSystem_Patch.cs @@ -21,23 +21,6 @@ public static void PowerSystem_GameTick_Prefix(long time, ref bool isActive) } } - [HarmonyPostfix] - [HarmonyPatch(nameof(PowerSystem.GameTick))] - public static void PowerSystem_GameTick_Postfix(PowerSystem __instance) - { - if (!Multiplayer.IsActive) - { - return; - } - for (var i = 1; i < __instance.netCursor; i++) - { - var pNet = __instance.netPool[i]; - pNet.energyRequired += Multiplayer.Session.PowerTowers.GetExtraDemand(__instance.planet.id, i); - } - Multiplayer.Session.PowerTowers.GivePlayerPower(); - Multiplayer.Session.PowerTowers.UpdateAllAnimations(__instance.planet.id); - } - [HarmonyPrefix] [HarmonyPatch(nameof(PowerSystem.RemoveNodeComponent))] public static bool RemoveNodeComponent(PowerSystem __instance, int id) @@ -47,9 +30,10 @@ public static bool RemoveNodeComponent(PowerSystem __instance, int id) return true; } // as the destruct is synced across players this event is too - // and as such we can safely remove power demand for every player - var pComp = __instance.nodePool[id]; - Multiplayer.Session.PowerTowers.RemExtraDemand(__instance.planet.id, pComp.networkId, id); + // and as such we can safely remove power demand for every player + Multiplayer.Session.PowerTowers.LocalChargerIds.Remove(id); + var hashId = (long)__instance.factory.planetId << 32 | (long)id; + Multiplayer.Session.PowerTowers.RemoteChargerHashIds.Remove(hashId); return true; } diff --git a/NebulaPatcher/Patches/Dynamic/StationComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/StationComponent_Patch.cs index 45bb9eaed..f9c5cdf3d 100644 --- a/NebulaPatcher/Patches/Dynamic/StationComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/StationComponent_Patch.cs @@ -1,9 +1,9 @@ #region using HarmonyLib; -using NebulaModel.Logger; using NebulaModel.Packets.Logistics; using NebulaWorld; +#pragma warning disable IDE1006 #endregion @@ -82,12 +82,4 @@ public static bool Free_Prefix(StationComponent __instance) } return !__instance.isStellar; } - - [HarmonyPrefix] - [HarmonyPatch(nameof(StationComponent.Reset))] - public static bool Reset_Prefix(StationComponent __instance) - { - Log.Debug($"Reset called on gid {__instance.gid}"); - return true; - } } diff --git a/NebulaPatcher/Patches/Dynamic/StorageComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/StorageComponent_Patch.cs index 8934e7b65..3647f8658 100644 --- a/NebulaPatcher/Patches/Dynamic/StorageComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/StorageComponent_Patch.cs @@ -38,7 +38,7 @@ public static bool AddItem_Prefix(StorageComponent __instance, int itemId, int c [HarmonyPatch(nameof(StorageComponent.AddItemStacked))] public static bool AddItemStacked_Prefix(StorageComponent __instance, int itemId, int count, int inc, out int remainInc) { - //Run only in MP, if it is not triggered remotly and if this event was triggered manually by an user + //Run only in MP, if it is not triggered remotely and if this event was triggered manually by an user if (Multiplayer.IsActive && !Multiplayer.Session.Storage.IsIncomingRequest && Multiplayer.Session.Storage.IsHumanInput && GameMain.data.localPlanet != null) { @@ -55,7 +55,7 @@ public static bool AddItemStacked_Prefix(StorageComponent __instance, int itemId public static bool TakeItemFromGrid_Prefix(StorageComponent __instance, int gridIndex, ref int itemId, ref int count, out int inc) { - //Run only in MP, if it is not triggered remotly and if this event was triggered manually by an user + //Run only in MP, if it is not triggered remotely and if this event was triggered manually by an user if (Multiplayer.IsActive && !Multiplayer.Session.Storage.IsIncomingRequest && Multiplayer.Session.Storage.IsHumanInput && GameMain.data.localPlanet != null) { @@ -110,6 +110,20 @@ public static bool TakeTailItems_Prefix(StorageComponent __instance, ref int cou } + [HarmonyPostfix] + [HarmonyPatch(typeof(StorageComponent), nameof(StorageComponent.SetFilter))] + private static void SetFilter_Postfix(StorageComponent __instance, int gridIndex, int filterId) + { + //Run only in MP, if it is not triggered remotely and if this event was triggered manually by an user + if (Multiplayer.IsActive && !Multiplayer.Session.Storage.IsIncomingRequest && + Multiplayer.Session.Storage.IsHumanInput && GameMain.data.localPlanet is not null) + { + HandleUserInteraction(__instance, + new StorageSyncSetFilterPacket(__instance.id, GameMain.data.localPlanet.id, gridIndex, filterId, __instance.type)); + } + // return true; + } + private static void HandleUserInteraction(StorageComponent __instance, T packet) where T : class, new() { //Skip if change was done in player's inventory diff --git a/NebulaPatcher/Patches/Dynamic/UIBattleBaseWindow_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIBattleBaseWindow_Patch.cs new file mode 100644 index 000000000..a8f041c6a --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/UIBattleBaseWindow_Patch.cs @@ -0,0 +1,132 @@ +#region + +using HarmonyLib; +using NebulaModel.Packets.Factory.BattleBase; +using NebulaWorld; +#pragma warning disable IDE1006 + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(UIBattleBaseWindow))] +internal class UIBattleBaseWindow_Patch +{ + private static void SendEvent(UIBattleBaseWindow __instance, BattleBaseSettingEvent settingEvent, float arg) + { + // client will wait for server approve those interactions + Multiplayer.Session.Network.SendPacket(new BattleBaseSettingUpdatePacket( + __instance.factory.planetId, __instance.battleBaseId, + settingEvent, arg)); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnMaxChargePowerSliderChange))] + public static bool OnMaxChargePowerSliderChange_Prefix(UIBattleBaseWindow __instance, float arg0) + { + if (!Multiplayer.IsActive || __instance.battleBaseId == 0 || __instance.factory == null || + __instance.battleBase.id != __instance.battleBaseId) + { + return true; + } + if (__instance.eventLock) + { + // Prevent ping-pong of sending update packets + return false; + } + SendEvent(__instance, BattleBaseSettingEvent.ChangeMaxChargePower, arg0); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnDroneButtonClick))] + public static bool OnDroneButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = __instance.constructionModule.droneEnabled ? 0f : 1f; + SendEvent(__instance, BattleBaseSettingEvent.ToggleDroneEnabled, arg); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnDronePriorityButtonClick))] + public static bool OnDronePriorityButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var ratio = __instance.constructionModule.dronePriorConstructRatio; + var newRatio = ratio < 0.25f ? 0.5f : ratio < 0.75f ? 1f : 0f; + SendEvent(__instance, BattleBaseSettingEvent.ChangeDronesPriority, newRatio); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnFleetButtonClick))] + public static bool OnFleetButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = __instance.combatModule.moduleEnabled ? 0f : 1f; + SendEvent(__instance, BattleBaseSettingEvent.ToggleCombatModuleEnabled, arg); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnAutoReconstructButtonClick))] + public static bool OnAutoReconstructButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = __instance.constructionModule.autoReconstruct ? 0f : 1f; + SendEvent(__instance, BattleBaseSettingEvent.ToggleAutoReconstruct, arg); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnAutoPickButtonClick))] + public static bool OnAutoPickButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = __instance.battleBase.autoPickEnabled ? 0f : 1f; + SendEvent(__instance, BattleBaseSettingEvent.ToggleAutoPickEnabled, arg); + return Multiplayer.Session.LocalPlayer.IsHost; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnFleetTypeButtonClick))] + public static bool OnFleetTypeButtonClick_Prefix(UIBattleBaseWindow __instance, int obj) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = ItemProto.kFighterGroundIds[obj]; + SendEvent(__instance, BattleBaseSettingEvent.ChangeFleetConfig, arg); + return true; // Let local handles the storage/inventory interactions and broadcast the changes + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBattleBaseWindow.OnAutoReplenishButtonClick))] + public static bool OnAutoReplenishButtonClick_Prefix(UIBattleBaseWindow __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + var arg = __instance.combatModule.autoReplenishFleet ? 0f : 1f; + SendEvent(__instance, BattleBaseSettingEvent.ToggleAutoReplenishFleet, arg); + return Multiplayer.Session.LocalPlayer.IsHost; + } +} diff --git a/NebulaPatcher/Patches/Dynamic/UICombatSettingsDF_Patch.cs b/NebulaPatcher/Patches/Dynamic/UICombatSettingsDF_Patch.cs new file mode 100644 index 000000000..ee93efab1 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/UICombatSettingsDF_Patch.cs @@ -0,0 +1,46 @@ +#region + +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using NebulaModel; +using NebulaModel.Logger; +using NebulaModel.Packets.Session; +using NebulaPatcher.Patches.Transpilers; +using NebulaWorld; +using UnityEngine; +using UnityEngine.UI; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic +{ + [HarmonyPatch(typeof(UICombatSettingsDF))] + internal class UICombatSettingsDF_Patch + { + [HarmonyPostfix] + [HarmonyPatch(nameof(UICombatSettingsDF.ApplySettings))] + public static void ApplySettings_Postfix(UICombatSettingsDF __instance) + { + if (!Multiplayer.IsInMultiplayerMenu || !Multiplayer.Session.LocalPlayer.IsHost) + { + return; + } + // syncing players are those who have not loaded into the game yet, so they might still be in the lobby. they need to check if this packet is relevant for them in the corresponding handler. + // just remembered others cant be in game anyways when host ist still in lobby >.> + using (Multiplayer.Session.Network.PlayerManager.GetSyncingPlayers(out var syncingPlayers)) + { + foreach (var entry in syncingPlayers) + { + entry.Key.SendPacket(new LobbyUpdateCombatValues(__instance.gameDesc.combatSettings)); + } + } + using (Multiplayer.Session.Network.PlayerManager.GetPendingPlayers(out var pendingPlayers)) + { + foreach (var entry in pendingPlayers) + { + entry.Key.SendPacket(new LobbyUpdateCombatValues(__instance.gameDesc.combatSettings)); + } + } + } + } +} diff --git a/NebulaPatcher/Patches/Dynamic/UIDEOverview_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIDEOverview_Patch.cs index f65a06dec..6acb9d609 100644 --- a/NebulaPatcher/Patches/Dynamic/UIDEOverview_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIDEOverview_Patch.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using NebulaModel.Logger; using NebulaModel.Packets.GameHistory; using NebulaWorld; @@ -18,7 +17,6 @@ internal class UIDEOverview_Patch [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Original Function Name")] public static void _OnInit_Postfix() { - Log.Debug("UIDEOverview._OnInit"); UIRoot.instance.uiGame.dysonEditor.controlPanel.topFunction.pauseButton.button.interactable = !Multiplayer.IsActive; } diff --git a/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs index 61dede7a9..81abb4340 100644 --- a/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs @@ -9,6 +9,7 @@ using NebulaWorld; using UnityEngine; using UnityEngine.UI; +using static NebulaPatcher.Patches.Dynamic.UIOptionWindow_Patch; #endregion @@ -19,6 +20,12 @@ internal class UIGalaxySelect_Patch { private static int MainMenuStarID = -1; + private static Toggle DFToggle; + private static bool OriginalDFToggleIsOn; + private static bool OriginalDFToggleInteractable; + private static Color OriginalDFToggleDisabledColor; + private static Tooltip DFToggleTooltip; + [HarmonyPostfix] [HarmonyPatch(nameof(UIGalaxySelect._OnOpen))] [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Original Function Name")] @@ -27,14 +34,14 @@ public static void _OnOpen_Postfix(UIGalaxySelect __instance) if (Multiplayer.IsActive && Multiplayer.Session.LocalPlayer.IsClient) { var galaxySelectRect = __instance.gameObject.GetComponent(); - - galaxySelectRect.Find("star-count").gameObject.SetActive(false); - galaxySelectRect.Find("resource-multiplier").gameObject.SetActive(false); - galaxySelectRect.Find("galaxy-seed").GetComponentInChildren().enabled = false; galaxySelectRect.Find("random-button").gameObject.SetActive(false); - galaxySelectRect.Find("property-multiplier").gameObject.SetActive(false); - galaxySelectRect.Find("seed-key").gameObject.SetActive(false); - galaxySelectRect.Find("sandbox-mode").gameObject.SetActive(false); + var settingGroupRect = galaxySelectRect.Find("setting-group"); + for (var i = 0; i < settingGroupRect.childCount; i++) + { + var childObject = settingGroupRect.GetChild(i).gameObject; + if (childObject.name != "top-title" && childObject.name != "galaxy-seed") + childObject.SetActive(false); + } } if (!Multiplayer.IsActive) { @@ -72,6 +79,11 @@ public static void _OnOpen_Postfix(UIGalaxySelect __instance) { MainMenuStarID = GameMain.localStar.id; } + +#if RELEASE + DisableDarkFogToggle(); +#endif + var button = GameObject.Find("UI Root/Overlay Canvas/Galaxy Select/start-button").GetComponent