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; }
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); }
/// <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; }
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; }