diff --git a/Client/Entities/EntitiesPed.cs b/Client/Entities/EntitiesPed.cs index 7ba8b90..c57de54 100644 --- a/Client/Entities/EntitiesPed.cs +++ b/Client/Entities/EntitiesPed.cs @@ -1,6 +1,7 @@ using System; using System.Drawing; using System.Collections.Generic; +using System.Linq; using GTA; using GTA.Native; @@ -22,6 +23,8 @@ namespace CoopClient private Dictionary LastProps = new Dictionary(); public Dictionary Props { get; set; } public Vector3 Position { get; set; } + + #region -- ON FOOT -- public Vector3 Rotation { get; set; } public Vector3 Velocity { get; set; } public byte Speed { get; set; } @@ -34,9 +37,19 @@ namespace CoopClient public bool IsShooting { get; set; } public bool IsReloading { get; set; } public int CurrentWeaponHash { get; set; } + #endregion public Blip PedBlip; + #region -- VEHICLE -- + public bool IsInVehicle { get; set; } + public int VehicleModelHash { get; set; } + public int VehicleSeatIndex { get; set; } + public Vehicle MainVehicle { get; set; } + public Vector3 VehiclePosition { get; set; } + public Quaternion VehicleRotation { get; set; } + #endregion + public void DisplayLocally(string username) { /* @@ -61,6 +74,12 @@ namespace CoopClient #region NOT_IN_RANGE if (!Game.Player.Character.IsInRange(Position, 250f)) { + if (MainVehicle != null && MainVehicle.Exists() && MainVehicle.PassengerCount <= 1) + { + MainVehicle.Delete(); + MainVehicle = null; + } + if (Character != null && Character.Exists()) { Character.Kill(); @@ -121,7 +140,7 @@ namespace CoopClient } } - if (username != null && Character.IsInRange(Game.Player.Character.Position, 20f)) + if (username != null && Character.IsVisible && Character.IsInRange(Game.Player.Character.Position, 20f)) { float sizeOffset; if (GameplayCamera.IsFirstPersonAimCamActive) @@ -190,6 +209,50 @@ namespace CoopClient } } + if (IsInVehicle) + { + DisplayInVehicle(); + } + else + { + DisplayOnFoot(); + } + #endregion + } + + private void DisplayInVehicle() + { + if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model.Hash != VehicleModelHash) + { + List vehs = World.GetNearbyVehicles(Character, 3f, new Model[] { VehicleModelHash }).OrderBy(v => (v.Position - Character.Position).Length()).Take(3).ToList(); + + if (vehs.Count == 0 || !vehs[0].IsSeatFree((VehicleSeat)VehicleSeatIndex)) + { + MainVehicle = World.CreateVehicle(new Model(VehicleModelHash), VehiclePosition, VehicleRotation.Z); + } + else + { + MainVehicle = vehs[0]; + } + } + + if (!Character.IsInVehicle()) + { + Character.Task.WarpIntoVehicle(MainVehicle, (VehicleSeat)VehicleSeatIndex); + Character.IsVisible = true; + } + + MainVehicle.Position = VehiclePosition; + MainVehicle.Quaternion = VehicleRotation; + } + + private void DisplayOnFoot() + { + if (Character.IsInVehicle()) + { + Character.Task.LeaveVehicle(); + } + if (IsJumping && !LastIsJumping) { Character.Task.Jump(); @@ -262,7 +325,6 @@ namespace CoopClient { WalkTo(); } - #endregion } private void CreateCharacter(string username) @@ -272,6 +334,10 @@ namespace CoopClient Character = World.CreatePed(new Model(ModelHash), Position, Rotation.Z); Character.RelationshipGroup = Main.RelationshipGroup; + if (IsInVehicle) + { + Character.IsVisible = false; + } Character.BlockPermanentEvents = true; Character.CanRagdoll = false; Character.IsInvincible = true; diff --git a/Client/Entities/EntitiesThread.cs b/Client/Entities/EntitiesThread.cs index 547606e..9a7121b 100644 --- a/Client/Entities/EntitiesThread.cs +++ b/Client/Entities/EntitiesThread.cs @@ -36,6 +36,11 @@ namespace CoopClient.Entities npc.Value.Character.Delete(); } + if (npc.Value.MainVehicle != null && npc.Value.MainVehicle.Exists() && npc.Value.MainVehicle.PassengerCount == 0) + { + npc.Value.MainVehicle.Delete(); + } + Main.Npcs.Remove(npc.Key); } else diff --git a/Client/Main.cs b/Client/Main.cs index 17191ba..9b6b36c 100644 --- a/Client/Main.cs +++ b/Client/Main.cs @@ -259,19 +259,25 @@ namespace CoopClient DebugSyncPed = Players["DebugKey"]; } - if (!player.IsInVehicle() && DateTime.Now.Subtract(ArtificialLagCounter).TotalMilliseconds >= 300) + if (DateTime.Now.Subtract(ArtificialLagCounter).TotalMilliseconds < 300) { - ArtificialLagCounter = DateTime.Now; + return; + } - byte? flags = Util.GetPedFlags(player, FullDebugSync, true); + ArtificialLagCounter = DateTime.Now; - if (FullDebugSync) - { - DebugSyncPed.ModelHash = player.Model.Hash; - DebugSyncPed.Props = Util.GetPedProps(player); - } - DebugSyncPed.Health = player.Health; - DebugSyncPed.Position = player.Position; + byte? flags = Util.GetPedFlags(player, FullDebugSync, true); + + if (FullDebugSync) + { + DebugSyncPed.ModelHash = player.Model.Hash; + DebugSyncPed.Props = Util.GetPedProps(player); + } + DebugSyncPed.Health = player.Health; + DebugSyncPed.Position = player.Position; + + if (!player.IsInVehicle()) + { DebugSyncPed.Rotation = player.Rotation; DebugSyncPed.Velocity = player.Velocity; DebugSyncPed.Speed = Util.GetPedSpeed(player); @@ -285,6 +291,16 @@ namespace CoopClient DebugSyncPed.IsRagdoll = (flags.Value & (byte)PedDataFlags.IsRagdoll) > 0; DebugSyncPed.IsOnFire = (flags.Value & (byte)PedDataFlags.IsOnFire) > 0; } + else + { + Vehicle veh = player.CurrentVehicle; + DebugSyncPed.VehicleModelHash = veh.Model.Hash; + DebugSyncPed.VehicleSeatIndex = (int)player.SeatIndex; + DebugSyncPed.VehiclePosition = veh.Position; + DebugSyncPed.VehicleRotation = veh.Quaternion; + } + + DebugSyncPed.IsInVehicle = (flags.Value & (byte)PedDataFlags.IsInVehicle) > 0; if (DebugSyncPed.Character != null && DebugSyncPed.Character.Exists()) { @@ -292,6 +308,12 @@ namespace CoopClient Function.Call(Hash.SET_ENTITY_NO_COLLISION_ENTITY, player.Handle, DebugSyncPed.Character.Handle, false); } + if (DebugSyncPed.MainVehicle != null && DebugSyncPed.MainVehicle.Exists() && player.IsInVehicle()) + { + Function.Call(Hash.SET_ENTITY_NO_COLLISION_ENTITY, DebugSyncPed.MainVehicle.Handle, player.CurrentVehicle.Handle, false); + Function.Call(Hash.SET_ENTITY_NO_COLLISION_ENTITY, player.CurrentVehicle.Handle, DebugSyncPed.MainVehicle.Handle, false); + } + FullDebugSync = !FullDebugSync; } } diff --git a/Client/Networking.cs b/Client/Networking.cs index ca15c31..887b223 100644 --- a/Client/Networking.cs +++ b/Client/Networking.cs @@ -222,6 +222,11 @@ namespace CoopClient packet.NetIncomingMessageToPacket(message); LightSyncPlayer((LightSyncPlayerPacket)packet); break; + case (byte)PacketTypes.FullSyncNpcVehPacket: + packet = new FullSyncNpcVehPacket(); + packet.NetIncomingMessageToPacket(message); + FullSyncNpcVeh((FullSyncNpcVehPacket)packet); + break; case (byte)PacketTypes.ChatMessagePacket: packet = new ChatMessagePacket(); packet.NetIncomingMessageToPacket(message); @@ -323,6 +328,7 @@ namespace CoopClient npc.IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0; npc.IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0; npc.IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0; + npc.IsInVehicle = (packet.Flag.Value & (byte)PedDataFlags.IsInVehicle) > 0; } else { @@ -344,7 +350,8 @@ namespace CoopClient IsReloading = (packet.Flag.Value & (byte)PedDataFlags.IsReloading) > 0, IsJumping = (packet.Flag.Value & (byte)PedDataFlags.IsJumping) > 0, IsRagdoll = (packet.Flag.Value & (byte)PedDataFlags.IsRagdoll) > 0, - IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0 + IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0, + IsInVehicle = (packet.Flag.Value & (byte)PedDataFlags.IsInVehicle) > 0 }); } } @@ -370,6 +377,42 @@ namespace CoopClient player.IsOnFire = (packet.Flag.Value & (byte)PedDataFlags.IsOnFire) > 0; } } + + private void FullSyncNpcVeh(FullSyncNpcVehPacket packet) + { + if (Main.Npcs.ContainsKey(packet.ID)) + { + EntitiesNpc npc = Main.Npcs[packet.ID]; + npc.LastUpdateReceived = Environment.TickCount; + npc.ModelHash = packet.ModelHash; + npc.Props = packet.Props; + npc.Health = packet.Health; + npc.Position = packet.Position.ToVector(); + npc.VehicleModelHash = packet.VehModelHash; + npc.VehicleSeatIndex = packet.VehSeatIndex; + npc.VehiclePosition = packet.VehPosition.ToVector(); + npc.VehicleRotation = packet.VehRotation.ToQuaternion(); + npc.LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0; + npc.IsInVehicle = (packet.Flag.Value & (byte)PedDataFlags.IsInVehicle) > 0; + } + else + { + Main.Npcs.Add(packet.ID, new EntitiesNpc() + { + LastUpdateReceived = Environment.TickCount, + ModelHash = packet.ModelHash, + Props = packet.Props, + Health = packet.Health, + Position = packet.Position.ToVector(), + VehicleModelHash = packet.VehModelHash, + VehicleSeatIndex = packet.VehSeatIndex, + VehiclePosition = packet.VehPosition.ToVector(), + VehicleRotation = packet.VehRotation.ToQuaternion(), + LastSyncWasFull = (packet.Flag.Value & (byte)PedDataFlags.LastSyncWasFull) > 0, + IsInVehicle = (packet.Flag.Value & (byte)PedDataFlags.IsInVehicle) > 0 + }); + } + } #endregion #region SEND @@ -423,20 +466,39 @@ namespace CoopClient { NetOutgoingMessage outgoingMessage = Client.CreateMessage(); - new FullSyncNpcPacket() + if (!npc.IsInVehicle()) { - ID = Main.LocalPlayerID + npc.Handle, - ModelHash = npc.Model.Hash, - Props = Util.GetPedProps(npc), - Health = npc.Health, - Position = npc.Position.ToLVector(), - Rotation = npc.Rotation.ToLVector(), - Velocity = npc.Velocity.ToLVector(), - Speed = Util.GetPedSpeed(npc), - AimCoords = Util.GetPedAimCoords(npc, true).ToLVector(), - CurrentWeaponHash = (int)npc.Weapons.Current.Hash, - Flag = Util.GetPedFlags(npc, true) - }.PacketToNetOutGoingMessage(outgoingMessage); + new FullSyncNpcPacket() + { + ID = Main.LocalPlayerID + npc.Handle, + ModelHash = npc.Model.Hash, + Props = Util.GetPedProps(npc), + Health = npc.Health, + Position = npc.Position.ToLVector(), + Rotation = npc.Rotation.ToLVector(), + Velocity = npc.Velocity.ToLVector(), + Speed = Util.GetPedSpeed(npc), + AimCoords = Util.GetPedAimCoords(npc, true).ToLVector(), + CurrentWeaponHash = (int)npc.Weapons.Current.Hash, + Flag = Util.GetPedFlags(npc, true) + }.PacketToNetOutGoingMessage(outgoingMessage); + } + else + { + new FullSyncNpcVehPacket() + { + ID = Main.LocalPlayerID + npc.Handle, + ModelHash = npc.Model.Hash, + Props = Util.GetPedProps(npc), + Health = npc.Health, + Position = npc.Position.ToLVector(), + VehModelHash = npc.CurrentVehicle.Model.Hash, + VehSeatIndex = (int)npc.SeatIndex, + VehPosition = npc.CurrentVehicle.Position.ToLVector(), + VehRotation = npc.CurrentVehicle.Quaternion.ToLQuaternion(), + Flag = Util.GetPedFlags(npc, true) + }.PacketToNetOutGoingMessage(outgoingMessage); + } Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered); Client.FlushSendQueue(); diff --git a/Client/Packets.cs b/Client/Packets.cs index 9139f3b..16e5e4a 100644 --- a/Client/Packets.cs +++ b/Client/Packets.cs @@ -21,6 +21,17 @@ namespace CoopClient Z = vec.Z, }; } + + public static LQuaternion ToLQuaternion(this Quaternion vec) + { + return new LQuaternion() + { + X = vec.X, + Y = vec.Y, + Z = vec.Z, + W = vec.W + }; + } } #endregion @@ -51,6 +62,37 @@ namespace CoopClient public float Z { get; set; } } + [ProtoContract] + public struct LQuaternion + { + #region CLIENT-ONLY + public Quaternion ToQuaternion() + { + return new Quaternion(X, Y, Z, W); + } + #endregion + + public LQuaternion(float X, float Y, float Z, float W) + { + this.X = X; + this.Y = Y; + this.Z = Z; + this.W = W; + } + + [ProtoMember(1)] + public float X { get; set; } + + [ProtoMember(2)] + public float Y { get; set; } + + [ProtoMember(3)] + public float Z { get; set; } + + [ProtoMember(4)] + public float W { get; set; } + } + public enum ModVersion { V0_1_0 @@ -64,6 +106,7 @@ namespace CoopClient FullSyncPlayerPacket, FullSyncNpcPacket, LightSyncPlayerPacket, + FullSyncNpcVehPacket, ChatMessagePacket } @@ -76,7 +119,8 @@ namespace CoopClient IsReloading = 1 << 3, IsJumping = 1 << 4, IsRagdoll = 1 << 5, - IsOnFire = 1 << 6 + IsOnFire = 1 << 6, + IsInVehicle = 1 << 7 } public interface IPacket @@ -383,6 +427,68 @@ namespace CoopClient } } + [ProtoContract] + public class FullSyncNpcVehPacket : Packet + { + [ProtoMember(1)] + public string ID { get; set; } + + [ProtoMember(2)] + public int ModelHash { get; set; } + + [ProtoMember(3)] + public Dictionary Props { get; set; } + + [ProtoMember(4)] + public int Health { get; set; } + + [ProtoMember(5)] + public LVector3 Position { get; set; } + + [ProtoMember(6)] + public int VehModelHash { get; set; } + + [ProtoMember(7)] + public int VehSeatIndex { get; set; } + + [ProtoMember(8)] + public LVector3 VehPosition { get; set; } + + [ProtoMember(9)] + public LQuaternion VehRotation { get; set; } + + [ProtoMember(10)] + public byte? Flag { get; set; } = 0; + + public override void PacketToNetOutGoingMessage(NetOutgoingMessage message) + { + message.Write((byte)PacketTypes.FullSyncNpcVehPacket); + + byte[] result = CoopSerializer.Serialize(this); + + message.Write(result.Length); + message.Write(result); + } + + public override void NetIncomingMessageToPacket(NetIncomingMessage message) + { + int len = message.ReadInt32(); + + FullSyncNpcVehPacket data = CoopSerializer.Deserialize(message.ReadBytes(len)); + + ID = data.ID; + ModelHash = data.ModelHash; + Props = data.Props; + Health = data.Health; + Position = data.Position; + VehModelHash = data.VehModelHash; + VehSeatIndex = data.VehSeatIndex; + VehPosition = data.VehPosition; + VehRotation = data.VehRotation; + Flag = data.Flag; + } + } + [ProtoContract] public class ChatMessagePacket : Packet { diff --git a/Client/Util.cs b/Client/Util.cs index fa2a3c7..fa91892 100644 --- a/Client/Util.cs +++ b/Client/Util.cs @@ -74,6 +74,11 @@ namespace CoopClient flags |= (byte)PedDataFlags.IsOnFire; } + if (ped.IsInVehicle()) + { + flags |= (byte)PedDataFlags.IsInVehicle; + } + return flags; } diff --git a/Server/Packets.cs b/Server/Packets.cs index 553bc1f..d01a31a 100644 --- a/Server/Packets.cs +++ b/Server/Packets.cs @@ -32,6 +32,30 @@ namespace CoopServer #endregion } + [ProtoContract] + public struct LQuaternion + { + public LQuaternion(float X, float Y, float Z, float W) + { + this.X = X; + this.Y = Y; + this.Z = Z; + this.W = W; + } + + [ProtoMember(1)] + public float X { get; set; } + + [ProtoMember(2)] + public float Y { get; set; } + + [ProtoMember(3)] + public float Z { get; set; } + + [ProtoMember(4)] + public float W { get; set; } + } + public enum ModVersion { V0_1_0 @@ -45,6 +69,7 @@ namespace CoopServer FullSyncPlayerPacket, FullSyncNpcPacket, LightSyncPlayerPacket, + FullSyncNpcVehPacket, ChatMessagePacket } @@ -57,7 +82,8 @@ namespace CoopServer IsReloading = 1 << 3, IsJumping = 1 << 4, IsRagdoll = 1 << 5, - IsOnFire = 1 << 6 + IsOnFire = 1 << 6, + IsInVehicle = 1 << 7 } public interface IPacket @@ -364,6 +390,68 @@ namespace CoopServer } } + [ProtoContract] + public class FullSyncNpcVehPacket : Packet + { + [ProtoMember(1)] + public string ID { get; set; } + + [ProtoMember(2)] + public int ModelHash { get; set; } + + [ProtoMember(3)] + public Dictionary Props { get; set; } + + [ProtoMember(4)] + public int Health { get; set; } + + [ProtoMember(5)] + public LVector3 Position { get; set; } + + [ProtoMember(6)] + public int VehModelHash { get; set; } + + [ProtoMember(7)] + public int VehSeatIndex { get; set; } + + [ProtoMember(8)] + public LVector3 VehPosition { get; set; } + + [ProtoMember(9)] + public LQuaternion VehRotation { get; set; } + + [ProtoMember(10)] + public byte? Flag { get; set; } = 0; + + public override void PacketToNetOutGoingMessage(NetOutgoingMessage message) + { + message.Write((byte)PacketTypes.FullSyncNpcVehPacket); + + byte[] result = CoopSerializer.Serialize(this); + + message.Write(result.Length); + message.Write(result); + } + + public override void NetIncomingMessageToPacket(NetIncomingMessage message) + { + int len = message.ReadInt32(); + + FullSyncNpcVehPacket data = CoopSerializer.Deserialize(message.ReadBytes(len)); + + ID = data.ID; + ModelHash = data.ModelHash; + Props = data.Props; + Health = data.Health; + Position = data.Position; + VehModelHash = data.VehModelHash; + VehSeatIndex = data.VehSeatIndex; + VehPosition = data.VehPosition; + VehRotation = data.VehRotation; + Flag = data.Flag; + } + } + [ProtoContract] public class ChatMessagePacket : Packet { diff --git a/Server/Server.cs b/Server/Server.cs index 5475419..92d57ca 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -229,6 +229,25 @@ namespace CoopServer message.SenderConnection.Disconnect(e.Message); } break; + case (byte)PacketTypes.FullSyncNpcVehPacket: + if (MainSettings.NpcsAllowed) + { + try + { + packet = new FullSyncNpcVehPacket(); + packet.NetIncomingMessageToPacket(message); + FullSyncNpcVeh(message.SenderConnection, (FullSyncNpcVehPacket)packet); + } + catch (Exception e) + { + message.SenderConnection.Disconnect(e.Message); + } + } + else + { + message.SenderConnection.Disconnect("Npcs are not allowed!"); + } + break; case (byte)PacketTypes.ChatMessagePacket: try { @@ -478,6 +497,19 @@ namespace CoopServer MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); } + private static void FullSyncNpcVeh(NetConnection local, FullSyncNpcVehPacket packet) + { + List playerList = GetAllInRange(packet.Position, 300f, local); + if (playerList.Count == 0) + { + return; + } + + NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage(); + packet.PacketToNetOutGoingMessage(outgoingMessage); + MainNetServer.SendMessage(outgoingMessage, playerList, NetDeliveryMethod.ReliableOrdered, 0); + } + // Send a message to targets or all players private static void SendChatMessage(ChatMessagePacket packet, List targets = null) { diff --git a/Server/Util.cs b/Server/Util.cs index a88d20a..ea084f7 100644 --- a/Server/Util.cs +++ b/Server/Util.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Xml.Serialization; namespace CoopServer @@ -9,7 +10,7 @@ namespace CoopServer { XmlSerializer ser = new(typeof(T)); - string path = Directory.GetCurrentDirectory() + "\\" + file; + string path = AppContext.BaseDirectory + "\\" + file; T settings; if (File.Exists(path))