private void FaceClickCoordinates(IEntity user, EntityCoordinates coordinates)
        {
            var diff = coordinates.ToMapPos(EntityManager) - user.Transform.MapPosition.Position;

            if (diff.LengthSquared <= 0.01f)
            {
                return;
            }
            var diffAngle = Angle.FromWorldVec(diff);

            if (Get <ActionBlockerSystem>().CanChangeDirection(user))
            {
                user.Transform.WorldRotation = diffAngle;
            }
            else
            {
                if (user.TryGetComponent(out BuckleComponent? buckle) && (buckle.BuckledTo != null))
                {
                    // We're buckled to another object. Is that object rotatable?
                    if (buckle.BuckledTo !.Owner.TryGetComponent(out SharedRotatableComponent? rotatable) && rotatable.RotateWhileAnchored)
                    {
                        // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel".
                        // (Since the user being buckled to it holds it down with their weight.)
                        // This is logically equivalent to RotateWhileAnchored.
                        // Barstools and office chairs have independent wheels, while regular chairs don't.
                        rotatable.Owner.Transform.LocalRotation = diffAngle;
                    }
                }
            }
        }
        private bool CheckRangeAndSetFacing(EntityCoordinates target, IEntity player)
        {
            // ensure it's within their clickable range
            var targetWorldPos = target.ToMapPos(EntityManager);
            var rangeBox       = new Box2(player.Transform.WorldPosition, player.Transform.WorldPosition)
                                 .Enlarged(_entityManager.MaxUpdateRange);

            if (!rangeBox.Contains(targetWorldPos))
            {
                Logger.DebugS("action", "user {0} attempted to" +
                              " perform target action further than allowed range",
                              player.Name);
                return(false);
            }

            if (!ActionBlockerSystem.CanChangeDirection(player))
            {
                return(true);
            }

            // don't set facing unless they clicked far enough away
            var diff = targetWorldPos - player.Transform.WorldPosition;

            if (diff.LengthSquared > 0.01f)
            {
                player.Transform.LocalRotation = new Angle(diff);
            }

            return(true);
        }
Exemple #3
0
        /// <inheritdoc />
        public IEnumerable <IEntity> GetEntitiesInRange(EntityCoordinates position, float range, bool approximate = false)
        {
            var mapPosition = position.ToMapPos(this);
            var aabb        = new Box2(mapPosition - new Vector2(range / 2, range / 2),
                                       mapPosition + new Vector2(range / 2, range / 2));

            return(GetEntitiesIntersecting(_mapManager.GetGrid(position.GetGridId(this)).ParentMapId, aabb, approximate));
        }
        protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
        {
            // Verify user is on the same map as the entity they clicked on
            if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
            {
                return(false);
            }

            _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));

            return(true);
        }
Exemple #5
0
        private bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
        {
            // Verify user is on the same map as the entity they clicked on
            if (coordinates.GetMapId(_entityManager) != EntityManager.GetComponent <TransformComponent>(user).MapID)
            {
                Logger.WarningS("system.interaction",
                                $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on a map they aren't located on");
                return(false);
            }

            _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));

            return(true);
        }
Exemple #6
0
        private bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates)
        {
            // Verify user is on the same map as the entity they clicked on
            if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
            {
                Logger.WarningS("system.interaction",
                                $"User entity {ToPrettyString(user):user} clicked on a map they aren't located on");
                return(false);
            }

            _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));

            return(true);
        }
        public bool IsColliding(EntityCoordinates coordinates)
        {
            var bounds      = pManager.ColliderAABB;
            var worldcoords = coordinates.ToMapPos(pManager.EntityManager);

            var collisionbox = Box2.FromDimensions(
                bounds.Left + worldcoords.X,
                bounds.Bottom + worldcoords.Y,
                bounds.Width,
                bounds.Height);

            if (pManager.PhysicsManager.TryCollideRect(collisionbox, pManager.MapManager.GetGrid(coordinates.GetGridId(pManager.EntityManager)).ParentMapId))
            {
                return(true);
            }

            return(false);
        }
Exemple #8
0
        private bool CheckRangeAndSetFacing(EntityCoordinates target, EntityUid player)
        {
            // ensure it's within their clickable range
            var targetWorldPos = target.ToMapPos(EntityManager);
            var rangeBox       = new Box2(_entities.GetComponent <TransformComponent>(player).WorldPosition, _entities.GetComponent <TransformComponent>(player).WorldPosition)
                                 .Enlarged(MaxUpdateRange);

            if (!rangeBox.Contains(targetWorldPos))
            {
                Logger.DebugS("action", "user {0} attempted to" +
                              " perform target action further than allowed range",
                              _entities.GetComponent <MetaDataComponent>(player).EntityName);
                return(false);
            }

            EntitySystem.Get <RotateToFaceSystem>().TryFaceCoordinates(player, targetWorldPos);
            return(true);
        }
        private bool HandleDrop(ICommonSession?session, EntityCoordinates coords, EntityUid uid)
        {
            var ent = ((IPlayerSession?)session)?.AttachedEntity;

            if (ent == null || !ent.IsValid())
            {
                return(false);
            }

            if (!ent.TryGetComponent(out HandsComponent? handsComp))
            {
                return(false);
            }

            if (handsComp.ActiveHand == null || handsComp.GetActiveHand == null)
            {
                return(false);
            }

            // It's important to note that the calculations are done in map coordinates (they're absolute).
            // They're translated back to EntityCoordinates at the end.
            var entMap       = ent.Transform.MapPosition;
            var targetPos    = coords.ToMapPos(EntityManager);
            var dropVector   = targetPos - entMap.Position;
            var targetVector = Vector2.Zero;

            if (dropVector != Vector2.Zero)
            {
                var targetLength = MathF.Min(dropVector.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error
                var newCoords    = new MapCoordinates(dropVector.Normalized * targetLength + entMap.Position, entMap.MapId);
                var rayLength    = Get <SharedInteractionSystem>().UnobstructedDistance(entMap, newCoords, ignoredEnt: ent);
                targetVector = dropVector.Normalized * rayLength;
            }

            var resultMapCoordinates = new MapCoordinates(entMap.Position + targetVector, entMap.MapId);
            var resultEntCoordinates = EntityCoordinates.FromMap(coords.GetParent(EntityManager), resultMapCoordinates);

            handsComp.Drop(handsComp.ActiveHand, resultEntCoordinates);

            return(true);
        }
        private bool HandleDrop(ICommonSession session, EntityCoordinates coords, EntityUid uid)
        {
            var ent = ((IPlayerSession)session).AttachedEntity;

            if (ent == null || !ent.IsValid())
            {
                return(false);
            }

            if (!ent.TryGetComponent(out HandsComponent handsComp))
            {
                return(false);
            }

            if (handsComp.ActiveHand == null || handsComp.GetActiveHand == null)
            {
                return(false);
            }

            var entMap       = ent.Transform.MapPosition;
            var targetPos    = coords.ToMapPos(EntityManager);
            var dropVector   = targetPos - entMap.Position;
            var targetVector = Vector2.Zero;

            if (dropVector != Vector2.Zero)
            {
                var targetLength = MathF.Min(dropVector.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error
                var newCoords    = coords.WithPosition(dropVector.Normalized * targetLength + entMap.Position).ToMap(EntityManager);
                var rayLength    = Get <SharedInteractionSystem>().UnobstructedDistance(entMap, newCoords, ignoredEnt: ent);
                targetVector = dropVector.Normalized * rayLength;
            }

            handsComp.Drop(handsComp.ActiveHand, coords.WithPosition(entMap.Position + targetVector));

            return(true);
        }
Exemple #11
0
        /// <summary>
        ///     Throw an entity in the direction of <paramref name="targetLoc"/> from <paramref name="sourceLoc"/>.
        /// </summary>
        /// <param name="thrownEnt">The entity to throw.</param>
        /// <param name="throwForce">
        /// The force to throw the entity with.
        /// Total impulse applied is equal to this force applied for one second.
        /// </param>
        /// <param name="targetLoc">
        /// The target location to throw at.
        /// This is only used to calculate a direction,
        /// actual distance is purely determined by <paramref name="throwForce"/>.
        /// </param>
        /// <param name="sourceLoc">
        /// The position to start the throw from.
        /// </param>
        /// <param name="spread">
        /// If true, slightly spread the actual throw angle.
        /// </param>
        /// <param name="throwSourceEnt">
        /// The entity that did the throwing. An opposite impulse will be applied to this entity if passed in.
        /// </param>
        public static void Throw(this IEntity thrownEnt, float throwForce, EntityCoordinates targetLoc, EntityCoordinates sourceLoc, bool spread = false, IEntity throwSourceEnt = null)
        {
            if (!thrownEnt.TryGetComponent(out IPhysicsComponent colComp))
            {
                return;
            }

            var entityManager = IoCManager.Resolve <IEntityManager>();

            colComp.CanCollide = true;
            // I can now collide with player, so that i can do damage.

            if (!thrownEnt.TryGetComponent(out ThrownItemComponent projComp))
            {
                projComp = thrownEnt.AddComponent <ThrownItemComponent>();

                if (colComp.PhysicsShapes.Count == 0)
                {
                    colComp.PhysicsShapes.Add(new PhysShapeAabb());
                }

                colComp.PhysicsShapes[0].CollisionMask |= (int)CollisionGroup.ThrownItem;
                colComp.Status = BodyStatus.InAir;
            }
            var angle = new Angle(targetLoc.ToMapPos(entityManager) - sourceLoc.ToMapPos(entityManager));

            if (spread)
            {
                var spreadRandom = IoCManager.Resolve <IRobustRandom>();
                angle += Angle.FromDegrees(spreadRandom.NextGaussian(0, 3));
            }

            if (throwSourceEnt != null)
            {
                projComp.User = throwSourceEnt;
                projComp.IgnoreEntity(throwSourceEnt);

                if (ActionBlockerSystem.CanChangeDirection(throwSourceEnt))
                {
                    throwSourceEnt.Transform.LocalRotation = angle.GetCardinalDir().ToAngle();
                }
            }

            // scaling is handled elsewhere, this is just multiplying by 60 independent of timing as a fix until elsewhere values are updated
            var spd = throwForce * 60;

            projComp.StartThrow(angle.ToVec(), spd);

            if (throwSourceEnt != null &&
                throwSourceEnt.TryGetComponent <IPhysicsComponent>(out var physics))
            {
                if (throwSourceEnt.IsWeightless())
                {
                    // We don't check for surrounding entities,
                    // so you'll still get knocked around if you're hugging the station wall in zero g.
                    // I got kinda lazy is the reason why. Also it makes a bit of sense.
                    // If somebody wants they can come along and make it so magboots completely hold you still.
                    // Would be a cool incentive to use them.
                    const float throwFactor = 0.2f; // Break Newton's Third Law for better gameplay
                    var         mover       = physics.EnsureController <ThrowKnockbackController>();
                    mover.Push(-angle.ToVec(), spd * throwFactor);
                }
            }
        }
    public override void Shoot(GunComponent gun, List <IShootable> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid?user = null)
    {
        var fromMap      = fromCoordinates.ToMap(EntityManager);
        var toMap        = toCoordinates.ToMapPos(EntityManager);
        var mapDirection = toMap - fromMap.Position;
        var mapAngle     = mapDirection.ToAngle();
        var angle        = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle());

        // Update shot based on the recoil
        toMap        = fromMap.Position + angle.ToVec() * mapDirection.Length;
        mapDirection = toMap - fromMap.Position;
        var entityDirection = Transform(fromCoordinates.EntityId).InvWorldMatrix.Transform(toMap) - fromCoordinates.Position;

        // I must be high because this was getting tripped even when true.
        // DebugTools.Assert(direction != Vector2.Zero);
        var shotProjectiles = new List <EntityUid>(ammo.Count);

        foreach (var shootable in ammo)
        {
            switch (shootable)
            {
            // Cartridge shoots something else
            case CartridgeAmmoComponent cartridge:
                if (!cartridge.Spent)
                {
                    if (cartridge.Count > 1)
                    {
                        var angles = LinearSpread(mapAngle - cartridge.Spread / 2,
                                                  mapAngle + cartridge.Spread / 2, cartridge.Count);

                        for (var i = 0; i < cartridge.Count; i++)
                        {
                            var uid = Spawn(cartridge.Prototype, fromCoordinates);
                            ShootProjectile(uid, angles[i].ToVec(), user);
                            shotProjectiles.Add(uid);
                        }
                    }
                    else
                    {
                        var uid = Spawn(cartridge.Prototype, fromCoordinates);
                        ShootProjectile(uid, mapDirection, user);
                        shotProjectiles.Add(uid);
                    }

                    SetCartridgeSpent(cartridge, true);
                    MuzzleFlash(gun.Owner, cartridge, user);
                    PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);

                    if (cartridge.DeleteOnSpawn)
                    {
                        Del(cartridge.Owner);
                    }
                }
                else
                {
                    PlaySound(gun.Owner, gun.SoundEmpty?.GetSound(Random, ProtoManager), user);
                }

                // Something like ballistic might want to leave it in the container still
                if (!cartridge.DeleteOnSpawn && !Containers.IsEntityInContainer(cartridge.Owner))
                {
                    EjectCartridge(cartridge.Owner);
                }

                Dirty(cartridge);
                break;

            // Ammo shoots itself
            case AmmoComponent newAmmo:
                shotProjectiles.Add(newAmmo.Owner);
                MuzzleFlash(gun.Owner, newAmmo, user);
                PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);

                // Do a throw
                if (!HasComp <ProjectileComponent>(newAmmo.Owner))
                {
                    RemComp <AmmoComponent>(newAmmo.Owner);
                    // TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack.
                    ThrowingSystem.TryThrow(newAmmo.Owner, mapDirection, 20f, user);
                    break;
                }

                ShootProjectile(newAmmo.Owner, mapDirection, user);
                break;

            case HitscanPrototype hitscan:
                var ray            = new CollisionRay(fromMap.Position, mapDirection.Normalized, hitscan.CollisionMask);
                var rayCastResults = Physics.IntersectRay(fromMap.MapId, ray, hitscan.MaxLength, user, false).ToList();

                if (rayCastResults.Count >= 1)
                {
                    var result   = rayCastResults[0];
                    var distance = result.Distance;
                    FireEffects(fromCoordinates, distance, entityDirection.ToAngle(), hitscan, result.HitEntity);

                    var dmg = hitscan.Damage;

                    if (dmg != null)
                    {
                        dmg = Damageable.TryChangeDamage(result.HitEntity, dmg);
                    }

                    if (dmg != null)
                    {
                        PlayImpactSound(result.HitEntity, dmg, hitscan.Sound, hitscan.ForceSound);

                        if (user != null)
                        {
                            Logs.Add(LogType.HitScanHit,
                                     $"{ToPrettyString(user.Value):user} hit {ToPrettyString(result.HitEntity):target} using hitscan and dealt {dmg.Total:damage} damage");
                        }
                        else
                        {
                            Logs.Add(LogType.HitScanHit,
                                     $"Hit {ToPrettyString(result.HitEntity):target} using hitscan and dealt {dmg.Total:damage} damage");
                        }
                    }
                }
                else
                {
                    FireEffects(fromCoordinates, hitscan.MaxLength, entityDirection.ToAngle(), hitscan);
                }
                PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user);
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }
        }

        RaiseLocalEvent(gun.Owner, new AmmoShotEvent()
        {
            FiredProjectiles = shotProjectiles,
        }, false);
    }
Exemple #13
0
    private DockingConfig?GetDockingConfig(ShuttleComponent component, EntityUid targetGrid)
    {
        var gridDocks = GetDocks(targetGrid);

        if (gridDocks.Count <= 0)
        {
            return(null);
        }

        var xformQuery         = GetEntityQuery <TransformComponent>();
        var targetGridGrid     = Comp <IMapGridComponent>(targetGrid);
        var targetGridXform    = xformQuery.GetComponent(targetGrid);
        var targetGridAngle    = targetGridXform.WorldRotation.Reduced();
        var targetGridRotation = targetGridAngle.ToVec();

        var shuttleDocks = GetDocks(component.Owner);
        var shuttleAABB  = Comp <IMapGridComponent>(component.Owner).Grid.LocalAABB;

        var validDockConfigs = new List <DockingConfig>();

        if (shuttleDocks.Count > 0)
        {
            // We'll try all combinations of shuttle docks and see which one is most suitable
            foreach (var shuttleDock in shuttleDocks)
            {
                var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);

                foreach (var gridDock in gridDocks)
                {
                    var gridXform = xformQuery.GetComponent(gridDock.Owner);

                    if (!CanDock(
                            shuttleDock, shuttleDockXform,
                            gridDock, gridXform,
                            targetGridRotation,
                            shuttleAABB,
                            targetGridGrid,
                            out var dockedAABB,
                            out var matty,
                            out var targetAngle))
                    {
                        continue;
                    }

                    // Can't just use the AABB as we want to get bounds as tight as possible.
                    var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
                    spawnPosition = new EntityCoordinates(targetGridXform.MapUid !.Value, spawnPosition.ToMapPos(EntityManager));

                    var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);

                    // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
                    if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
                                                          dockedBounds).Any(o => o.GridEntityId != targetGrid))
                    {
                        continue;
                    }

                    // Alright well the spawn is valid now to check how many we can connect
                    // Get the matrix for each shuttle dock and test it against the grid docks to see
                    // if the connected position / direction matches.

                    var dockedPorts = new List <(DockingComponent DockA, DockingComponent DockB)>()
                    {
                        (shuttleDock, gridDock),
                    };

                    // TODO: Check shuttle orientation as the tiebreaker.

                    foreach (var other in shuttleDocks)
                    {
                        if (other == shuttleDock)
                        {
                            continue;
                        }

                        foreach (var otherGrid in gridDocks)
                        {
                            if (otherGrid == gridDock)
                            {
                                continue;
                            }

                            if (!CanDock(
                                    other,
                                    xformQuery.GetComponent(other.Owner),
                                    otherGrid,
                                    xformQuery.GetComponent(otherGrid.Owner),
                                    targetGridRotation,
                                    shuttleAABB, targetGridGrid,
                                    out var otherDockedAABB,
                                    out _,
                                    out var otherTargetAngle) ||
                                !otherDockedAABB.Equals(dockedAABB) ||
                                !targetAngle.Equals(otherTargetAngle))
                            {
                                continue;
                            }

                            dockedPorts.Add((other, otherGrid));
                        }
                    }

                    var spawnRotation = shuttleDockXform.LocalRotation +
                                        gridXform.LocalRotation +
                                        targetGridXform.LocalRotation;

                    validDockConfigs.Add(new DockingConfig()
                    {
                        Docks       = dockedPorts,
                        Area        = dockedAABB.Value,
                        Coordinates = spawnPosition,
                        Angle       = spawnRotation,
                    });
                }
            }
        }

        if (validDockConfigs.Count <= 0)
        {
            return(null);
        }

        // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
        validDockConfigs = validDockConfigs
                           .OrderByDescending(x => x.Docks.Any(docks => HasComp <EmergencyDockComponent>(docks.DockB.Owner)))
                           .ThenByDescending(x => x.Docks.Count)
                           .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();

        var location = validDockConfigs.First();

        location.TargetGrid = targetGrid;
        // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.

        return(location);
    }
            public void Update(float frameTime)
            {
                Age += TimeSpan.FromSeconds(frameTime);
                if (Age >= Deathtime)
                {
                    return;
                }

                Velocity           += Acceleration * frameTime;
                RadialVelocity     += RadialAcceleration * frameTime;
                TangentialVelocity += TangentialAcceleration * frameTime;

                var deltaPosition = new Vector2(0f, 0f);

                //If we have an emitter we can do special effects around that emitter position
                if (_mapManager.GridExists(EmitterCoordinates.GetGridId(_entityManager)))
                {
                    //Calculate delta p due to radial velocity
                    var positionRelativeToEmitter =
                        Coordinates.ToMapPos(_entityManager) - EmitterCoordinates.ToMapPos(_entityManager);
                    var deltaRadial = RadialVelocity * frameTime;
                    deltaPosition = positionRelativeToEmitter * (deltaRadial / positionRelativeToEmitter.Length);

                    //Calculate delta p due to tangential velocity
                    var radius = positionRelativeToEmitter.Length;
                    if (radius > 0)
                    {
                        var theta = (float)Math.Atan2(positionRelativeToEmitter.Y, positionRelativeToEmitter.X);
                        theta         += TangentialVelocity * frameTime;
                        deltaPosition += new Vector2(radius * (float)Math.Cos(theta), radius * (float)Math.Sin(theta))
                                         - positionRelativeToEmitter;
                    }
                }

                //Calculate new position from our velocity as well as possible rotation/movement around emitter
                deltaPosition += Velocity * frameTime;
                Coordinates    = Coordinates.Offset(deltaPosition);

                //Finish calculating new rotation, size, color
                Rotation += RotationRate * frameTime;
                Size     += SizeDelta * frameTime;
                Color    += ColorDelta * frameTime;

                if (RsiState == null)
                {
                    return;
                }

                // Calculate RSI animations.
                var delayCount = RsiState.DelayCount;

                if (delayCount > 0 && (AnimationLoops || AnimationIndex < delayCount - 1))
                {
                    AnimationTime += frameTime;
                    while (RsiState.GetDelay(AnimationIndex) < AnimationTime)
                    {
                        var delay = RsiState.GetDelay(AnimationIndex);
                        AnimationIndex += 1;
                        AnimationTime  -= delay;
                        if (AnimationIndex == delayCount)
                        {
                            if (AnimationLoops)
                            {
                                AnimationIndex = 0;
                            }
                            else
                            {
                                break;
                            }
                        }

                        EffectSprite = RsiState.GetFrame(RSI.State.Direction.South, AnimationIndex);
                    }
                }
            }
Exemple #15
0
    /// <summary>
    /// Fires a round of ammo out of the weapon.
    /// </summary>
    private void Fire(EntityUid shooter, ServerRangedBarrelComponent component, EntityCoordinates coordinates)
    {
        if (component.ShotsLeft == 0)
        {
            SoundSystem.Play(Filter.Pvs(component.Owner), component.SoundEmpty.GetSound(), component.Owner);
            return;
        }

        var ammo = PeekAtAmmo(component);

        if (TakeOutProjectile(component, Transform(shooter).Coordinates) is not {
            Valid: true
        } projectile)
        {
            SoundSystem.Play(Filter.Pvs(component.Owner), component.SoundEmpty.GetSound(), component.Owner);
            return;
        }

        var targetPos = coordinates.ToMapPos(EntityManager);

        // At this point firing is confirmed
        var direction = (targetPos - Transform(shooter).WorldPosition).ToAngle();
        var angle     = GetRecoilAngle(component, direction);

        // This should really be client-side but for now we'll just leave it here
        if (HasComp <CameraRecoilComponent>(shooter))
        {
            var kick = -angle.ToVec() * 0.15f;
            _recoil.KickCamera(shooter, kick);
        }

        // This section probably needs tweaking so there can be caseless hitscan etc.
        if (TryComp(projectile, out HitscanComponent? hitscan))
        {
            FireHitscan(shooter, hitscan, component, angle);
        }
        else if (HasComp <ProjectileComponent>(projectile) &&
                 TryComp(ammo, out AmmoComponent? ammoComponent))
        {
            FireProjectiles(shooter, projectile, component, ammoComponent.ProjectilesFired, ammoComponent.EvenSpreadAngle, angle, ammoComponent.Velocity, ammo !.Value);

            if (component.CanMuzzleFlash)
            {
                MuzzleFlash(component.Owner, ammoComponent, angle);
            }

            if (ammoComponent.Caseless)
            {
                EntityManager.DeleteEntity(ammo.Value);
            }
        }
        else
        {
            // Invalid types
            throw new InvalidOperationException();
        }

        SoundSystem.Play(Filter.Broadcast(), component.SoundGunshot.GetSound(), component.Owner);

        component.Dirty(EntityManager);
        component.LastFire = _gameTiming.CurTime;
    }
        public bool TryPoint(ICommonSession?session, EntityCoordinates coords, EntityUid uid)
        {
            var player = (session as IPlayerSession)?.ContentData()?.Mind?.CurrentEntity;

            if (player == null)
            {
                return(false);
            }

            if (_pointers.TryGetValue(session !, out var lastTime) &&
                _gameTiming.CurTime < lastTime + PointDelay)
            {
                return(false);
            }

            if (EntityManager.TryGetEntity(uid, out var entity) && entity.HasComponent <PointingArrowComponent>())
            {
                // this is a pointing arrow. no pointing here...
                return(false);
            }

            if (!InRange(coords, player.Transform.Coordinates))
            {
                player.PopupMessage(Loc.GetString("You can't reach there!"));
                return(false);
            }

            if (ActionBlockerSystem.CanChangeDirection(player))
            {
                var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position;
                if (diff.LengthSquared > 0.01f)
                {
                    player.Transform.LocalRotation = new Angle(diff);
                }
            }

            var arrow = EntityManager.SpawnEntity("pointingarrow", coords);

            var layer = (int)VisibilityFlags.Normal;

            if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
            {
                var arrowVisibility = arrow.EnsureComponent <VisibilityComponent>();
                layer = arrowVisibility.Layer = playerVisibility.Layer;
            }

            // Get players that are in range and whose visibility layer matches the arrow's.
            var viewers = _playerManager.GetPlayersBy((playerSession) =>
            {
                if ((playerSession.VisibilityMask & layer) == 0)
                {
                    return(false);
                }

                var ent = playerSession.ContentData()?.Mind?.CurrentEntity;

                return(ent != null &&
                       ent.Transform.MapPosition.InRange(player.Transform.MapPosition, PointingRange));
            });

            string selfMessage;
            string viewerMessage;
            string?viewerPointedAtMessage = null;

            if (EntityManager.TryGetEntity(uid, out var pointed))
            {
                selfMessage = player == pointed
                    ? Loc.GetString("You point at yourself.")
                    : Loc.GetString("You point at {0:theName}.", pointed);

                viewerMessage = player == pointed
                    ? $"{player.Name} {Loc.GetString("points at {0:themself}.", player)}"
                    : $"{player.Name} {Loc.GetString("points at {0:theName}.", pointed)}";

                viewerPointedAtMessage = $"{player.Name} {Loc.GetString("points at you.")}";
            }
            else
            {
                var tileRef = _mapManager.GetGrid(coords.GetGridId(EntityManager)).GetTileRef(coords);
                var tileDef = _tileDefinitionManager[tileRef.Tile.TypeId];

                selfMessage = Loc.GetString("You point at {0}.", tileDef.DisplayName);

                viewerMessage = $"{player.Name} {Loc.GetString("points at {0}.", tileDef.DisplayName)}";
            }

            _pointers[session !] = _gameTiming.CurTime;