private async void HandleAfterInteract(EntityUid uid, SpawnAfterInteractComponent component, AfterInteractEvent args)
        {
            if (string.IsNullOrEmpty(component.Prototype))
            {
                return;
            }
            if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridId(EntityManager), out var grid))
            {
                return;
            }
            if (!grid.TryGetTileRef(args.ClickLocation, out var tileRef))
            {
                return;
            }

            bool IsTileClear()
            {
                return(tileRef.Tile.IsEmpty == false);
            }

            if (!IsTileClear())
            {
                return;
            }

            if (component.DoAfterTime > 0)
            {
                var doAfterArgs = new DoAfterEventArgs(args.User, component.DoAfterTime)
                {
                    BreakOnUserMove = true,
                    BreakOnStun     = true,
                    PostCheck       = IsTileClear,
                };
                var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);

                if (result != DoAfterStatus.Finished)
                {
                    return;
                }
            }

            if (component.Deleted || Deleted(component.Owner))
            {
                return;
            }

            if (EntityManager.TryGetComponent <SharedStackComponent?>(component.Owner, out var stackComp) &&
                component.RemoveOnInteract && !_stackSystem.Use(uid, 1, stackComp))
            {
                return;
            }

            EntityManager.SpawnEntity(component.Prototype, args.ClickLocation.SnapToGrid(grid));

            if (component.RemoveOnInteract && stackComp == null && !((!EntityManager.EntityExists(component.Owner) ? EntityLifeStage.Deleted : EntityManager.GetComponent <MetaDataComponent>(component.Owner).EntityLifeStage) >= EntityLifeStage.Deleted))
            {
                EntityManager.DeleteEntity(component.Owner);
            }
        }
        private async void OnAfterInteract(EntityUid uid, RCDComponent rcd, AfterInteractEvent args)
        {
            if (args.Handled || !args.CanReach)
            {
                return;
            }

            if (rcd.CancelToken != null)
            {
                rcd.CancelToken?.Cancel();
                rcd.CancelToken = null;
                args.Handled    = true;
                return;
            }

            if (!args.ClickLocation.IsValid(EntityManager))
            {
                return;
            }

            var clickLocationMod = args.ClickLocation;

            // Initial validity check
            if (!clickLocationMod.IsValid(EntityManager))
            {
                return;
            }
            // Try to fix it (i.e. if clicking on space)
            // Note: Ideally there'd be a better way, but there isn't right now.
            var gridID = clickLocationMod.GetGridId(EntityManager);

            if (!gridID.IsValid())
            {
                clickLocationMod = clickLocationMod.AlignWithClosestGridTile();
                gridID           = clickLocationMod.GetGridId(EntityManager);
            }
            // Check if fixing it failed / get final grid ID
            if (!gridID.IsValid())
            {
                return;
            }

            var mapGrid = _mapManager.GetGrid(gridID);
            var tile    = mapGrid.GetTileRef(clickLocationMod);
            var snapPos = mapGrid.TileIndicesFor(clickLocationMod);

            //No changing mode mid-RCD
            var startingMode = rcd.Mode;

            args.Handled = true;
            var user = args.User;

            //Using an RCD isn't instantaneous
            rcd.CancelToken = new CancellationTokenSource();
            var doAfterEventArgs = new DoAfterEventArgs(user, rcd.Delay, rcd.CancelToken.Token, args.Target)
            {
                BreakOnDamage = true,
                BreakOnStun   = true,
                NeedHand      = true,
                ExtraCheck    = () => IsRCDStillValid(rcd, args, mapGrid, tile, startingMode) //All of the sanity checks are here
            };

            var result = await _doAfterSystem.WaitDoAfter(doAfterEventArgs);

            rcd.CancelToken = null;

            if (result == DoAfterStatus.Cancelled)
            {
                return;
            }

            switch (rcd.Mode)
            {
            //Floor mode just needs the tile to be a space tile (subFloor)
            case RcdMode.Floors:
                mapGrid.SetTile(snapPos, new Tile(_tileDefinitionManager["floor_steel"].TileId));
                _logs.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} {snapPos} to floor_steel");
                break;

            //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
            case RcdMode.Deconstruct:
                if (!tile.IsBlockedTurf(true))     //Delete the turf
                {
                    mapGrid.SetTile(snapPos, Tile.Empty);
                    _logs.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridIndex} tile: {snapPos} to space");
                }
                else     //Delete what the user targeted
                {
                    if (args.Target is { Valid : true } target)
                    {
                        _logs.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to delete {ToPrettyString(target):target}");
                        QueueDel(target);
                    }
                }
                break;

            //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile,
            // thus we early return to avoid the tile set code.
            case RcdMode.Walls:
                var ent = EntityManager.SpawnEntity("WallSolid", mapGrid.GridTileToLocal(snapPos));
                Transform(ent).LocalRotation = Angle.Zero;     // Walls always need to point south.
                _logs.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(ent)} at {snapPos} on grid {mapGrid.Index}");
                break;

            case RcdMode.Airlock:
                var airlock = EntityManager.SpawnEntity("Airlock", mapGrid.GridTileToLocal(snapPos));
                Transform(airlock).LocalRotation = Transform(rcd.Owner).LocalRotation;     //Now apply icon smoothing.
                _logs.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(airlock)} at {snapPos} on grid {mapGrid.Index}");
                break;

            default:
                args.Handled = true;
                return;     //I don't know why this would happen, but sure I guess. Get out of here invalid state!
            }

            SoundSystem.Play(Filter.Pvs(uid, entityManager: EntityManager), rcd.SuccessSound.GetSound(), rcd.Owner);
            rcd.CurrentAmmo--;
            args.Handled = true;
        }
Exemple #3
0
        async Task <bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
        {
            // FIXME: Make this work properly. Right now it relies on the click location being on a grid, which is bad.
            if (!eventArgs.ClickLocation.IsValid(Owner.EntityManager) || !eventArgs.ClickLocation.GetGridId(Owner.EntityManager).IsValid())
            {
                return(false);
            }

            //No changing mode mid-RCD
            var startingMode = _mode;

            var mapGrid = _mapManager.GetGrid(eventArgs.ClickLocation.GetGridId(Owner.EntityManager));
            var tile    = mapGrid.GetTileRef(eventArgs.ClickLocation);
            var snapPos = mapGrid.TileIndicesFor(eventArgs.ClickLocation);

            //Using an RCD isn't instantaneous
            var cancelToken      = new CancellationTokenSource();
            var doAfterEventArgs = new DoAfterEventArgs(eventArgs.User, _delay, cancelToken.Token, eventArgs.Target)
            {
                BreakOnDamage = true,
                BreakOnStun   = true,
                NeedHand      = true,
                ExtraCheck    = () => IsRCDStillValid(eventArgs, mapGrid, tile, snapPos, startingMode) //All of the sanity checks are here
            };

            var result = await _doAfterSystem.WaitDoAfter(doAfterEventArgs);

            if (result == DoAfterStatus.Cancelled)
            {
                return(true);
            }

            switch (_mode)
            {
            //Floor mode just needs the tile to be a space tile (subFloor)
            case RcdMode.Floors:
                mapGrid.SetTile(eventArgs.ClickLocation, new Robust.Shared.Map.Tile(_tileDefinitionManager["floor_steel"].TileId));
                break;

            //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
            case RcdMode.Deconstruct:
                if (!tile.IsBlockedTurf(true))     //Delete the turf
                {
                    mapGrid.SetTile(snapPos, Robust.Shared.Map.Tile.Empty);
                }
                else     //Delete what the user targeted
                {
                    eventArgs.Target?.Delete();
                }
                break;

            //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code.
            case RcdMode.Walls:
                var ent = _serverEntityManager.SpawnEntity("WallSolid", mapGrid.GridTileToLocal(snapPos));
                ent.Transform.LocalRotation = Angle.Zero;     // Walls always need to point south.
                break;

            case RcdMode.Airlock:
                var airlock = _serverEntityManager.SpawnEntity("Airlock", mapGrid.GridTileToLocal(snapPos));
                airlock.Transform.LocalRotation = Owner.Transform.LocalRotation;     //Now apply icon smoothing.
                break;

            default:
                return(true);    //I don't know why this would happen, but sure I guess. Get out of here invalid state!
            }

            SoundSystem.Play(Filter.Pvs(Owner), _successSound.GetSound(), Owner);
            _ammo--;
            return(true);
        }
Exemple #4
0
        /// <summary>
        /// Allows a user to pick up entities by clicking them, or pick up all entities in a certain radius
        /// around a click.
        /// </summary>
        /// <returns></returns>
        private async void AfterInteract(EntityUid uid, ServerStorageComponent storageComp, AfterInteractEvent eventArgs)
        {
            if (!eventArgs.CanReach)
            {
                return;
            }

            if (storageComp.CancelToken != null)
            {
                storageComp.CancelToken.Cancel();
                storageComp.CancelToken = null;
                return;
            }

            // Pick up all entities in a radius around the clicked location.
            // The last half of the if is because carpets exist and this is terrible
            if (storageComp.AreaInsert && (eventArgs.Target == null || !HasComp <ItemComponent>(eventArgs.Target.Value)))
            {
                var validStorables = new List <EntityUid>();
                foreach (var entity in _entityLookupSystem.GetEntitiesInRange(eventArgs.ClickLocation, storageComp.AreaInsertRadius, LookupFlags.None))
                {
                    if (entity == eventArgs.User ||
                        !HasComp <ItemComponent>(entity) ||
                        !_interactionSystem.InRangeUnobstructed(eventArgs.User, entity))
                    {
                        continue;
                    }

                    validStorables.Add(entity);
                }

                //If there's only one then let's be generous
                if (validStorables.Count > 1)
                {
                    storageComp.CancelToken = new CancellationTokenSource();
                    var doAfterArgs = new DoAfterEventArgs(eventArgs.User, 0.2f * validStorables.Count, storageComp.CancelToken.Token, uid)
                    {
                        BreakOnStun     = true,
                        BreakOnDamage   = true,
                        BreakOnUserMove = true,
                        NeedHand        = true,
                    };

                    await _doAfterSystem.WaitDoAfter(doAfterArgs);
                }

                // TODO: Make it use the event DoAfter
                var successfullyInserted          = new List <EntityUid>();
                var successfullyInsertedPositions = new List <EntityCoordinates>();
                foreach (var entity in validStorables)
                {
                    // Check again, situation may have changed for some entities, but we'll still pick up any that are valid
                    if (_containerSystem.IsEntityInContainer(entity) ||
                        entity == eventArgs.User ||
                        !HasComp <ItemComponent>(entity))
                    {
                        continue;
                    }

                    if (TryComp <TransformComponent>(uid, out var transformOwner) && TryComp <TransformComponent>(entity, out var transformEnt))
                    {
                        var position = EntityCoordinates.FromMap(transformOwner.Parent?.Owner ?? uid, transformEnt.MapPosition);

                        if (PlayerInsertEntityInWorld(uid, eventArgs.User, entity, storageComp))
                        {
                            successfullyInserted.Add(entity);
                            successfullyInsertedPositions.Add(position);
                        }
                    }
                }

                // If we picked up atleast one thing, play a sound and do a cool animation!
                if (successfullyInserted.Count > 0)
                {
                    if (storageComp.StorageInsertSound is not null)
                    {
                        SoundSystem.Play(storageComp.StorageInsertSound.GetSound(), Filter.Pvs(uid, entityManager: EntityManager), uid, AudioParams.Default);
                    }
                    RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid, successfullyInserted, successfullyInsertedPositions));
                }
                return;
            }
            // Pick up the clicked entity
            else if (storageComp.QuickInsert)
            {
                if (eventArgs.Target is not {
                    Valid : true
                } target)
                {
                    return;
                }

                if (_containerSystem.IsEntityInContainer(target) ||
                    target == eventArgs.User ||
                    !HasComp <ItemComponent>(target))
                {
                    return;
                }

                if (TryComp <TransformComponent>(uid, out var transformOwner) && TryComp <TransformComponent>(target, out var transformEnt))
                {
                    var parent = transformOwner.ParentUid;

                    var position = EntityCoordinates.FromMap(
                        parent.IsValid() ? parent : uid,
                        transformEnt.MapPosition);
                    if (PlayerInsertEntityInWorld(uid, eventArgs.User, target, storageComp))
                    {
                        RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(uid,
                                                                            new List <EntityUid> {
                            target
                        },
                                                                            new List <EntityCoordinates> {
                            position
                        }));
                    }
                }
            }
            return;
        }
Exemple #5
0
        private async void OnAfterInteract(EntityUid uid, RCDComponent rcd, AfterInteractEvent args)
        {
            if (args.Handled)
            {
                return;
            }

            // FIXME: Make this work properly. Right now it relies on the click location being on a grid, which is bad.
            var clickLocationMod = args.ClickLocation;

            // Initial validity check
            if (!clickLocationMod.IsValid(EntityManager))
            {
                return;
            }
            // Try to fix it (i.e. if clicking on space)
            // Note: Ideally there'd be a better way, but there isn't right now.
            var gridID = clickLocationMod.GetGridId(EntityManager);

            if (!gridID.IsValid())
            {
                clickLocationMod = clickLocationMod.AlignWithClosestGridTile();
                gridID           = clickLocationMod.GetGridId(EntityManager);
            }
            // Check if fixing it failed / get final grid ID
            if (!gridID.IsValid())
            {
                return;
            }

            var mapGrid = _mapManager.GetGrid(gridID);
            var tile    = mapGrid.GetTileRef(clickLocationMod);
            var snapPos = mapGrid.TileIndicesFor(clickLocationMod);

            //No changing mode mid-RCD
            var startingMode = rcd.Mode;

            //Using an RCD isn't instantaneous
            var cancelToken      = new CancellationTokenSource();
            var doAfterEventArgs = new DoAfterEventArgs(args.User, rcd.Delay, cancelToken.Token, args.Target)
            {
                BreakOnDamage = true,
                BreakOnStun   = true,
                NeedHand      = true,
                ExtraCheck    = () => IsRCDStillValid(rcd, args, mapGrid, tile, snapPos, startingMode) //All of the sanity checks are here
            };

            var result = await _doAfterSystem.WaitDoAfter(doAfterEventArgs);

            if (result == DoAfterStatus.Cancelled)
            {
                args.Handled = true;
                return;
            }

            switch (rcd.Mode)
            {
            //Floor mode just needs the tile to be a space tile (subFloor)
            case RcdMode.Floors:
                mapGrid.SetTile(clickLocationMod, new Tile(_tileDefinitionManager["floor_steel"].TileId));
                break;

            //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
            case RcdMode.Deconstruct:
                if (!tile.IsBlockedTurf(true))     //Delete the turf
                {
                    mapGrid.SetTile(snapPos, Tile.Empty);
                }
                else     //Delete what the user targeted
                {
                    args.Target?.Delete();
                }
                break;

            //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile,
            // thus we early return to avoid the tile set code.
            case RcdMode.Walls:
                var ent = EntityManager.SpawnEntity("WallSolid", mapGrid.GridTileToLocal(snapPos));
                ent.Transform.LocalRotation = Angle.Zero;     // Walls always need to point south.
                break;

            case RcdMode.Airlock:
                var airlock = EntityManager.SpawnEntity("Airlock", mapGrid.GridTileToLocal(snapPos));
                airlock.Transform.LocalRotation = rcd.Owner.Transform.LocalRotation;     //Now apply icon smoothing.
                break;

            default:
                args.Handled = true;
                return;     //I don't know why this would happen, but sure I guess. Get out of here invalid state!
            }

            SoundSystem.Play(Filter.Pvs(uid), rcd.SuccessSound.GetSound(), rcd.Owner);
            rcd.CurrentAmmo--;
            args.Handled = true;
        }