diff --git a/Content.Server/Tools/Components/LatticeCuttingComponent.cs b/Content.Server/Tools/Components/LatticeCuttingComponent.cs new file mode 100644 index 0000000000..468113f4bd --- /dev/null +++ b/Content.Server/Tools/Components/LatticeCuttingComponent.cs @@ -0,0 +1,30 @@ +using System.Threading; +using Content.Shared.Tools; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Tools.Components; + +[RegisterComponent] +public sealed class LatticeCuttingComponent : Component +{ + [ViewVariables] + [DataField("toolComponentNeeded")] + public bool ToolComponentNeeded = true; + + [ViewVariables] + [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string QualityNeeded = "Cutting"; + + [ViewVariables] + [DataField("delay")] + public float Delay = 0.25f; + + [ViewVariables] + [DataField("vacuumDelay")] + public float VacuumDelay = 1.75f; + + /// + /// Used for do_afters. + /// + public CancellationTokenSource? CancelTokenSource = null; +} \ No newline at end of file diff --git a/Content.Server/Tools/ToolSystem.LatticeCutting.cs b/Content.Server/Tools/ToolSystem.LatticeCutting.cs new file mode 100644 index 0000000000..4bf3577932 --- /dev/null +++ b/Content.Server/Tools/ToolSystem.LatticeCutting.cs @@ -0,0 +1,101 @@ +using System.Threading; +using Content.Server.Tools.Components; +using Content.Shared.Interaction; +using Content.Shared.Maps; +using Content.Shared.Tools.Components; +using Robust.Shared.Map; + +namespace Content.Server.Tools; + +public sealed partial class ToolSystem +{ + private void InitializeLatticeCutting() + { + SubscribeLocalEvent(OnLatticeCuttingAfterInteract); + SubscribeLocalEvent(OnLatticeCutComplete); + SubscribeLocalEvent(OnLatticeCutCancelled); + } + + private void OnLatticeCutCancelled(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCancelledEvent args) + { + component.CancelTokenSource = null; + } + + private void OnLatticeCutComplete(EntityUid uid, LatticeCuttingComponent component, LatticeCuttingCompleteEvent args) + { + component.CancelTokenSource = null; + var gridUid = args.Coordinates.GetGridUid(EntityManager); + if (gridUid == null) + return; + var grid = _mapManager.GetGrid(gridUid.Value); + var tile = grid.GetTileRef(args.Coordinates); + + if (_tileDefinitionManager[tile.Tile.TypeId] is not ContentTileDefinition tileDef + || !tileDef.CanWirecutter + || tileDef.BaseTurfs.Count == 0 + || tile.IsBlockedTurf(true)) + return; + + tile.CutTile(_mapManager, _tileDefinitionManager, EntityManager); + } + + private void OnLatticeCuttingAfterInteract(EntityUid uid, LatticeCuttingComponent component, + AfterInteractEvent args) + { + if (args.Handled || !args.CanReach || args.Target != null) + return; + + if (TryCut(args.User, component, args.ClickLocation)) + args.Handled = true; + } + + private bool TryCut(EntityUid user, LatticeCuttingComponent component, EntityCoordinates clickLocation) + { + if (component.CancelTokenSource != null) + return true; + + ToolComponent? tool = null; + if (component.ToolComponentNeeded && !TryComp(component.Owner, out tool)) + return false; + + if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid)) + return false; + + var tile = mapGrid.GetTileRef(clickLocation); + + var coordinates = mapGrid.GridTileToLocal(tile.GridIndices); + + if (!_interactionSystem.InRangeUnobstructed(user, coordinates, popup: false)) + return false; + + if (_tileDefinitionManager[tile.Tile.TypeId] is not ContentTileDefinition tileDef + || !tileDef.CanWirecutter + || tileDef.BaseTurfs.Count == 0 + || _tileDefinitionManager[tileDef.BaseTurfs[^1]] is not ContentTileDefinition newDef + || tile.IsBlockedTurf(true)) + return false; + + var tokenSource = new CancellationTokenSource(); + component.CancelTokenSource = tokenSource; + + if (!UseTool(component.Owner, user, null, 0f, component.Delay, new[] {component.QualityNeeded}, + new LatticeCuttingCompleteEvent + { + Coordinates = clickLocation + }, new LatticeCuttingCancelledEvent(), toolComponent: tool, doAfterEventTarget: component.Owner, + cancelToken: tokenSource.Token)) + component.CancelTokenSource = null; + + return true; + } + + private sealed class LatticeCuttingCompleteEvent : EntityEventArgs + { + public EntityCoordinates Coordinates { get; set; } + } + + private sealed class LatticeCuttingCancelledEvent : EntityEventArgs + { + } +} + diff --git a/Content.Server/Tools/ToolSystem.cs b/Content.Server/Tools/ToolSystem.cs index b890d1ebdd..f8ddceefe5 100644 --- a/Content.Server/Tools/ToolSystem.cs +++ b/Content.Server/Tools/ToolSystem.cs @@ -32,6 +32,7 @@ namespace Content.Server.Tools base.Initialize(); InitializeTilePrying(); + InitializeLatticeCutting(); InitializeWelders(); SubscribeLocalEvent(OnDoAfterComplete); diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index 395d4e3495..ae1b83e900 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -36,6 +36,8 @@ namespace Content.Shared.Maps [DataField("canCrowbar")] public bool CanCrowbar { get; private set; } + [DataField("canWirecutter")] public bool CanWirecutter { get; private set; } + /// /// These play when the mob has shoes on. /// diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index 48d2cf8583..ffbe19aff5 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -102,13 +102,9 @@ namespace Content.Shared.Maps IRobustRandom? robustRandom = null) { var tile = tileRef.Tile; - var indices = tileRef.GridIndices; // If the arguments are null, resolve the needed dependencies. - mapManager ??= IoCManager.Resolve(); tileDefinitionManager ??= IoCManager.Resolve(); - entityManager ??= IoCManager.Resolve(); - robustRandom ??= IoCManager.Resolve(); if (tile.IsEmpty) return false; @@ -116,19 +112,58 @@ namespace Content.Shared.Maps if (!tileDef.CanCrowbar) return false; + return DeconstructTile(tileRef, mapManager, tileDefinitionManager, entityManager, robustRandom); + } + + public static bool CutTile(this TileRef tileRef, + IMapManager? mapManager = null, + ITileDefinitionManager? tileDefinitionManager = null, + IEntityManager? entityManager = null, + IRobustRandom? robustRandom = null) + { + var tile = tileRef.Tile; + + // If the arguments are null, resolve the needed dependencies. + tileDefinitionManager ??= IoCManager.Resolve(); + + if (tile.IsEmpty) return false; + + var tileDef = (ContentTileDefinition) tileDefinitionManager[tile.TypeId]; + + if (!tileDef.CanWirecutter) return false; + + return DeconstructTile(tileRef, mapManager, tileDefinitionManager, entityManager, robustRandom); + } + + private static bool DeconstructTile(this TileRef tileRef, + IMapManager? mapManager = null, + ITileDefinitionManager? tileDefinitionManager = null, + IEntityManager? entityManager = null, + IRobustRandom? robustRandom = null) + { + var indices = tileRef.GridIndices; + + mapManager ??= IoCManager.Resolve(); + tileDefinitionManager ??= IoCManager.Resolve(); + entityManager ??= IoCManager.Resolve(); + robustRandom ??= IoCManager.Resolve(); + + var tileDef = (ContentTileDefinition) tileDefinitionManager[tileRef.Tile.TypeId]; var mapGrid = mapManager.GetGrid(tileRef.GridUid); + const float margin = 0.1f; + var (x, y) = ((mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin, + (mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin); + var coordinates = mapGrid.GridTileToLocal(indices).Offset(new Vector2(x, y)); + + //Actually spawn the relevant tile item at the right position and give it some random offset. + var tileItem = entityManager.SpawnEntity(tileDef.ItemDropPrototypeName, coordinates); + entityManager.GetComponent(tileItem).LocalRotation + = robustRandom.NextDouble() * Math.Tau; + var plating = tileDefinitionManager[tileDef.BaseTurfs[^1]]; - mapGrid.SetTile(tileRef.GridIndices, new Tile(plating.TileId)); - - const float margin = 0.1f; - - var (x, y) = ((mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin, (mapGrid.TileSize - 2 * margin) * robustRandom.NextFloat() + margin); - - //Actually spawn the relevant tile item at the right position and give it some random offset. - var tileItem = entityManager.SpawnEntity(tileDef.ItemDropPrototypeName, indices.ToEntityCoordinates(tileRef.GridUid, mapManager).Offset(new Vector2(x, y))); - entityManager.GetComponent(tileItem).LocalRotation = robustRandom.NextDouble() * Math.Tau; + mapGrid.SetTile(tileRef.GridIndices, new Tile(plating.TileId)); return true; } diff --git a/Resources/Locale/en-US/tools/components/lattice-cutting-component.ftl b/Resources/Locale/en-US/tools/components/lattice-cutting-component.ftl new file mode 100644 index 0000000000..2fa76d83fc --- /dev/null +++ b/Resources/Locale/en-US/tools/components/lattice-cutting-component.ftl @@ -0,0 +1 @@ +comp-lattice-cutting-unsafe-warning = You feel air blow past your fingers... Maybe you should reconsider? diff --git a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml index e5267db3a5..d5f6273801 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml @@ -19,6 +19,7 @@ slots: - Belt - type: TilePrying + - type: LatticeCutting - type: Tool qualities: - Prying diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 110f362bc9..b2249bf9c6 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -34,6 +34,7 @@ cutters: Rainbow - type: Item sprite: Objects/Tools/wirecutters.rsi + - type: LatticeCutting - type: entity name: screwdriver diff --git a/Resources/Prototypes/Tiles/plating.yml b/Resources/Prototypes/Tiles/plating.yml index 7fd6608ad2..150d71e453 100644 --- a/Resources/Prototypes/Tiles/plating.yml +++ b/Resources/Prototypes/Tiles/plating.yml @@ -18,10 +18,12 @@ baseTurfs: - Space isSubfloor: true + canWirecutter: true footstepSounds: collection: FootstepPlating friction: 0.5 isSpace: true + itemDrop: PartRodMetal1 thermalConductivity: 0.04 heatCapacity: 10000