using GTA; using GTA.Math; using RageCoop.Core; using GTA.Native; namespace RageCoop.Client { internal class SyncedProjectile : SyncedEntity { public ProjectileDataFlags Flags { private get; set; }=ProjectileDataFlags.None; public readonly Vector3 Origin; private bool _firstSend = false; public bool IsValid { get; private set; } = true; public new bool IsLocal { get; private set; } = false; public Projectile MainProjectile { get; set; } public SyncedEntity Shooter { get; set; } public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded); /// /// Invalid property for projectile. /// private new int OwnerID { set { } } internal override Player Owner => Shooter.Owner; public WeaponHash WeaponHash { get; set; } private WeaponAsset Asset { get; set; } public void ExtractData(ref Packets.ProjectileSync p) { p.Position=MainProjectile.Position; p.Velocity=MainProjectile.Velocity; p.Rotation=MainProjectile.Rotation; p.ID=ID; p.ShooterID=Shooter.ID; p.WeaponHash=(uint)MainProjectile.WeaponHash; p.Flags=ProjectileDataFlags.None; if (MainProjectile.IsDead) { p.Flags |= ProjectileDataFlags.Exploded; } if (MainProjectile.AttachedEntity!=null) { p.Flags |= ProjectileDataFlags.IsAttached; } if (Shooter is SyncedVehicle) { p.Flags |= ProjectileDataFlags.IsShotByVehicle; } if (_firstSend) { p.Flags |= ProjectileDataFlags.IsAttached; _firstSend=false; } } public SyncedProjectile(Projectile p) { var owner = p.OwnerEntity; if (owner==null) { IsValid=false;return; } ID=EntityPool.RequestNewID(); MainProjectile = p; Origin=p.Position; if(EntityPool.PedsByHandle.TryGetValue(owner.Handle,out var shooter)) { if (shooter.MainPed!=null && (p.AttachedEntity==shooter.MainPed.Weapons.CurrentWeaponObject || p.AttachedEntity== shooter.MainPed)) { // Reloading IsValid=false; return; } Shooter=shooter; IsLocal=shooter.IsLocal; } else if(EntityPool.VehiclesByHandle.TryGetValue(owner.Handle,out var shooterVeh)) { Shooter=shooterVeh; IsLocal=shooterVeh.IsLocal; } else { IsValid=false; } } public SyncedProjectile(int id) { ID= id; IsLocal=false; } internal override void Update() { // Skip update if no new sync message has arrived. if (!NeedUpdate) { return; } if (MainProjectile == null || !MainProjectile.Exists()) { CreateProjectile(); return; } MainProjectile.Velocity=Velocity+(Position+Shooter.Owner.PacketTravelTime*Velocity-MainProjectile.Position); MainProjectile.Rotation=Rotation; LastUpdated=Main.Ticked; } private void CreateProjectile() { Asset=new WeaponAsset(WeaponHash); if (!Asset.IsLoaded) { Asset.Request(); return; } if(Shooter == null) { return; } Entity owner; owner=(Shooter as SyncedPed)?.MainPed ?? (Entity)(Shooter as SyncedVehicle)?.MainVehicle; var end = Position+Velocity; Function.Call(Hash.SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z, end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1,owner); var ps = World.GetAllProjectiles(); MainProjectile=ps[ps.Length-1]; MainProjectile.IsCollisionEnabled=false; MainProjectile.Position=Position; MainProjectile.Rotation =Rotation; MainProjectile.Velocity=Velocity; Main.Delay(()=>MainProjectile.IsCollisionEnabled=true, 100); EntityPool.Add(this); } } }