Merge branch 'master' into offmed-staging

This commit is contained in:
Janet Blackquill
2025-10-05 14:12:34 -04:00
100 changed files with 3760 additions and 2199 deletions

View File

@@ -7,6 +7,18 @@ public sealed class EmpSystem : SharedEmpSystem
{ {
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EmpDisabledComponent, ComponentStartup>(OnStartup);
}
private void OnStartup(Entity<EmpDisabledComponent> ent, ref ComponentStartup args)
{
// EmpPulseEvent.Affected will spawn the first visual effect directly when the emp is used
ent.Comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * ent.Comp.EffectCooldown;
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -16,7 +28,7 @@ public sealed class EmpSystem : SharedEmpSystem
{ {
if (Timing.CurTime > comp.TargetTime) if (Timing.CurTime > comp.TargetTime)
{ {
comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * TimeSpan.FromSeconds(comp.EffectCooldown); comp.TargetTime = Timing.CurTime + _random.NextFloat(0.8f, 1.2f) * comp.EffectCooldown;
Spawn(EmpDisabledEffectPrototype, transform.Coordinates); Spawn(EmpDisabledEffectPrototype, transform.Coordinates);
} }
} }

View File

@@ -73,6 +73,9 @@ namespace Content.Client.Input
human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); human.AddFunction(ContentKeyFunctions.OpenInventoryMenu);
human.AddFunction(ContentKeyFunctions.SmartEquipBackpack); human.AddFunction(ContentKeyFunctions.SmartEquipBackpack);
human.AddFunction(ContentKeyFunctions.SmartEquipBelt); human.AddFunction(ContentKeyFunctions.SmartEquipBelt);
human.AddFunction(ContentKeyFunctions.SmartEquipPocket1);
human.AddFunction(ContentKeyFunctions.SmartEquipPocket2);
human.AddFunction(ContentKeyFunctions.SmartEquipSuitStorage);
human.AddFunction(ContentKeyFunctions.OpenBackpack); human.AddFunction(ContentKeyFunctions.OpenBackpack);
human.AddFunction(ContentKeyFunctions.OpenBelt); human.AddFunction(ContentKeyFunctions.OpenBelt);
human.AddFunction(ContentKeyFunctions.MouseMiddle); human.AddFunction(ContentKeyFunctions.MouseMiddle);

View File

@@ -190,6 +190,9 @@ namespace Content.Client.Options.UI.Tabs
AddHeader("ui-options-header-interaction-adv"); AddHeader("ui-options-header-interaction-adv");
AddButton(ContentKeyFunctions.SmartEquipBackpack); AddButton(ContentKeyFunctions.SmartEquipBackpack);
AddButton(ContentKeyFunctions.SmartEquipBelt); AddButton(ContentKeyFunctions.SmartEquipBelt);
AddButton(ContentKeyFunctions.SmartEquipPocket1);
AddButton(ContentKeyFunctions.SmartEquipPocket2);
AddButton(ContentKeyFunctions.SmartEquipSuitStorage);
AddButton(ContentKeyFunctions.OpenBackpack); AddButton(ContentKeyFunctions.OpenBackpack);
AddButton(ContentKeyFunctions.OpenBelt); AddButton(ContentKeyFunctions.OpenBelt);
AddButton(ContentKeyFunctions.ThrowItemInHand); AddButton(ContentKeyFunctions.ThrowItemInHand);

View File

@@ -0,0 +1,5 @@
using Content.Shared.Power.EntitySystems;
namespace Content.Client.Power.EntitySystems;
public sealed class BatterySystem : SharedBatterySystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared.Power.EntitySystems;
namespace Content.Client.Power.EntitySystems;
public sealed class ChargerSystem : SharedChargerSystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared.SurveillanceCamera;
namespace Content.Client.SurveillanceCamera;
public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem;

View File

@@ -33,6 +33,7 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
component.EjectEnd = state.EjectEnd; component.EjectEnd = state.EjectEnd;
component.DenyEnd = state.DenyEnd; component.DenyEnd = state.DenyEnd;
component.DispenseOnHitEnd = state.DispenseOnHitEnd; component.DispenseOnHitEnd = state.DispenseOnHitEnd;
component.Broken = state.Broken;
// If all we did was update amounts then we can leave BUI buttons in place. // If all we did was update amounts then we can leave BUI buttons in place.
var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) || var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) ||

View File

@@ -0,0 +1,243 @@
using System.Numerics;
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Charges.Systems;
using Content.Shared.RCD;
using Content.Shared.RCD.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Construction;
public sealed class RCDTest : InteractionTest
{
private static readonly EntProtoId RCDProtoId = "RCD";
private static readonly ProtoId<RCDPrototype> RCDSettingWall = "WallSolid";
private static readonly ProtoId<RCDPrototype> RCDSettingAirlock = "Airlock";
private static readonly ProtoId<RCDPrototype> RCDSettingPlating = "Plating";
private static readonly ProtoId<RCDPrototype> RCDSettingFloorSteel = "FloorSteel";
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstruct = "Deconstruct";
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstructTile = "DeconstructTile";
private static readonly ProtoId<RCDPrototype> RCDSettingDeconstructLattice = "DeconstructLattice";
/// <summary>
/// Tests RCD construction and deconstruction, as well as selecting options from the radial menu.
/// </summary>
[Test]
public async Task RCDConstructionDeconstructionTest()
{
// Place some tiles around the player so that we have space to build.
var pNorth = new EntityCoordinates(SPlayer, new Vector2(0, 1));
var pSouth = new EntityCoordinates(SPlayer, new Vector2(0, -1));
var pEast = new EntityCoordinates(SPlayer, new Vector2(1, 0));
var pWest = new EntityCoordinates(SPlayer, new Vector2(-1, 0));
// Use EntityCoordinates relative to the grid because the player turns around when interacting.
pNorth = Transform.WithEntityId(pNorth, MapData.Grid);
pSouth = Transform.WithEntityId(pSouth, MapData.Grid);
pEast = Transform.WithEntityId(pEast, MapData.Grid);
pWest = Transform.WithEntityId(pWest, MapData.Grid);
await SetTile(Plating, SEntMan.GetNetCoordinates(pNorth), MapData.Grid);
await SetTile(Plating, SEntMan.GetNetCoordinates(pSouth), MapData.Grid);
await SetTile(Plating, SEntMan.GetNetCoordinates(pEast), MapData.Grid);
await SetTile(Lattice, SEntMan.GetNetCoordinates(pWest), MapData.Grid);
Assert.That(ProtoMan.TryIndex(RCDSettingWall, out var settingWall), $"RCDPrototype not found: {RCDSettingWall}.");
Assert.That(settingWall.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
Assert.That(ProtoMan.TryIndex(RCDSettingAirlock, out var settingAirlock), $"RCDPrototype not found: {RCDSettingAirlock}.");
Assert.That(settingAirlock.Prototype, Is.Not.Null, $"RCDPrototype has a null spawning prototype.");
Assert.That(ProtoMan.TryIndex(RCDSettingPlating, out var settingPlating), $"RCDPrototype not found: {RCDSettingPlating}.");
Assert.That(settingPlating.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
Assert.That(ProtoMan.TryIndex(RCDSettingFloorSteel, out var settingFloorSteel), $"RCDPrototype not found: {RCDSettingFloorSteel}.");
Assert.That(settingFloorSteel.Prototype, Is.Not.Null, "RCDPrototype has a null spawning prototype.");
Assert.That(ProtoMan.TryIndex(RCDSettingDeconstructTile, out var settingDeconstructTile), $"RCDPrototype not found: {RCDSettingDeconstructTile}.");
Assert.That(ProtoMan.TryIndex(RCDSettingDeconstructLattice, out var settingDeconstructLattice), $"RCDPrototype not found: {RCDSettingDeconstructLattice}.");
var rcd = await PlaceInHands(RCDProtoId);
// Give the RCD enough charges to do everything.
var sCharges = SEntMan.System<SharedChargesSystem>();
await Server.WaitPost(() =>
{
sCharges.SetMaxCharges(ToServer(rcd), 10000);
sCharges.SetCharges(ToServer(rcd), 10000);
});
var initialCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges, Is.EqualTo(10000), "RCD did not have the correct amount of charges.");
// Make sure that picking it up did not open the UI.
Assert.That(IsUiOpen(RcdUiKey.Key), Is.False, "RCD UI was opened when picking it up.");
// Switch to building walls.
await SetRcdProto(rcd, RCDSettingWall);
// Build a wall next to the player.
await Interact(null, pNorth);
// Check that there is exactly one wall.
await RunSeconds(settingWall.Delay + 1); // wait for the construction to finish
await AssertEntityLookup((settingWall.Prototype, 1));
// Check that the wall is in the correct tile.
var wallUid = await FindEntity(settingWall.Prototype);
var wallNetUid = FromServer(wallUid);
AssertLocation(wallNetUid, FromServer(pNorth));
// Check that the cost of the wall was subtracted from the current charges.
var newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingWall.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
initialCharges = newCharges;
// Try building another wall in the same spot.
await Interact(null, pNorth);
await RunSeconds(settingWall.Delay + 1); // wait for the construction to finish
// Check that there is still exactly one wall.
await AssertEntityLookup((settingWall.Prototype, 1));
// Check that the failed construction did not cost us any charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges, Is.EqualTo(newCharges), "RCD has wrong amount of charges after failing to build something.");
// Switch to building airlocks.
await SetRcdProto(rcd, RCDSettingAirlock);
// Build an airlock next to the player.
await Interact(null, pSouth);
// Check that there is exactly one airlock.
await RunSeconds(settingAirlock.Delay + 1); // wait for the construction to finish
await AssertEntityLookup(
(settingWall.Prototype, 1),
(settingAirlock.Prototype, 1)
);
// Check that the airlock is in the correct tile.
var airlockUid = await FindEntity(settingAirlock.Prototype);
var airlockNetUid = FromServer(airlockUid);
AssertLocation(airlockNetUid, FromServer(pSouth));
// Check that the cost of the airlock was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingAirlock.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
initialCharges = newCharges;
// Switch to building plating.
await SetRcdProto(rcd, RCDSettingPlating);
// Try building plating on existing plating.
await AssertTile(settingPlating.Prototype, FromServer(pEast));
await Interact(null, pEast);
// Check that the tile did not change.
await AssertTile(settingPlating.Prototype, FromServer(pEast));
// Check that the failed construction did not cost us any charges.
await RunSeconds(settingPlating.Delay + 1); // wait for the construction to finish
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges, Is.EqualTo(newCharges), "RCD has wrong amount of charges after failing to build something.");
// Try building plating on top of lattice.
await AssertTile(Lattice, FromServer(pWest));
await Interact(null, pWest);
await RunSeconds(settingPlating.Delay + 1); // wait for the construction to finish
// Check that the tile is now plating.
await AssertTile(settingPlating.Prototype, FromServer(pWest));
// Check that the cost of the plating was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingPlating.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
initialCharges = newCharges;
// Switch to building steel tiles.
await SetRcdProto(rcd, RCDSettingFloorSteel);
// Try building a steel tile on top of plating.
await Interact(null, pEast);
// Check that the tile is now a steel tile.
await RunSeconds(settingFloorSteel.Delay + 1); // wait for the construction to finish
await AssertTile(settingFloorSteel.Prototype, FromServer(pEast));
// Check that the cost of the plating was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingFloorSteel.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after building something.");
initialCharges = newCharges;
// Switch to deconstruction mode.
await SetRcdProto(rcd, RCDSettingDeconstruct);
// Deconstruct the wall.
Assert.That(SEntMan.TryGetComponent<RCDDeconstructableComponent>(wallUid, out var wallComp), "Wall entity did not have the RCDDeconstructableComponent.");
await Interact(wallUid, pNorth);
await RunSeconds(wallComp.Delay + 1); // wait for the deconstruction to finish
AssertDeleted(wallNetUid);
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - wallComp.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
initialCharges = newCharges;
// Deconstruct the airlock.
Assert.That(SEntMan.TryGetComponent<RCDDeconstructableComponent>(airlockUid, out var airlockComp), "Wall entity did not have the RCDDeconstructableComponent.");
await Interact(airlockUid, pSouth);
await RunSeconds(airlockComp.Delay + 1); // wait for the deconstruction to finish
AssertDeleted(airlockNetUid);
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - airlockComp.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
initialCharges = newCharges;
// Deconstruct the steel tile.
await Interact(null, pEast);
await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
await AssertTile(Lattice, FromServer(pEast));
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingDeconstructTile.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
initialCharges = newCharges;
// Deconstruct the plating.
await Interact(null, pWest);
await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
await AssertTile(Lattice, FromServer(pWest));
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingDeconstructTile.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
initialCharges = newCharges;
// Deconstruct the lattice.
await Interact(null, pWest);
await RunSeconds(settingDeconstructLattice.Delay + 1); // wait for the deconstruction to finish
await AssertTile(null, FromServer(pWest));
// Check that the cost of the deconstruction was subtracted from the current charges.
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
Assert.That(initialCharges - settingDeconstructLattice.Cost, Is.EqualTo(newCharges), "RCD has wrong amount of charges after deconstructing something.");
// Wait for the visual effect to disappear.
await RunSeconds(3);
// Check that there are no entities left.
await AssertEntityLookup();
}
private async Task SetRcdProto(NetEntity rcd, ProtoId<RCDPrototype> protoId)
{
await UseInHand();
await RunTicks(3);
Assert.That(IsUiOpen(RcdUiKey.Key), Is.True, "RCD UI was not opened when using the RCD while holding it.");
// Simulating a click on the right control for nested radial menus is very complicated.
// So we just manually send a networking message from the client to tell the server we selected an option.
// TODO: Write a separate test for clicking through a SimpleRadialMenu.
await SendBui(RcdUiKey.Key, new RCDSystemMessage(protoId), rcd);
await CloseBui(RcdUiKey.Key, rcd);
Assert.That(IsUiOpen(RcdUiKey.Key), Is.False, "RCD UI is still open.");
}
}

View File

@@ -726,7 +726,7 @@ public abstract partial class InteractionTest
tile = MapSystem.GetTileRef(gridUid, grid, serverCoords).Tile; tile = MapSystem.GetTileRef(gridUid, grid, serverCoords).Tile;
}); });
Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId)); Assert.That(tile.TypeId, Is.EqualTo(targetTile.TypeId), $"Expected tile at NetCoordinates {coords}: {TileMan[targetTile.TypeId].Name}. But was: {TileMan[tile.TypeId].Name}");
} }
protected void AssertGridCount(int value) protected void AssertGridCount(int value)
@@ -742,6 +742,20 @@ public abstract partial class InteractionTest
Assert.That(count, Is.EqualTo(value)); Assert.That(count, Is.EqualTo(value));
} }
/// <summary>
/// Check that some entity is close to a certain coordinate location.
/// </summary>
/// <param name="target">The entity to check the location for. Defaults to <see cref="target"/></param>
/// <param name="coordinates">The coordinates the entity should be at.</param>
/// <param name="radius">The maximum allowed distance from the target coords</param>
protected void AssertLocation(NetEntity? target, NetCoordinates coords, float radius = 0.01f)
{
target ??= Target;
Assert.That(target, Is.Not.Null, "No target specified");
Assert.That(Position(target!.Value).TryDelta(SEntMan, Transform, ToServer(coords), out var delta), "Could not calculate distance between coordinates.");
Assert.That(delta.Length(), Is.LessThanOrEqualTo(radius), $"{SEntMan.ToPrettyString(SEntMan.GetEntity(target.Value))} was not at the intended location. Distance: {delta}, allowed distance: {radius}");
}
#endregion #endregion
#region Entity lookups #region Entity lookups
@@ -986,9 +1000,9 @@ public abstract partial class InteractionTest
/// <summary> /// <summary>
/// Sends a bui message using the given bui key. /// Sends a bui message using the given bui key.
/// </summary> /// </summary>
protected async Task SendBui(Enum key, BoundUserInterfaceMessage msg, EntityUid? _ = null) protected async Task SendBui(Enum key, BoundUserInterfaceMessage msg, NetEntity? target = null)
{ {
if (!TryGetBui(key, out var bui)) if (!TryGetBui(key, out var bui, target))
return; return;
await Client.WaitPost(() => bui.SendMessage(msg)); await Client.WaitPost(() => bui.SendMessage(msg));
@@ -1000,9 +1014,9 @@ public abstract partial class InteractionTest
/// <summary> /// <summary>
/// Sends a bui message using the given bui key. /// Sends a bui message using the given bui key.
/// </summary> /// </summary>
protected async Task CloseBui(Enum key, EntityUid? _ = null) protected async Task CloseBui(Enum key, NetEntity? target = null)
{ {
if (!TryGetBui(key, out var bui)) if (!TryGetBui(key, out var bui, target))
return; return;
await Client.WaitPost(() => bui.Close()); await Client.WaitPost(() => bui.Close());
@@ -1424,15 +1438,25 @@ public abstract partial class InteractionTest
protected EntityUid? ToServer(NetEntity? nent) => SEntMan.GetEntity(nent); protected EntityUid? ToServer(NetEntity? nent) => SEntMan.GetEntity(nent);
protected EntityUid? ToClient(NetEntity? nent) => CEntMan.GetEntity(nent); protected EntityUid? ToClient(NetEntity? nent) => CEntMan.GetEntity(nent);
protected EntityUid ToServer(EntityUid cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid)); protected EntityUid ToServer(EntityUid cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
protected EntityUid ToClient(EntityUid cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid)); protected EntityUid ToClient(EntityUid suid) => CEntMan.GetEntity(SEntMan.GetNetEntity(suid));
protected EntityUid? ToServer(EntityUid? cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid)); protected EntityUid? ToServer(EntityUid? cuid) => SEntMan.GetEntity(CEntMan.GetNetEntity(cuid));
protected EntityUid? ToClient(EntityUid? cuid) => CEntMan.GetEntity(SEntMan.GetNetEntity(cuid)); protected EntityUid? ToClient(EntityUid? suid) => CEntMan.GetEntity(SEntMan.GetNetEntity(suid));
protected EntityCoordinates ToServer(NetCoordinates coords) => SEntMan.GetCoordinates(coords); protected EntityCoordinates ToServer(NetCoordinates coords) => SEntMan.GetCoordinates(coords);
protected EntityCoordinates ToClient(NetCoordinates coords) => CEntMan.GetCoordinates(coords); protected EntityCoordinates ToClient(NetCoordinates coords) => CEntMan.GetCoordinates(coords);
protected EntityCoordinates? ToServer(NetCoordinates? coords) => SEntMan.GetCoordinates(coords); protected EntityCoordinates? ToServer(NetCoordinates? coords) => SEntMan.GetCoordinates(coords);
protected EntityCoordinates? ToClient(NetCoordinates? coords) => CEntMan.GetCoordinates(coords); protected EntityCoordinates? ToClient(NetCoordinates? coords) => CEntMan.GetCoordinates(coords);
protected NetEntity FromServer(EntityUid suid) => SEntMan.GetNetEntity(suid);
protected NetEntity FromClient(EntityUid cuid) => CEntMan.GetNetEntity(cuid);
protected NetEntity? FromServer(EntityUid? suid) => SEntMan.GetNetEntity(suid);
protected NetEntity? FromClient(EntityUid? cuid) => CEntMan.GetNetEntity(cuid);
protected NetCoordinates FromServer(EntityCoordinates scoords) => SEntMan.GetNetCoordinates(scoords);
protected NetCoordinates FromClient(EntityCoordinates ccoords) => CEntMan.GetNetCoordinates(ccoords);
protected NetCoordinates? FromServer(EntityCoordinates? scoords) => SEntMan.GetNetCoordinates(scoords);
protected NetCoordinates? FromClient(EntityCoordinates? ccoords) => CEntMan.GetNetCoordinates(ccoords);
#endregion #endregion
#region Metadata & Transforms #region Metadata & Transforms

View File

@@ -1,12 +1,11 @@
#nullable enable #nullable enable
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Power.Nodes; using Content.Server.Power.Nodes;
using Content.Shared.Coordinates; using Content.Shared.Coordinates;
using Content.Shared.NodeContainer; using Content.Shared.NodeContainer;
using Content.Shared.Power.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;

View File

@@ -5,6 +5,7 @@ using Content.Server.Maps;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.NodeGroups; using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using Content.Shared.Power.Components;
using Content.Shared.NodeContainer; using Content.Shared.NodeContainer;
using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization;

View File

@@ -5,7 +5,6 @@ using Content.Server.Body.Systems;
using Content.Server.Electrocution; using Content.Server.Electrocution;
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Server.GhostKick; using Content.Server.GhostKick;
using Content.Server.Medical;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Server.Physics.Components; using Content.Server.Physics.Components;
using Content.Server.Pointing.Components; using Content.Server.Pointing.Components;
@@ -32,6 +31,7 @@ using Content.Shared.Electrocution;
using Content.Shared.Gravity; using Content.Shared.Gravity;
using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Components;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Medical;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;

View File

@@ -24,6 +24,7 @@ using Content.Shared.Doors.Components;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Power.Components;
using Content.Shared.Stacks; using Content.Shared.Stacks;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;

View File

@@ -1,14 +1,10 @@
using System.Linq;
using Content.Server.Emp;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Emp; using Content.Shared.Emp;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components; using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Prototypes; using Content.Shared.Prototypes;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Clothing.Systems; namespace Content.Server.Clothing.Systems;
@@ -16,15 +12,12 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
{ {
[Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected); SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args)
@@ -37,21 +30,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
SetSelectedPrototype(uid, args.SelectedId, component: component); SetSelectedPrototype(uid, args.SelectedId, component: component);
} }
private void OnEmpPulse(EntityUid uid, ChameleonClothingComponent component, ref EmpPulseEvent args)
{
if (!component.AffectedByEmp)
return;
if (component.EmpContinuous)
component.NextEmpChange = _timing.CurTime + TimeSpan.FromSeconds(1f / component.EmpChangeIntensity);
var pick = GetRandomValidPrototype(component.Slot, component.RequireTag);
SetSelectedPrototype(uid, pick, component: component);
args.Affected = true;
args.Disabled = true;
}
private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null) private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
@@ -64,7 +42,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
/// <summary> /// <summary>
/// Change chameleon items name, description and sprite to mimic other entity prototype. /// Change chameleon items name, description and sprite to mimic other entity prototype.
/// </summary> /// </summary>
public void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false, public override void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false,
ChameleonClothingComponent? component = null) ChameleonClothingComponent? component = null)
{ {
if (!Resolve(uid, ref component, false)) if (!Resolve(uid, ref component, false))
@@ -88,14 +66,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
Dirty(uid, component); Dirty(uid, component);
} }
/// <summary>
/// Get a random prototype for a given slot.
/// </summary>
public string GetRandomValidPrototype(SlotFlags slot, string? tag = null)
{
return _random.Pick(GetValidTargets(slot, tag).ToList());
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -106,7 +76,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
if (!chameleon.EmpContinuous) if (!chameleon.EmpContinuous)
continue; continue;
if (_timing.CurTime < chameleon.NextEmpChange) if (Timing.CurTime < chameleon.NextEmpChange)
continue; continue;
// randomly pick cloth element from available and apply it // randomly pick cloth element from available and apply it

View File

@@ -1,7 +1,7 @@
using Content.Server.Mech.Systems; using Content.Server.Mech.Systems;
using Content.Server.Power.Components;
using Content.Shared.Construction; using Content.Shared.Construction;
using Content.Shared.Mech.Components; using Content.Shared.Mech.Components;
using Content.Shared.Power.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Containers; using Robust.Server.Containers;
using Robust.Shared.Containers; using Robust.Shared.Containers;

View File

@@ -1,4 +1,4 @@
using Content.Server.Medical; using Content.Shared.Medical;
namespace Content.Server.Destructible.Thresholds.Behaviors; namespace Content.Server.Destructible.Thresholds.Behaviors;

View File

@@ -2,16 +2,11 @@ using Content.Server.Power.EntitySystems;
using Content.Server.Radio; using Content.Server.Radio;
using Content.Server.SurveillanceCamera; using Content.Server.SurveillanceCamera;
using Content.Shared.Emp; using Content.Shared.Emp;
using Robust.Shared.Map;
namespace Content.Server.Emp; namespace Content.Server.Emp;
public sealed class EmpSystem : SharedEmpSystem public sealed class EmpSystem : SharedEmpSystem
{ {
[Dependency] private readonly EntityLookupSystem _lookup = default!;
public const string EmpPulseEffectPrototype = "EffectEmpPulse";
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -22,84 +17,6 @@ public sealed class EmpSystem : SharedEmpSystem
SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive); SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive);
} }
public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
{
foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
{
TryEmpEffects(uid, energyConsumption, duration);
}
Spawn(EmpPulseEffectPrototype, coordinates);
}
/// <summary>
/// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
/// </summary>
/// <param name="coordinates">The location to trigger the EMP pulse at.</param>
/// <param name="range">The range of the EMP pulse.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, float duration)
{
foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
{
TryEmpEffects(uid, energyConsumption, duration);
}
Spawn(EmpPulseEffectPrototype, coordinates);
}
/// <summary>
/// Attempts to apply the effects of an EMP pulse onto an entity by first raising an <see cref="EmpAttemptEvent"/>, followed by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void TryEmpEffects(EntityUid uid, float energyConsumption, float duration)
{
var attemptEv = new EmpAttemptEvent();
RaiseLocalEvent(uid, attemptEv);
if (attemptEv.Cancelled)
return;
DoEmpEffects(uid, energyConsumption, duration);
}
/// <summary>
/// Applies the effects of an EMP pulse onto an entity by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
public void DoEmpEffects(EntityUid uid, float energyConsumption, float duration)
{
var ev = new EmpPulseEvent(energyConsumption, false, false, TimeSpan.FromSeconds(duration));
RaiseLocalEvent(uid, ref ev);
if (ev.Affected)
Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates);
if (!ev.Disabled)
return;
var disabled = EnsureComp<EmpDisabledComponent>(uid);
disabled.DisabledUntil = Timing.CurTime + TimeSpan.FromSeconds(duration);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<EmpDisabledComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.DisabledUntil < Timing.CurTime)
{
RemComp<EmpDisabledComponent>(uid);
var ev = new EmpDisabledRemoved();
RaiseLocalEvent(uid, ref ev);
}
}
}
private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args) private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args)
{ {
args.Cancelled = true; args.Cancelled = true;
@@ -120,14 +37,3 @@ public sealed class EmpSystem : SharedEmpSystem
args.Cancelled = true; args.Cancelled = true;
} }
} }
/// <summary>
/// Raised on an entity before <see cref="EmpPulseEvent"/>. Cancel this to prevent the emp event being raised.
/// </summary>
public sealed partial class EmpAttemptEvent : CancellableEntityEventArgs;
[ByRefEvent]
public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration);
[ByRefEvent]
public record struct EmpDisabledRemoved();

View File

@@ -11,7 +11,6 @@ using Content.Server.Emp;
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Medical;
using Content.Server.Polymorph.Components; using Content.Server.Polymorph.Components;
using Content.Server.Polymorph.Systems; using Content.Server.Polymorph.Systems;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
@@ -29,6 +28,7 @@ using Content.Shared.EntityEffects.Effects;
using Content.Shared.EntityEffects; using Content.Shared.EntityEffects;
using Content.Shared.Flash; using Content.Shared.Flash;
using Content.Shared.Maps; using Content.Shared.Maps;
using Content.Shared.Medical;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Random; using Content.Shared.Random;

View File

@@ -1,8 +1,8 @@
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Coordinates.Helpers; using Content.Shared.Coordinates.Helpers;
using Content.Server.Power.Components;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Power.Components;
using Content.Shared.Storage; using Content.Shared.Storage;
namespace Content.Server.Holosign; namespace Content.Server.Holosign;
@@ -12,7 +12,6 @@ public sealed class HolosignSystem : EntitySystem
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();

View File

@@ -8,6 +8,7 @@ using Content.Shared.Examine;
using Content.Shared.Light; using Content.Shared.Light;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Color = Robust.Shared.Maths.Color; using Color = Robust.Shared.Maths.Color;

View File

@@ -1,4 +1,3 @@
using Content.Server.Emp;
using Content.Server.Ghost; using Content.Server.Ghost;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
using Content.Shared.Light.EntitySystems; using Content.Shared.Light.EntitySystems;
@@ -16,8 +15,6 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem
SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo); SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args) private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
@@ -55,10 +52,4 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem
// need this to update visualizers // need this to update visualizers
UpdateLight(uid, light); UpdateLight(uid, light);
} }
private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
{
if (TryDestroyBulb(uid, component))
args.Affected = true;
}
} }

View File

@@ -2,7 +2,6 @@ using System.Linq;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Mech.Components; using Content.Server.Mech.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -14,6 +13,7 @@ using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems; using Content.Shared.Mech.EntitySystems;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power.Components;
using Content.Shared.Tools; using Content.Shared.Tools;
using Content.Shared.Tools.Components; using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems; using Content.Shared.Tools.Systems;

View File

@@ -1,8 +1,6 @@
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Emp;
using Content.Server.Medical.CrewMonitoring; using Content.Server.Medical.CrewMonitoring;
using Content.Shared.DeviceNetwork.Components; using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Medical.SuitSensors; using Content.Shared.Medical.SuitSensors;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -14,14 +12,6 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SuitSensorComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<SuitSensorComponent, EmpDisabledRemoved>(OnEmpFinished);
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -70,22 +60,4 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem
_deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device); _deviceNetworkSystem.QueuePacket(uid, sensor.ConnectedServer, payload, device: device);
} }
} }
private void OnEmpPulse(Entity<SuitSensorComponent> ent, ref EmpPulseEvent args)
{
args.Affected = true;
args.Disabled = true;
ent.Comp.PreviousMode = ent.Comp.Mode;
SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null);
ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked;
ent.Comp.ControlsLocked = true;
}
private void OnEmpFinished(Entity<SuitSensorComponent> ent, ref EmpDisabledRemoved args)
{
SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null);
ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked;
}
} }

View File

@@ -1,112 +0,0 @@
using Content.Server.Body.Systems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Forensics;
using Content.Server.Popups;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Robust.Server.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server.Medical
{
public sealed class VomitSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly MobStateSystem _mobstate = default!;
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly ThirstSystem _thirst = default!;
private static readonly ProtoId<SoundCollectionPrototype> VomitCollection = "Vomit";
private readonly SoundSpecifier _vomitSound = new SoundCollectionSpecifier(VomitCollection,
AudioParams.Default.WithVariation(0.2f).WithVolume(-4f));
/// <summary>
/// Make an entity vomit, if they have a stomach.
/// </summary>
public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = -40f, bool force = false)
{
// Main requirement: You have a stomach
var stomachList = _body.GetBodyOrganEntityComps<StomachComponent>(uid);
if (stomachList.Count == 0)
return;
// Vomit only if entity is alive
// Ignore condition if force was set to true
if (!force && _mobstate.IsDead(uid))
return;
// Vomiting makes you hungrier and thirstier
if (TryComp<HungerComponent>(uid, out var hunger))
_hunger.ModifyHunger(uid, hungerAdded, hunger);
if (TryComp<ThirstComponent>(uid, out var thirst))
_thirst.ModifyThirst(uid, thirst, thirstAdded);
// It fully empties the stomach, this amount from the chem stream is relatively small
var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
// Apply a bit of slowdown
_movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f);
// TODO: Need decals
var solution = new Solution();
// Empty the stomach out into it
foreach (var stomach in stomachList)
{
if (_solutionContainer.ResolveSolution(stomach.Owner, StomachSystem.DefaultSolutionName, ref stomach.Comp1.Solution, out var sol))
{
solution.AddSolution(sol, _proto);
sol.RemoveAllSolution();
_solutionContainer.UpdateChemicals(stomach.Comp1.Solution.Value);
}
}
// Adds a tiny amount of the chem stream from earlier along with vomit
if (TryComp<BloodstreamComponent>(uid, out var bloodStream))
{
const float chemMultiplier = 0.1f;
var vomitAmount = solutionSize;
// Takes 10% of the chemicals removed from the chem stream
if (_solutionContainer.ResolveSolution(uid, bloodStream.ChemicalSolutionName, ref bloodStream.ChemicalSolution))
{
var vomitChemstreamAmount = _solutionContainer.SplitSolution(bloodStream.ChemicalSolution.Value, vomitAmount);
vomitChemstreamAmount.ScaleSolution(chemMultiplier);
solution.AddSolution(vomitChemstreamAmount, _proto);
vomitAmount -= (float)vomitChemstreamAmount.Volume;
}
// Makes a vomit solution the size of 90% of the chemicals removed from the chemstream
solution.AddReagent(new ReagentId("Vomit", _bloodstream.GetEntityBloodData(uid)), vomitAmount); // TODO: Dehardcode vomit prototype
}
if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
{
_forensics.TransferDna(puddle, uid, false);
}
// Force sound to play as spill doesn't work if solution is empty.
_audio.PlayPvs(_vomitSound, uid);
_popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid);
}
}
}

View File

@@ -6,7 +6,7 @@ using Content.Shared.Interaction;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems; using Content.Shared.Ninja.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Robust.Shared.Audio; using Content.Shared.Power.Components;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
namespace Content.Server.Ninja.Systems; namespace Content.Server.Ninja.Systems;

View File

@@ -1,11 +1,11 @@
using Content.Server.Emp;
using Content.Server.Ninja.Events; using Content.Server.Ninja.Events;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.Emp;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems; using Content.Shared.Ninja.Systems;
using Content.Shared.Popups; using Content.Shared.Power.Components;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -16,7 +16,7 @@ namespace Content.Server.Ninja.Systems;
/// </summary> /// </summary>
public sealed class NinjaSuitSystem : SharedNinjaSuitSystem public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
{ {
[Dependency] private readonly EmpSystem _emp = default!; [Dependency] private readonly SharedEmpSystem _emp = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SpaceNinjaSystem _ninja = default!; [Dependency] private readonly SpaceNinjaSystem _ninja = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -30,7 +30,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt); SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt);
SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana); SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana);
SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp); SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp);
} }
@@ -44,7 +43,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
// raise event to let ninja components get starting battery // raise event to let ninja components get starting battery
_ninja.GetNinjaBattery(user.Owner, out var uid, out var _); _ninja.GetNinjaBattery(user.Owner, out var uid, out var _);
if (uid is not {} battery_uid) if (uid is not { } battery_uid)
return; return;
var ev = new NinjaBatteryChangedEvent(battery_uid, ent.Owner); var ev = new NinjaBatteryChangedEvent(battery_uid, ent.Owner);
@@ -96,17 +95,10 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
// if a cell is able to automatically recharge, boost the score drastically depending on the recharge rate, // if a cell is able to automatically recharge, boost the score drastically depending on the recharge rate,
// this is to ensure a ninja can still upgrade to a micro reactor cell even if they already have a medium or high. // this is to ensure a ninja can still upgrade to a micro reactor cell even if they already have a medium or high.
if (TryComp<BatterySelfRechargerComponent>(uid, out var selfcomp) && selfcomp.AutoRecharge) if (TryComp<BatterySelfRechargerComponent>(uid, out var selfcomp) && selfcomp.AutoRecharge)
return battcomp.MaxCharge + (selfcomp.AutoRechargeRate*AutoRechargeValue); return battcomp.MaxCharge + selfcomp.AutoRechargeRate * AutoRechargeValue;
return battcomp.MaxCharge; return battcomp.MaxCharge;
} }
private void OnEmpAttempt(EntityUid uid, NinjaSuitComponent comp, EmpAttemptEvent args)
{
// ninja suit (battery) is immune to emp
// powercell relays the event to suit
args.Cancel();
}
protected override void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user) protected override void UserUnequippedSuit(Entity<NinjaSuitComponent> ent, Entity<SpaceNinjaComponent> user)
{ {
base.UserUnequippedSuit(ent, user); base.UserUnequippedSuit(ent, user);
@@ -144,6 +136,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
Popup.PopupEntity(Loc.GetString(message), user, user); Popup.PopupEntity(Loc.GetString(message), user, user);
} }
// TODO: Move this to shared when power cells are predicted.
private void OnEmp(Entity<NinjaSuitComponent> ent, ref NinjaEmpEvent args) private void OnEmp(Entity<NinjaSuitComponent> ent, ref NinjaEmpEvent args)
{ {
var (uid, comp) = ent; var (uid, comp) = ent;
@@ -159,7 +152,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
if (CheckDisabled(ent, user)) if (CheckDisabled(ent, user))
return; return;
var coords = _transform.GetMapCoordinates(user); _emp.EmpPulse(Transform(user).Coordinates, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration, user);
_emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
} }
} }

View File

@@ -1,26 +1,20 @@
using Content.Server.Communications; using Content.Server.Communications;
using Content.Server.Chat.Managers;
using Content.Server.CriminalRecords.Systems; using Content.Server.CriminalRecords.Systems;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Objectives.Components; using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems; using Content.Server.Objectives.Systems;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Server.Research.Systems; using Content.Server.Research.Systems;
using Content.Server.Roles;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Doors.Components; using Content.Shared.Doors.Components;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems; using Content.Shared.Ninja.Systems;
using Content.Shared.Power.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Ninja.Systems; namespace Content.Server.Ninja.Systems;

View File

@@ -1,4 +1,5 @@
using Content.Server.Power.NodeGroups; using Content.Server.Power.NodeGroups;
using Content.Shared.Power.Components;
namespace Content.Server.Power.Components namespace Content.Server.Power.Components
{ {

View File

@@ -1,67 +0,0 @@
using Content.Server.Power.EntitySystems;
using Content.Shared.Guidebook;
namespace Content.Server.Power.Components
{
/// <summary>
/// Battery node on the pow3r network. Needs other components to connect to actual networks.
/// </summary>
[RegisterComponent]
[Virtual]
[Access(typeof(BatterySystem))]
public partial class BatteryComponent : Component
{
public string SolutionName = "battery";
/// <summary>
/// Maximum charge of the battery in joules (ie. watt seconds)
/// </summary>
[DataField]
[GuidebookData]
public float MaxCharge;
/// <summary>
/// Current charge of the battery in joules (ie. watt seconds)
/// </summary>
[DataField("startingCharge")]
public float CurrentCharge;
/// <summary>
/// The price per one joule. Default is 1 credit for 10kJ.
/// </summary>
[DataField]
public float PricePerJoule = 0.0001f;
}
/// <summary>
/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage).
/// </summary>
[ByRefEvent]
public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge);
/// <summary>
/// Raised when it is necessary to get information about battery charges.
/// </summary>
[ByRefEvent]
public sealed class GetChargeEvent : EntityEventArgs
{
public float CurrentCharge;
public float MaxCharge;
}
/// <summary>
/// Raised when it is necessary to change the current battery charge to a some value.
/// </summary>
[ByRefEvent]
public sealed class ChangeChargeEvent : EntityEventArgs
{
public float OriginalValue;
public float ResidualValue;
public ChangeChargeEvent(float value)
{
OriginalValue = value;
ResidualValue = value;
}
}
}

View File

@@ -1,37 +0,0 @@
using Content.Shared.Power;
using Content.Shared.Whitelist;
namespace Content.Server.Power.Components
{
[RegisterComponent]
public sealed partial class ChargerComponent : Component
{
[ViewVariables]
public CellChargerStatus Status;
/// <summary>
/// The charge rate of the charger, in watts
/// </summary>
[DataField("chargeRate")]
public float ChargeRate = 20.0f;
/// <summary>
/// The container ID that is holds the entities being charged.
/// </summary>
[DataField("slotId", required: true)]
public string SlotId = string.Empty;
/// <summary>
/// A whitelist for what entities can be charged by this Charger.
/// </summary>
[DataField("whitelist")]
public EntityWhitelist? Whitelist;
/// <summary>
/// Indicates whether the charger is portable and thus subject to EMP effects
/// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent.
/// </summary>
[DataField]
public bool Portable = false;
}
}

View File

@@ -1,11 +1,12 @@
using Content.Server.Emp;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.APC; using Content.Shared.APC;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Emp;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -203,6 +204,9 @@ public sealed class ApcSystem : EntitySystem
return ApcExternalPowerState.Good; return ApcExternalPowerState.Good;
} }
// TODO: This subscription should be in shared.
// But I am not moving ApcComponent to shared, this PR already got soaped enough and that component uses several layers of OOP.
// At least the EMP visuals won't mispredict, since all APCs also have the BatteryComponent, which also has a EMP effect and is in shared.
private void OnEmpPulse(EntityUid uid, ApcComponent component, ref EmpPulseEvent args) private void OnEmpPulse(EntityUid uid, ApcComponent component, ref EmpPulseEvent args)
{ {
if (component.MainBreakerEnabled) if (component.MainBreakerEnabled)

View File

@@ -2,6 +2,7 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;

View File

@@ -1,7 +1,9 @@
using Content.Server.Emp;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -10,7 +12,7 @@ using Robust.Shared.Timing;
namespace Content.Server.Power.EntitySystems namespace Content.Server.Power.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public sealed class BatterySystem : EntitySystem public sealed class BatterySystem : SharedBatterySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
@@ -22,7 +24,6 @@ namespace Content.Server.Power.EntitySystems
SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate); SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate); SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate);
SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice); SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
SubscribeLocalEvent<BatteryComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge); SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge);
SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge); SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge);
@@ -50,7 +51,7 @@ namespace Content.Server.Power.EntitySystems
if (effectiveMax == 0) if (effectiveMax == 0)
effectiveMax = 1; effectiveMax = 1;
var chargeFraction = batteryComponent.CurrentCharge / effectiveMax; var chargeFraction = batteryComponent.CurrentCharge / effectiveMax;
var chargePercentRounded = (int) (chargeFraction * 100); var chargePercentRounded = (int)(chargeFraction * 100);
args.PushMarkup( args.PushMarkup(
Loc.GetString( Loc.GetString(
"examinable-battery-component-examine-detail", "examinable-battery-component-examine-detail",
@@ -108,15 +109,6 @@ namespace Content.Server.Power.EntitySystems
{ {
args.Price += component.CurrentCharge * component.PricePerJoule; args.Price += component.CurrentCharge * component.PricePerJoule;
} }
private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args)
{
args.Affected = true;
UseCharge(uid, args.EnergyConsumption, component);
// Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP.
TrySetChargeCooldown(uid);
}
private void OnChangeCharge(Entity<BatteryComponent> entity, ref ChangeChargeEvent args) private void OnChangeCharge(Entity<BatteryComponent> entity, ref ChangeChargeEvent args)
{ {
if (args.ResidualValue == 0) if (args.ResidualValue == 0)
@@ -131,7 +123,7 @@ namespace Content.Server.Power.EntitySystems
args.MaxCharge += entity.Comp.MaxCharge; args.MaxCharge += entity.Comp.MaxCharge;
} }
public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) public override float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{ {
if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0) if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0)
return 0; return 0;
@@ -139,7 +131,7 @@ namespace Content.Server.Power.EntitySystems
return ChangeCharge(uid, -value, battery); return ChangeCharge(uid, -value, battery);
} }
public void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) public override void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{ {
if (!Resolve(uid, ref battery)) if (!Resolve(uid, ref battery))
return; return;
@@ -174,7 +166,7 @@ namespace Content.Server.Power.EntitySystems
/// <summary> /// <summary>
/// Changes the current battery charge by some value /// Changes the current battery charge by some value
/// </summary> /// </summary>
public float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null) public override float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{ {
if (!Resolve(uid, ref battery)) if (!Resolve(uid, ref battery))
return 0; return 0;
@@ -190,10 +182,7 @@ namespace Content.Server.Power.EntitySystems
return delta; return delta;
} }
/// <summary> public override void TrySetChargeCooldown(EntityUid uid, float value = -1)
/// Checks if the entity has a self recharge and puts it on cooldown if applicable.
/// </summary>
public void TrySetChargeCooldown(EntityUid uid, float value = -1)
{ {
if (!TryComp<BatterySelfRechargerComponent>(uid, out var batteryself)) if (!TryComp<BatterySelfRechargerComponent>(uid, out var batteryself))
return; return;
@@ -228,7 +217,7 @@ namespace Content.Server.Power.EntitySystems
/// <summary> /// <summary>
/// If sufficient charge is available on the battery, use it. Otherwise, don't. /// If sufficient charge is available on the battery, use it. Otherwise, don't.
/// </summary> /// </summary>
public bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null) public override bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{ {
if (!Resolve(uid, ref battery, false) || value > battery.CurrentCharge) if (!Resolve(uid, ref battery, false) || value > battery.CurrentCharge)
return false; return false;

View File

@@ -1,8 +1,9 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Emp;
using Content.Server.PowerCell;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Server.PowerCell;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Emp; using Content.Shared.Emp;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -15,7 +16,7 @@ using Content.Shared.Whitelist;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;
[UsedImplicitly] [UsedImplicitly]
internal sealed class ChargerSystem : EntitySystem public sealed class ChargerSystem : SharedChargerSystem
{ {
[Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -25,6 +26,8 @@ internal sealed class ChargerSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
SubscribeLocalEvent<ChargerComponent, ComponentStartup>(OnStartup); SubscribeLocalEvent<ChargerComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged); SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted); SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
@@ -32,8 +35,6 @@ internal sealed class ChargerSystem : EntitySystem
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt); SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt); SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine); SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args) private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args)
@@ -46,7 +47,7 @@ internal sealed class ChargerSystem : EntitySystem
using (args.PushGroup(nameof(ChargerComponent))) using (args.PushGroup(nameof(ChargerComponent)))
{ {
// rate at which the charger charges // rate at which the charger charges
args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate))); args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int)component.ChargeRate)));
// try to get contents of the charger // try to get contents of the charger
if (!_container.TryGetContainer(uid, component.SlotId, out var container)) if (!_container.TryGetContainer(uid, component.SlotId, out var container))
@@ -70,7 +71,7 @@ internal sealed class ChargerSystem : EntitySystem
continue; continue;
var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100; var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100;
args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int) chargePercentage))); args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int)chargePercentage)));
} }
} }
} }
@@ -194,12 +195,6 @@ internal sealed class ChargerSystem : EntitySystem
} }
} }
private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args)
{
args.Affected = true;
args.Disabled = true;
}
private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
{ {
if (!component.Portable) if (!component.Portable)

View File

@@ -1,6 +1,4 @@
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.Nodes; using Content.Server.Power.Nodes;
using Content.Server.Power.NodeGroups; using Content.Server.Power.NodeGroups;
@@ -9,6 +7,7 @@ using Content.Shared.GameTicking.Components;
using Content.Shared.Pinpointer; using Content.Shared.Pinpointer;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;

View File

@@ -4,6 +4,7 @@ using Content.Server.Kitchen.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Power.Components;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;

View File

@@ -1,6 +1,7 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Content.Shared.SMES; using Content.Shared.SMES;
using JetBrains.Annotations; using JetBrains.Annotations;

View File

@@ -1,7 +1,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Power.Components;
using Robust.Shared.Console; using Robust.Shared.Console;
namespace Content.Server.Power namespace Content.Server.Power

View File

@@ -1,4 +1,4 @@
using Content.Server.Power.Components; using Content.Shared.Power;
using Content.Shared.PowerCell; using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;

View File

@@ -1,17 +1,17 @@
using Content.Server.Emp; using System.Diagnostics.CodeAnalysis;
using Content.Server.Kitchen.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.PowerCell; using Content.Shared.PowerCell;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Content.Shared.UserInterface;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Kitchen.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.UserInterface;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Popups;
using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem;
namespace Content.Server.PowerCell; namespace Content.Server.PowerCell;
@@ -34,7 +34,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(OnChargeChanged); SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined); SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(OnCellEmpAttempt);
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged); SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged); SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
@@ -221,14 +220,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
OnBatteryExamined(uid, battery, args); OnBatteryExamined(uid, battery, args);
} }
private void OnCellEmpAttempt(EntityUid uid, PowerCellComponent component, EmpAttemptEvent args)
{
var parent = Transform(uid).ParentUid;
// relay the attempt event to the slot so it can cancel it
if (HasComp<PowerCellSlotComponent>(parent))
RaiseLocalEvent(parent, args);
}
private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args) private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args)
{ {
TryGetBatteryFromSlot(uid, out var battery); TryGetBatteryFromSlot(uid, out var battery);

View File

@@ -1,13 +1,13 @@
using Content.Server.Explosion.EntitySystems; using Content.Server.Chat.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Examine;
using Robust.Shared.Utility;
using Content.Server.Chat.Systems;
using Content.Server.Station.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Station.Systems;
using Content.Shared.Examine;
using Content.Shared.Power.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.PowerSink namespace Content.Server.PowerSink
{ {

View File

@@ -1,5 +1,4 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Emp;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Radio; using Content.Shared.Radio;
using Content.Shared.Radio.Components; using Content.Shared.Radio.Components;
@@ -22,8 +21,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged); SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak); SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
SubscribeLocalEvent<HeadsetComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args) private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
@@ -71,7 +68,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
protected override void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args) protected override void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args)
{ {
base.OnGotUnequipped(uid, component, args); base.OnGotUnequipped(uid, component, args);
component.IsEquipped = false;
RemComp<ActiveRadioComponent>(uid); RemComp<ActiveRadioComponent>(uid);
RemComp<WearingHeadsetComponent>(args.Equipee); RemComp<WearingHeadsetComponent>(args.Equipee);
} }
@@ -84,6 +80,9 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
if (component.Enabled == value) if (component.Enabled == value)
return; return;
component.Enabled = value;
Dirty(uid, component);
if (!value) if (!value)
{ {
RemCompDeferred<ActiveRadioComponent>(uid); RemCompDeferred<ActiveRadioComponent>(uid);
@@ -115,13 +114,4 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
if (TryComp(parent, out ActorComponent? actor)) if (TryComp(parent, out ActorComponent? actor))
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
} }
private void OnEmpPulse(EntityUid uid, HeadsetComponent component, ref EmpPulseEvent args)
{
if (component.Enabled)
{
args.Affected = true;
args.Disabled = true;
}
}
} }

View File

@@ -1,4 +1,5 @@
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Power.Components;
namespace Content.Server.SensorMonitoring; namespace Content.Server.SensorMonitoring;

View File

@@ -1,8 +1,8 @@
using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Systems;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events; using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power.Components;
namespace Content.Server.SensorMonitoring; namespace Content.Server.SensorMonitoring;

View File

@@ -19,6 +19,7 @@ using Content.Shared.DoAfter;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Content.Shared.Roles; using Content.Shared.Roles;

View File

@@ -7,6 +7,8 @@ using Content.Shared.Examine;
using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Stunnable; using Content.Shared.Stunnable;
namespace Content.Server.Stunnable.Systems namespace Content.Server.Stunnable.Systems

View File

@@ -1,6 +1,7 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Shared.Speech; using Content.Shared.Speech;
using Content.Shared.Speech.Components; using Content.Shared.Speech.Components;
using Content.Shared.SurveillanceCamera.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Player; using Robust.Shared.Player;
using static Content.Server.Chat.Systems.ChatSystem; using static Content.Server.Chat.Systems.ChatSystem;

View File

@@ -1,13 +1,12 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Emp;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events; using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.SurveillanceCamera; using Content.Shared.SurveillanceCamera;
using Content.Shared.Verbs; using Content.Shared.SurveillanceCamera.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -15,7 +14,7 @@ using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.SurveillanceCamera; namespace Content.Server.SurveillanceCamera;
public sealed class SurveillanceCameraSystem : EntitySystem public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -57,15 +56,13 @@ public sealed class SurveillanceCameraSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged); SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived); SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName); SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork); SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetNetwork>(OnSetNetwork);
SubscribeLocalEvent<SurveillanceCameraComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
SubscribeLocalEvent<SurveillanceCameraComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<SurveillanceCameraComponent, EmpDisabledRemoved>(OnEmpDisabledRemoved);
} }
private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args) private void OnPacketReceived(EntityUid uid, SurveillanceCameraComponent component, DeviceNetworkPacketEvent args)
@@ -131,26 +128,6 @@ public sealed class SurveillanceCameraSystem : EntitySystem
} }
} }
private void AddVerbs(EntityUid uid, SurveillanceCameraComponent component, GetVerbsEvent<AlternativeVerb> verbs)
{
if (!_actionBlocker.CanInteract(verbs.User, uid) || !_actionBlocker.CanComplexInteract(verbs.User))
{
return;
}
if (component.NameSet && component.NetworkSet)
{
return;
}
AlternativeVerb verb = new();
verb.Text = Loc.GetString("surveillance-camera-setup");
verb.Act = () => OpenSetupInterface(uid, verbs.User, component);
verbs.Verbs.Add(verb);
}
private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, ref PowerChangedEvent args) private void OnPowerChanged(EntityUid camera, SurveillanceCameraComponent component, ref PowerChangedEvent args)
{ {
SetActive(camera, args.Powered, component); SetActive(camera, args.Powered, component);
@@ -173,6 +150,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem
component.CameraId = args.Name; component.CameraId = args.Name;
component.NameSet = true; component.NameSet = true;
Dirty(uid, component);
UpdateSetupInterface(uid, component); UpdateSetupInterface(uid, component);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\""); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor)} set the name of {ToPrettyString(uid)} to \"{args.Name}.\"");
} }
@@ -198,10 +176,11 @@ public sealed class SurveillanceCameraSystem : EntitySystem
_deviceNetworkSystem.SetReceiveFrequency(uid, frequency.Frequency); _deviceNetworkSystem.SetReceiveFrequency(uid, frequency.Frequency);
component.NetworkSet = true; component.NetworkSet = true;
Dirty(uid, component);
UpdateSetupInterface(uid, component); UpdateSetupInterface(uid, component);
} }
private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) protected override void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null)
{ {
if (!Resolve(uid, ref camera)) if (!Resolve(uid, ref camera))
return; return;
@@ -271,7 +250,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem
UpdateVisuals(camera, component); UpdateVisuals(camera, component);
} }
public void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) public override void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null)
{ {
if (!Resolve(camera, ref component)) if (!Resolve(camera, ref component))
{ {
@@ -418,21 +397,6 @@ public sealed class SurveillanceCameraSystem : EntitySystem
_appearance.SetData(uid, SurveillanceCameraVisualsKey.Key, key, appearance); _appearance.SetData(uid, SurveillanceCameraVisualsKey.Key, key, appearance);
} }
private void OnEmpPulse(EntityUid uid, SurveillanceCameraComponent component, ref EmpPulseEvent args)
{
if (component.Active)
{
args.Affected = true;
args.Disabled = true;
SetActive(uid, false);
}
}
private void OnEmpDisabledRemoved(EntityUid uid, SurveillanceCameraComponent component, ref EmpDisabledRemoved args)
{
SetActive(uid, true);
}
} }
public sealed class OnSurveillanceCameraViewerAddEvent : EntityEventArgs public sealed class OnSurveillanceCameraViewerAddEvent : EntityEventArgs

View File

@@ -1,7 +1,7 @@
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Tesla.Components; using Content.Server.Tesla.Components;
using Content.Server.Lightning; using Content.Server.Lightning;
using Content.Shared.Power.Components;
namespace Content.Server.Tesla.EntitySystems; namespace Content.Server.Tesla.EntitySystems;

View File

@@ -1,9 +1,7 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Emp;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Vocalization.Systems; using Content.Server.Vocalization.Systems;
using Content.Shared.Cargo; using Content.Shared.Cargo;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -35,7 +33,6 @@ namespace Content.Server.VendingMachines
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak); SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged); SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice); SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<VendingMachineComponent, TryVocalizeEvent>(OnTryVocalize); SubscribeLocalEvent<VendingMachineComponent, TryVocalizeEvent>(OnTryVocalize);
SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt); SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
@@ -86,6 +83,7 @@ namespace Content.Server.VendingMachines
private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs) private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs)
{ {
vendComponent.Broken = true; vendComponent.Broken = true;
Dirty(uid, vendComponent);
TryUpdateVisualState((uid, vendComponent)); TryUpdateVisualState((uid, vendComponent));
} }
@@ -94,6 +92,7 @@ namespace Content.Server.VendingMachines
if (!args.DamageIncreased && component.Broken) if (!args.DamageIncreased && component.Broken)
{ {
component.Broken = false; component.Broken = false;
Dirty(uid, component);
TryUpdateVisualState((uid, component)); TryUpdateVisualState((uid, component));
return; return;
} }
@@ -257,16 +256,6 @@ namespace Content.Server.VendingMachines
args.Price += priceSets.Max(); args.Price += priceSets.Max();
} }
private void OnEmpPulse(EntityUid uid, VendingMachineComponent component, ref EmpPulseEvent args)
{
if (!component.Broken && this.IsPowered(uid, EntityManager))
{
args.Affected = true;
args.Disabled = true;
component.NextEmpEject = Timing.CurTime;
}
}
private void OnTryVocalize(Entity<VendingMachineComponent> ent, ref TryVocalizeEvent args) private void OnTryVocalize(Entity<VendingMachineComponent> ent, ref TryVocalizeEvent args)
{ {
args.Cancelled |= ent.Comp.Broken; args.Cancelled |= ent.Comp.Broken;

View File

@@ -1,7 +1,6 @@
using Content.Server.Power.Components;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Events; using Content.Shared.Damage.Events;
using Content.Shared.FixedPoint; using Content.Shared.Power;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Projectiles; using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged;

View File

@@ -22,5 +22,5 @@ public sealed partial class XAEEmpInAreaComponent : Component
/// Duration (in seconds) for which devices going to be disabled. /// Duration (in seconds) for which devices going to be disabled.
/// </summary> /// </summary>
[DataField] [DataField]
public float DisableDuration = 60f; public TimeSpan DisableDuration = TimeSpan.FromSeconds(60);
} }

View File

@@ -1,6 +1,6 @@
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Xenoarchaeology.Artifact.XAE.Components; using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
using Content.Shared.Power.Components;
using Content.Shared.Xenoarchaeology.Artifact; using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.Artifact.XAE; using Content.Shared.Xenoarchaeology.Artifact.XAE;

View File

@@ -61,5 +61,5 @@ public sealed partial class ElectricityAnomalyComponent : Component
/// Duration of devices being disabled by the emp pulse upon going supercritical. /// Duration of devices being disabled by the emp pulse upon going supercritical.
/// <summary> /// <summary>
[DataField, ViewVariables(VVAccess.ReadWrite)] [DataField, ViewVariables(VVAccess.ReadWrite)]
public float EmpDisabledDuration = 60f; public TimeSpan EmpDisabledDuration = TimeSpan.FromSeconds(60);
} }

View File

@@ -2,13 +2,16 @@ using System.Linq;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Contraband; using Content.Shared.Contraband;
using Content.Shared.Emp;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Lock; using Content.Shared.Lock;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Network;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -23,8 +26,11 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
[Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
[Dependency] protected readonly IGameTiming _timing = default!; [Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly LockSystem _lock = default!; [Dependency] private readonly LockSystem _lock = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
[Dependency] private readonly INetManager _net = default!;
private static readonly SlotFlags[] IgnoredSlots = private static readonly SlotFlags[] IgnoredSlots =
{ {
@@ -32,12 +38,12 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
SlotFlags.PREVENTEQUIP, SlotFlags.PREVENTEQUIP,
SlotFlags.NONE SlotFlags.NONE
}; };
private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray(); private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
private readonly Dictionary<SlotFlags, List<EntProtoId>> _data = new(); private readonly Dictionary<SlotFlags, List<EntProtoId>> _data = new();
public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new(); public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new();
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon"; private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
@@ -47,6 +53,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped); SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped); SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb); SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload); SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload);
PrepareAllVariants(); PrepareAllVariants();
@@ -97,21 +104,21 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
// clothing sprite logic // clothing sprite logic
if (TryComp(uid, out ClothingComponent? clothing) && if (TryComp(uid, out ClothingComponent? clothing) &&
proto.TryGetComponent("Clothing", out ClothingComponent? otherClothing)) proto.TryGetComponent(out ClothingComponent? otherClothing, Factory))
{ {
_clothingSystem.CopyVisuals(uid, otherClothing, clothing); _clothingSystem.CopyVisuals(uid, otherClothing, clothing);
} }
// appearance data logic // appearance data logic
if (TryComp(uid, out AppearanceComponent? appearance) && if (TryComp(uid, out AppearanceComponent? appearance) &&
proto.TryGetComponent("Appearance", out AppearanceComponent? appearanceOther)) proto.TryGetComponent(out AppearanceComponent? appearanceOther, Factory))
{ {
_appearance.AppendData(appearanceOther, uid); _appearance.AppendData(appearanceOther, uid);
Dirty(uid, appearance); Dirty(uid, appearance);
} }
// properly mark contraband // properly mark contraband
if (proto.TryGetComponent("Contraband", out ContrabandComponent? contra)) if (proto.TryGetComponent(out ContrabandComponent? contra, Factory))
{ {
EnsureComp<ContrabandComponent>(uid, out var current); EnsureComp<ContrabandComponent>(uid, out var current);
_contraband.CopyDetails(uid, contra, current); _contraband.CopyDetails(uid, contra, current);
@@ -138,6 +145,24 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
}); });
} }
private void OnEmpPulse(EntityUid uid, ChameleonClothingComponent component, ref EmpPulseEvent args)
{
if (!component.AffectedByEmp)
return;
if (component.EmpContinuous)
component.NextEmpChange = Timing.CurTime + TimeSpan.FromSeconds(1f / component.EmpChangeIntensity);
if (_net.IsServer) // needs RandomPredicted
{
var pick = GetRandomValidPrototype(component.Slot, component.RequireTag);
SetSelectedPrototype(uid, pick, component: component);
}
args.Affected = true;
args.Disabled = true;
}
protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { } protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { }
/// <summary> /// <summary>
@@ -157,7 +182,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
return false; return false;
// check if it's valid clothing // check if it's valid clothing
if (!proto.TryGetComponent("Clothing", out ClothingComponent? clothing)) if (!proto.TryGetComponent(out ClothingComponent? clothing, Factory))
return false; return false;
if (!clothing.Slots.HasFlag(chameleonSlot)) if (!clothing.Slots.HasFlag(chameleonSlot))
return false; return false;
@@ -187,6 +212,14 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
return validTargets; return validTargets;
} }
/// <summary>
/// Get a random prototype for a given slot.
/// </summary>
public string GetRandomValidPrototype(SlotFlags slot, string? tag = null)
{
return _random.Pick(GetValidTargets(slot, tag).ToList());
}
protected void PrepareAllVariants() protected void PrepareAllVariants()
{ {
_data.Clear(); _data.Clear();
@@ -215,4 +248,9 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
} }
} }
} }
// TODO: Predict and use component states for the UI
public virtual void SetSelectedPrototype(EntityUid uid, string? protoId, bool forceUpdate = false,
ChameleonClothingComponent? component = null)
{ }
} }

View File

@@ -5,24 +5,30 @@ namespace Content.Shared.Emp;
/// <summary> /// <summary>
/// While entity has this component it is "disabled" by EMP. /// While entity has this component it is "disabled" by EMP.
/// Add desired behaviour in other systems /// Add desired behaviour in other systems.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] [RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedEmpSystem))] [Access(typeof(SharedEmpSystem))]
public sealed partial class EmpDisabledComponent : Component public sealed partial class EmpDisabledComponent : Component
{ {
/// <summary> /// <summary>
/// Moment of time when component is removed and entity stops being "disabled" /// Moment of time when the component is removed and entity stops being "disabled".
/// </summary> /// </summary>
[DataField("timeLeft", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField] [AutoNetworkedField, AutoPausedField]
public TimeSpan DisabledUntil; public TimeSpan DisabledUntil = TimeSpan.Zero;
[DataField("effectCoolDown"), ViewVariables(VVAccess.ReadWrite)]
public float EffectCooldown = 3f;
/// <summary> /// <summary>
/// When next effect will be spawned /// Default time between visual effect spawns.
/// This gets a random multiplier.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan EffectCooldown = TimeSpan.FromSeconds(3);
/// <summary>
/// When next effect will be spawned.
/// TODO: Particle system.
/// </summary> /// </summary>
[AutoPausedField] [AutoPausedField]
public TimeSpan TargetTime = TimeSpan.Zero; public TimeSpan TargetTime = TimeSpan.Zero;

View File

@@ -1,21 +1,58 @@
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Rejuvenate;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Emp; namespace Content.Shared.Emp;
public abstract class SharedEmpSystem : EntitySystem public abstract class SharedEmpSystem : EntitySystem
{ {
[Dependency] protected readonly IGameTiming Timing = default!; [Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private HashSet<EntityUid> _entSet = new();
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<EmpDisabledComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EmpDisabledComponent, RejuvenateEvent>(OnRejuvenate);
} }
protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled"; public static readonly EntProtoId EmpPulseEffectPrototype = "EffectEmpPulse";
public static readonly EntProtoId EmpDisabledEffectPrototype = "EffectEmpDisabled";
public static readonly SoundSpecifier EmpSound = new SoundPathSpecifier("/Audio/Effects/Lightning/lightningbolt.ogg");
/// <summary>
/// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then by raising <see cref="EmpPulseEvent"/> on all entities in range.
/// </summary>
/// <param name="coordinates">The location to trigger the EMP pulse at.</param>
/// <param name="range">The range of the EMP pulse.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP pulse. In Joule.</param>
/// <param name="duration">The duration of the EMP effects.</param>
/// <param name="user">The player that caused the effect. Used for predicted audio.</param>
public void EmpPulse(MapCoordinates mapCoordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null)
{
foreach (var uid in _lookup.GetEntitiesInRange(mapCoordinates, range))
{
TryEmpEffects(uid, energyConsumption, duration, user);
}
// TODO: replace with PredictedSpawn once it works with animated sprites
if (_net.IsServer)
Spawn(EmpPulseEffectPrototype, mapCoordinates);
var coordinates = _transform.ToCoordinates(mapCoordinates);
_audio.PlayPredicted(EmpSound, coordinates, user);
}
/// <summary> /// <summary>
/// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range. /// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
@@ -24,12 +61,119 @@ public abstract class SharedEmpSystem : EntitySystem
/// <param name="range">The range of the EMP pulse.</param> /// <param name="range">The range of the EMP pulse.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param> /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
/// <param name="duration">The duration of the EMP effects.</param> /// <param name="duration">The duration of the EMP effects.</param>
public virtual void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration) /// <param name="user">The player that caused the effect. Used for predicted audio.</param>
public void EmpPulse(EntityCoordinates coordinates, float range, float energyConsumption, TimeSpan duration, EntityUid? user = null)
{ {
_entSet.Clear();
_lookup.GetEntitiesInRange(coordinates, range, _entSet);
foreach (var uid in _entSet)
{
TryEmpEffects(uid, energyConsumption, duration, user);
}
// TODO: replace with PredictedSpawn once it works with animated sprites
if (_net.IsServer)
Spawn(EmpPulseEffectPrototype, coordinates);
_audio.PlayPredicted(EmpSound, coordinates, user);
}
/// <summary>
/// Attempts to apply the effects of an EMP pulse onto an entity by first raising an <see cref="EmpAttemptEvent"/>, followed by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
/// <param name="user">The player that caused the EMP. For prediction purposes.</param>
/// <returns>If the entity was affected by the EMP.</returns>
public bool TryEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null)
{
var attemptEv = new EmpAttemptEvent();
RaiseLocalEvent(uid, ref attemptEv);
if (attemptEv.Cancelled)
return false;
return DoEmpEffects(uid, energyConsumption, duration, user);
}
/// <summary>
/// Applies the effects of an EMP pulse onto an entity by raising a <see cref="EmpPulseEvent"/> on it.
/// </summary>
/// <param name="uid">The entity to apply the EMP effects on.</param>
/// <param name="energyConsumption">The amount of energy consumed by the EMP.</param>
/// <param name="duration">The duration of the EMP effects.</param>
/// <param name="user">The player that caused the EMP. For prediction purposes.</param>
/// <returns>If the entity was affected by the EMP.</returns>
public bool DoEmpEffects(EntityUid uid, float energyConsumption, TimeSpan duration, EntityUid? user = null)
{
var ev = new EmpPulseEvent(energyConsumption, false, false, duration, user);
RaiseLocalEvent(uid, ref ev);
// TODO: replace with PredictedSpawn once it works with animated sprites
if (ev.Affected && _net.IsServer)
Spawn(EmpDisabledEffectPrototype, Transform(uid).Coordinates);
if (!ev.Disabled)
return ev.Affected;
var disabled = EnsureComp<EmpDisabledComponent>(uid);
disabled.DisabledUntil = Timing.CurTime + duration;
Dirty(uid, disabled);
return ev.Affected;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var curTime = Timing.CurTime;
var query = EntityQueryEnumerator<EmpDisabledComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (curTime < comp.DisabledUntil)
continue;
RemComp<EmpDisabledComponent>(uid);
}
} }
private void OnExamine(Entity<EmpDisabledComponent> ent, ref ExaminedEvent args) private void OnExamine(Entity<EmpDisabledComponent> ent, ref ExaminedEvent args)
{ {
args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine")); args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine"));
} }
private void OnRemove(Entity<EmpDisabledComponent> ent, ref ComponentRemove args)
{
var ev = new EmpDisabledRemovedEvent();
RaiseLocalEvent(ent, ref ev);
}
private void OnRejuvenate(Entity<EmpDisabledComponent> ent, ref RejuvenateEvent args)
{
RemCompDeferred<EmpDisabledComponent>(ent);
}
} }
/// <summary>
/// Raised on an entity before <see cref="EmpPulseEvent"/>. Cancel this to prevent the emp event being raised.
/// </summary>
[ByRefEvent]
public record struct EmpAttemptEvent(bool Cancelled);
/// <summary>
/// Raised on an entity when it gets hit by an EMP Pulse.
/// </summary>
/// <param name="EnergyConsumption">The amount of energy to remove from batteries. In Joule.</param>
/// <param name="Affected">Set this is true in the subscription to spawn a visual effect at the entity's location.</param>
/// <param name="Disabled">Set this to ture in the subscription to add <see cref="EmpDisabledComponent"/> to the entity.</param>
/// <param name="Duration">The duration the entity will be disabled.</param>
/// <param name="User">The player that caused the EMP. For prediction purposes.</param>
[ByRefEvent]
public record struct EmpPulseEvent(float EnergyConsumption, bool Affected, bool Disabled, TimeSpan Duration, EntityUid? User);
/// <summary>
/// Raised on an entity after <see cref="EmpDisabledComponent"/> is removed.
/// </summary>
[ByRefEvent]
public record struct EmpDisabledRemovedEvent();

View File

@@ -27,7 +27,7 @@ public sealed partial class EmpReactionEffect : EventEntityEffect<EmpReactionEff
/// Amount of time entities will be disabled /// Amount of time entities will be disabled
/// </summary> /// </summary>
[DataField("duration")] [DataField("duration")]
public float DisableDuration = 15; public TimeSpan DisableDuration = TimeSpan.FromSeconds(15);
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
=> Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability)); => Loc.GetString("reagent-effect-guidebook-emp-reaction-effect", ("chance", Probability));

View File

@@ -32,6 +32,9 @@ namespace Content.Shared.Input
public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu"; public static readonly BoundKeyFunction OpenInventoryMenu = "OpenInventoryMenu";
public static readonly BoundKeyFunction SmartEquipBackpack = "SmartEquipBackpack"; public static readonly BoundKeyFunction SmartEquipBackpack = "SmartEquipBackpack";
public static readonly BoundKeyFunction SmartEquipBelt = "SmartEquipBelt"; public static readonly BoundKeyFunction SmartEquipBelt = "SmartEquipBelt";
public static readonly BoundKeyFunction SmartEquipPocket1 = "SmartEquipPocket1";
public static readonly BoundKeyFunction SmartEquipPocket2 = "SmartEquipPocket2";
public static readonly BoundKeyFunction SmartEquipSuitStorage = "SmartEquipSuitStorage";
public static readonly BoundKeyFunction OpenBackpack = "OpenBackpack"; public static readonly BoundKeyFunction OpenBackpack = "OpenBackpack";
public static readonly BoundKeyFunction OpenBelt = "OpenBelt"; public static readonly BoundKeyFunction OpenBelt = "OpenBelt";
public static readonly BoundKeyFunction OpenAHelp = "OpenAHelp"; public static readonly BoundKeyFunction OpenAHelp = "OpenAHelp";

View File

@@ -35,6 +35,9 @@ public sealed class SmartEquipSystem : EntitySystem
CommandBinds.Builder CommandBinds.Builder
.Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack, handle: false, outsidePrediction: false)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt, handle: false, outsidePrediction: false)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SmartEquipPocket1, InputCmdHandler.FromDelegate(HandleSmartEquipPocket1, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SmartEquipPocket2, InputCmdHandler.FromDelegate(HandleSmartEquipPocket2, handle: false, outsidePrediction: false))
.Bind(ContentKeyFunctions.SmartEquipSuitStorage, InputCmdHandler.FromDelegate(HandleSmartEquipSuitStorage, handle: false, outsidePrediction: false))
.Register<SmartEquipSystem>(); .Register<SmartEquipSystem>();
} }
@@ -55,6 +58,21 @@ public sealed class SmartEquipSystem : EntitySystem
HandleSmartEquip(session, "belt"); HandleSmartEquip(session, "belt");
} }
private void HandleSmartEquipPocket1(ICommonSession? session)
{
HandleSmartEquip(session, "pocket1");
}
private void HandleSmartEquipPocket2(ICommonSession? session)
{
HandleSmartEquip(session, "pocket2");
}
private void HandleSmartEquipSuitStorage(ICommonSession? session)
{
HandleSmartEquip(session, "suitstorage");
}
private void HandleSmartEquip(ICommonSession? session, string equipmentSlot) private void HandleSmartEquip(ICommonSession? session, string equipmentSlot)
{ {
if (session is not { } playerSession) if (session is not { } playerSession)

View File

@@ -7,6 +7,7 @@ using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events; using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Emp;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Light.Components; using Content.Shared.Light.Components;
@@ -52,6 +53,7 @@ public abstract class SharedPoweredLightSystem : EntitySystem
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged); SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter); SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged); SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args) private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
@@ -230,29 +232,21 @@ public abstract class SharedPoweredLightSystem : EntitySystem
/// <summary> /// <summary>
/// Try to break bulb inside light fixture /// Try to break bulb inside light fixture
/// </summary> /// </summary>
public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null) public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null, EntityUid? user = null)
{ {
if (!Resolve(uid, ref light, false)) if (!Resolve(uid, ref light, false))
return false; return false;
// if we aren't mapinited,
// just null the spawned bulb
if (LifeStage(uid) < EntityLifeStage.MapInitialized)
{
light.HasLampOnSpawn = null;
return true;
}
// check bulb state // check bulb state
var bulbUid = GetBulb(uid, light); var bulbUid = GetBulb(uid, light);
if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb)) if (bulbUid == null || !TryComp<LightBulbComponent>(bulbUid.Value, out var lightBulb))
return false; return false;
if (lightBulb.State == LightBulbState.Broken) if (lightBulb.State == LightBulbState.Broken)
return false; return false;
// break it // break it
_bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb); _bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb);
_bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb); _bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb, user);
UpdateLight(uid, light); UpdateLight(uid, light);
return true; return true;
} }
@@ -327,13 +321,18 @@ public abstract class SharedPoweredLightSystem : EntitySystem
/// <summary> /// <summary>
/// Destroy the light bulb if the light took any damage. /// Destroy the light bulb if the light took any damage.
/// </summary> /// </summary>
/// <remarks>
/// TODO: This should be an IThresholdBehaviour once DestructibleSystem is predicted.
/// </remarks>
public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args) public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args)
{ {
if (GameTiming.ApplyingState) // The destruction is already networked on its own.
return;
// Was it being repaired, or did it take damage? // Was it being repaired, or did it take damage?
if (args.DamageIncreased) if (args.DamageIncreased)
{ {
// Eventually, this logic should all be done by this (or some other) system, not a component. TryDestroyBulb(uid, component, args.Origin);
TryDestroyBulb(uid, component);
} }
} }
@@ -348,6 +347,12 @@ public abstract class SharedPoweredLightSystem : EntitySystem
UpdateLight(uid, component); UpdateLight(uid, component);
} }
private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
{
if (TryDestroyBulb(uid, component))
args.Affected = true;
}
public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking) public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking)
{ {
if (light.IsBlinking == isNowBlinking) if (light.IsBlinking == isNowBlinking)

View File

@@ -5,6 +5,7 @@ using Content.Shared.Clothing;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Emp;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Interaction; using Content.Shared.Interaction;
@@ -53,6 +54,8 @@ public abstract class SharedSuitSensorSystem : EntitySystem
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn); SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
SubscribeLocalEvent<SuitSensorComponent, ClothingGotEquippedEvent>(OnEquipped); SubscribeLocalEvent<SuitSensorComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<SuitSensorComponent, ClothingGotUnequippedEvent>(OnUnequipped); SubscribeLocalEvent<SuitSensorComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<SuitSensorComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<SuitSensorComponent, EmpDisabledRemovedEvent>(OnEmpFinished);
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb); SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert); SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
@@ -137,6 +140,25 @@ public abstract class SharedSuitSensorSystem : EntitySystem
Dirty(ent); Dirty(ent);
} }
private void OnEmpPulse(Entity<SuitSensorComponent> ent, ref EmpPulseEvent args)
{
args.Affected = true;
args.Disabled = true;
ent.Comp.PreviousMode = ent.Comp.Mode;
SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null);
ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked;
ent.Comp.ControlsLocked = true;
// SetSensor already calls Dirty
}
private void OnEmpFinished(Entity<SuitSensorComponent> ent, ref EmpDisabledRemovedEvent args)
{
SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null);
ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked;
}
private void OnExamine(Entity<SuitSensorComponent> ent, ref ExaminedEvent args) private void OnExamine(Entity<SuitSensorComponent> ent, ref ExaminedEvent args)
{ {
if (!args.IsInDetailsRange) if (!args.IsInDetailsRange)

View File

@@ -85,13 +85,13 @@ public sealed partial class SuitSensorComponent : Component
/// <summary> /// <summary>
/// The previous mode of the suit. This is used to restore the state when an EMP effect ends. /// The previous mode of the suit. This is used to restore the state when an EMP effect ends.
/// </summary> /// </summary>
[DataField, ViewVariables] [DataField, AutoNetworkedField, ViewVariables]
public SuitSensorMode PreviousMode = SuitSensorMode.SensorOff; public SuitSensorMode PreviousMode = SuitSensorMode.SensorOff;
/// <summary> /// <summary>
/// The previous locked status of the controls. This is used to restore the state when an EMP effect ends. /// The previous locked status of the controls. This is used to restore the state when an EMP effect ends.
/// This keeps prisoner jumpsuits/internal implants from becoming unlocked after an EMP. /// This keeps prisoner jumpsuits/internal implants from becoming unlocked after an EMP.
/// </summary> /// </summary>
[DataField, ViewVariables] [DataField, AutoNetworkedField, ViewVariables]
public bool PreviousControlsLocked = false; public bool PreviousControlsLocked = false;
} }

View File

@@ -0,0 +1,140 @@
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Fluids;
using Content.Shared.Forensics.Systems;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Medical;
public sealed class VomitSystem : EntitySystem
{
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly HungerSystem _hunger = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
[Dependency] private readonly ThirstSystem _thirst = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedPuddleSystem _puddle = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BodyComponent, TryVomitEvent>(TryBodyVomitSolution);
}
private const float ChemMultiplier = 0.1f;
private static readonly ProtoId<SoundCollectionPrototype> VomitCollection = "Vomit";
private static readonly ProtoId<ReagentPrototype> VomitPrototype = "Vomit"; // TODO: Dehardcode vomit prototype
private readonly SoundSpecifier _vomitSound = new SoundCollectionSpecifier(VomitCollection,
AudioParams.Default.WithVariation(0.2f).WithVolume(-4f));
private void TryBodyVomitSolution(Entity<BodyComponent> ent, ref TryVomitEvent args)
{
if (args.Handled)
return;
// Main requirement: You have a stomach
var stomachList = _body.GetBodyOrganEntityComps<StomachComponent>((ent, null));
if (stomachList.Count == 0)
return;
// Empty the stomach out into it
foreach (var stomach in stomachList)
{
if (_solutionContainer.ResolveSolution(stomach.Owner, StomachSystem.DefaultSolutionName, ref stomach.Comp1.Solution, out var sol))
_solutionContainer.TryTransferSolution(stomach.Comp1.Solution.Value, args.Sol, sol.AvailableVolume);
}
args.Handled = true;
}
/// <summary>
/// Make an entity vomit, if they have a stomach.
/// </summary>
public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = -40f, bool force = false)
{
// Vomit only if entity is alive
// Ignore condition if force was set to true
if (!force && _mobState.IsDead(uid))
return;
// TODO: Need decals
var solution = new Solution();
var ev = new TryVomitEvent(solution, force);
RaiseLocalEvent(uid, ref ev);
if (!ev.Handled)
return;
// Vomiting makes you hungrier and thirstier
if (TryComp<HungerComponent>(uid, out var hunger))
_hunger.ModifyHunger(uid, hungerAdded, hunger);
if (TryComp<ThirstComponent>(uid, out var thirst))
_thirst.ModifyThirst(uid, thirst, thirstAdded);
// It fully empties the stomach, this amount from the chem stream is relatively small
var solutionSize = (MathF.Abs(thirstAdded) + MathF.Abs(hungerAdded)) / 6;
// Apply a bit of slowdown
_movementMod.TryUpdateMovementSpeedModDuration(uid, MovementModStatusSystem.VomitingSlowdown, TimeSpan.FromSeconds(solutionSize), 0.5f);
// Adds a tiny amount of the chem stream from earlier along with vomit
if (TryComp<BloodstreamComponent>(uid, out var bloodStream))
{
var vomitAmount = solutionSize;
// Takes 10% of the chemicals removed from the chem stream
if (_solutionContainer.ResolveSolution(uid, bloodStream.ChemicalSolutionName, ref bloodStream.ChemicalSolution))
{
var vomitChemstreamAmount = _solutionContainer.SplitSolution(bloodStream.ChemicalSolution.Value, vomitAmount);
vomitChemstreamAmount.ScaleSolution(ChemMultiplier);
solution.AddSolution(vomitChemstreamAmount, _proto);
vomitAmount -= (float)vomitChemstreamAmount.Volume;
}
// Makes a vomit solution the size of 90% of the chemicals removed from the chemstream
solution.AddReagent(new ReagentId(VomitPrototype, _bloodstream.GetEntityBloodData(uid)), vomitAmount);
}
if (_puddle.TrySpillAt(uid, solution, out var puddle, false))
{
_forensics.TransferDna(puddle, uid, false);
}
if (!_netManager.IsServer)
return;
// Force sound to play as spill doesn't work if solution is empty.
_audio.PlayPvs(_vomitSound, uid);
_popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid);
}
}
[ByRefEvent]
public record struct TryVomitEvent(Solution Sol, bool Forced = false, bool Handled = false);

View File

@@ -72,10 +72,10 @@ public sealed partial class NinjaSuitComponent : Component
public float EmpConsumption = 100000f; public float EmpConsumption = 100000f;
/// <summary> /// <summary>
/// How long the EMP effects last for, in seconds /// How long the EMP effects last for
/// </summary> /// </summary>
[DataField] [DataField]
public float EmpDuration = 60f; public TimeSpan EmpDuration = TimeSpan.FromSeconds(60);
} }
public sealed partial class RecallKatanaEvent : InstantActionEvent; public sealed partial class RecallKatanaEvent : InstantActionEvent;

View File

@@ -1,7 +1,7 @@
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems; using Content.Shared.Emp;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle; using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Item.ItemToggle.Components;
@@ -18,8 +18,8 @@ namespace Content.Shared.Ninja.Systems;
public abstract class SharedNinjaSuitSystem : EntitySystem public abstract class SharedNinjaSuitSystem : EntitySystem
{ {
[Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ItemToggleSystem _toggle = default!; [Dependency] private readonly ItemToggleSystem _toggle = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] protected readonly SharedPopupSystem Popup = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!;
[Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!; [Dependency] private readonly SharedSpaceNinjaSystem _ninja = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!;
@@ -36,6 +36,7 @@ public abstract class SharedNinjaSuitSystem : EntitySystem
SubscribeLocalEvent<NinjaSuitComponent, CreateItemAttemptEvent>(OnCreateStarAttempt); SubscribeLocalEvent<NinjaSuitComponent, CreateItemAttemptEvent>(OnCreateStarAttempt);
SubscribeLocalEvent<NinjaSuitComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt); SubscribeLocalEvent<NinjaSuitComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
SubscribeLocalEvent<NinjaSuitComponent, GotUnequippedEvent>(OnUnequipped); SubscribeLocalEvent<NinjaSuitComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
} }
private void OnEquipped(Entity<NinjaSuitComponent> ent, ref ClothingGotEquippedEvent args) private void OnEquipped(Entity<NinjaSuitComponent> ent, ref ClothingGotEquippedEvent args)
@@ -168,7 +169,14 @@ public abstract class SharedNinjaSuitSystem : EntitySystem
// mark the user as not wearing a suit // mark the user as not wearing a suit
_ninja.AssignSuit(user, null); _ninja.AssignSuit(user, null);
// disable glove abilities // disable glove abilities
if (user.Comp.Gloves is {} uid) if (user.Comp.Gloves is { } uid)
_toggle.TryDeactivate(uid, user: user); _toggle.TryDeactivate(uid, user: user);
} }
private void OnEmpAttempt(Entity<NinjaSuitComponent> ent, ref EmpAttemptEvent args)
{
// ninja suit (battery) is immune to emp
// powercell relays the event to suit
args.Cancelled = true;
}
} }

View File

@@ -0,0 +1,33 @@
namespace Content.Shared.Power;
/// <summary>
/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage).
/// </summary>
[ByRefEvent]
public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge);
/// <summary>
/// Raised when it is necessary to get information about battery charges.
/// </summary>
[ByRefEvent]
public sealed class GetChargeEvent : EntityEventArgs
{
public float CurrentCharge;
public float MaxCharge;
}
/// <summary>
/// Raised when it is necessary to change the current battery charge to a some value.
/// </summary>
[ByRefEvent]
public sealed class ChangeChargeEvent : EntityEventArgs
{
public float OriginalValue;
public float ResidualValue;
public ChangeChargeEvent(float value)
{
OriginalValue = value;
ResidualValue = value;
}
}

View File

@@ -6,7 +6,7 @@ namespace Content.Shared.Power.Components;
/// <summary> /// <summary>
/// Attached to APC powered entities that possess a rechargeable internal battery. /// Attached to APC powered entities that possess a rechargeable internal battery.
/// If external power is interrupted, the entity will draw power from this battery instead. /// If external power is interrupted, the entity will draw power from this battery instead.
/// Requires <see cref="Content.Server.Power.Components.ApcPowerReceiverComponent"/> and <see cref="Content.Server.Power.Components.BatteryComponent"/> to function. /// Requires <see cref="Content.Server.Power.Components.ApcPowerReceiverComponent"/> and <see cref="BatteryComponent"/> to function.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedPowerNetSystem), typeof(SharedPowerReceiverSystem))] [Access(typeof(SharedPowerNetSystem), typeof(SharedPowerReceiverSystem))]

View File

@@ -0,0 +1,34 @@
using Content.Shared.Power.EntitySystems;
using Content.Shared.Guidebook;
namespace Content.Shared.Power.Components;
/// <summary>
/// Battery node on the pow3r network. Needs other components to connect to actual networks.
/// </summary>
[RegisterComponent]
[Virtual]
[Access(typeof(SharedBatterySystem))]
public partial class BatteryComponent : Component
{
public string SolutionName = "battery";
/// <summary>
/// Maximum charge of the battery in joules (ie. watt seconds)
/// </summary>
[DataField]
[GuidebookData]
public float MaxCharge;
/// <summary>
/// Current charge of the battery in joules (ie. watt seconds)
/// </summary>
[DataField("startingCharge")]
public float CurrentCharge;
/// <summary>
/// The price per one joule. Default is 1 credit for 10kJ.
/// </summary>
[DataField]
public float PricePerJoule = 0.0001f;
}

View File

@@ -0,0 +1,36 @@
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Power.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class ChargerComponent : Component
{
[ViewVariables]
public CellChargerStatus Status;
/// <summary>
/// The charge rate of the charger, in watts
/// </summary>
[DataField]
public float ChargeRate = 20.0f;
/// <summary>
/// The container ID that is holds the entities being charged.
/// </summary>
[DataField(required: true)]
public string SlotId = string.Empty;
/// <summary>
/// A whitelist for what entities can be charged by this Charger.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
/// <summary>
/// Indicates whether the charger is portable and thus subject to EMP effects
/// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent.
/// </summary>
[DataField]
public bool Portable = false;
}

View File

@@ -0,0 +1,44 @@
using Content.Shared.Emp;
using Content.Shared.Power.Components;
namespace Content.Shared.Power.EntitySystems;
public abstract class SharedBatterySystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BatteryComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args)
{
args.Affected = true;
UseCharge(uid, args.EnergyConsumption, component);
// Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP.
TrySetChargeCooldown(uid);
}
public virtual float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
return 0f;
}
public virtual void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) { }
public virtual float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
return 0f;
}
/// <summary>
/// Checks if the entity has a self recharge and puts it on cooldown if applicable.
/// </summary>
public virtual void TrySetChargeCooldown(EntityUid uid, float value = -1) { }
public virtual bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
return false;
}
}

View File

@@ -0,0 +1,20 @@
using Content.Shared.Emp;
using Content.Shared.Power.Components;
namespace Content.Shared.Power.EntitySystems;
public abstract class SharedChargerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args)
{
args.Affected = true;
args.Disabled = true;
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.Emp;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -22,6 +23,8 @@ public abstract class SharedPowerCellSystem : EntitySystem
SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellInserted); SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellInserted);
SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellRemoved); SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellRemoved);
SubscribeLocalEvent<PowerCellSlotComponent, ContainerIsInsertingAttemptEvent>(OnCellInsertAttempt); SubscribeLocalEvent<PowerCellSlotComponent, ContainerIsInsertingAttemptEvent>(OnCellInsertAttempt);
SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(OnCellEmpAttempt);
} }
private void OnMapInit(Entity<PowerCellDrawComponent> ent, ref MapInitEvent args) private void OnMapInit(Entity<PowerCellDrawComponent> ent, ref MapInitEvent args)
@@ -71,6 +74,14 @@ public abstract class SharedPowerCellSystem : EntitySystem
RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false); RaiseLocalEvent(uid, new PowerCellChangedEvent(true), false);
} }
private void OnCellEmpAttempt(EntityUid uid, PowerCellComponent component, EmpAttemptEvent args)
{
var parent = Transform(uid).ParentUid;
// relay the attempt event to the slot so it can cancel it
if (HasComp<PowerCellSlotComponent>(parent))
RaiseLocalEvent(parent, ref args);
}
public void SetDrawEnabled(Entity<PowerCellDrawComponent?> ent, bool enabled) public void SetDrawEnabled(Entity<PowerCellDrawComponent?> ent, bool enabled)
{ {
if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled) if (!Resolve(ent, ref ent.Comp, false) || ent.Comp.Enabled == enabled)

View File

@@ -1,18 +1,20 @@
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Robust.Shared.GameStates;
namespace Content.Shared.Radio.Components; namespace Content.Shared.Radio.Components;
/// <summary> /// <summary>
/// This component relays radio messages to the parent entity's chat when equipped. /// This component relays radio messages to the parent entity's chat when equipped.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class HeadsetComponent : Component public sealed partial class HeadsetComponent : Component
{ {
[DataField("enabled")] [DataField, AutoNetworkedField]
public bool Enabled = true; public bool Enabled = true;
[DataField, AutoNetworkedField]
public bool IsEquipped = false; public bool IsEquipped = false;
[DataField("requiredSlot")] [DataField, AutoNetworkedField]
public SlotFlags RequiredSlot = SlotFlags.EARS; public SlotFlags RequiredSlot = SlotFlags.EARS;
} }

View File

@@ -1,3 +1,4 @@
using Content.Shared.Emp;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Radio.Components; using Content.Shared.Radio.Components;
@@ -9,9 +10,11 @@ public abstract class SharedHeadsetSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<HeadsetComponent, InventoryRelayedEvent<GetDefaultRadioChannelEvent>>(OnGetDefault); SubscribeLocalEvent<HeadsetComponent, InventoryRelayedEvent<GetDefaultRadioChannelEvent>>(OnGetDefault);
SubscribeLocalEvent<HeadsetComponent, GotEquippedEvent>(OnGotEquipped); SubscribeLocalEvent<HeadsetComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<HeadsetComponent, GotUnequippedEvent>(OnGotUnequipped); SubscribeLocalEvent<HeadsetComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<HeadsetComponent, EmpPulseEvent>(OnEmpPulse);
} }
private void OnGetDefault(EntityUid uid, HeadsetComponent component, InventoryRelayedEvent<GetDefaultRadioChannelEvent> args) private void OnGetDefault(EntityUid uid, HeadsetComponent component, InventoryRelayedEvent<GetDefaultRadioChannelEvent> args)
@@ -29,10 +32,21 @@ public abstract class SharedHeadsetSystem : EntitySystem
protected virtual void OnGotEquipped(EntityUid uid, HeadsetComponent component, GotEquippedEvent args) protected virtual void OnGotEquipped(EntityUid uid, HeadsetComponent component, GotEquippedEvent args)
{ {
component.IsEquipped = args.SlotFlags.HasFlag(component.RequiredSlot); component.IsEquipped = args.SlotFlags.HasFlag(component.RequiredSlot);
Dirty(uid, component);
} }
protected virtual void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args) protected virtual void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args)
{ {
component.IsEquipped = false; component.IsEquipped = false;
Dirty(uid, component);
}
private void OnEmpPulse(Entity<HeadsetComponent> ent, ref EmpPulseEvent args)
{
if (ent.Comp.Enabled)
{
args.Affected = true;
args.Disabled = true;
}
} }
} }

View File

@@ -1,10 +1,11 @@
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
namespace Content.Server.SurveillanceCamera; namespace Content.Shared.SurveillanceCamera.Components;
[RegisterComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SurveillanceCameraSystem))] [Access(typeof(SharedSurveillanceCameraSystem))]
public sealed partial class SurveillanceCameraComponent : Component public sealed partial class SurveillanceCameraComponent : Component
{ {
// List of active viewers. This is for bookkeeping purposes, // List of active viewers. This is for bookkeeping purposes,
@@ -24,23 +25,20 @@ public sealed partial class SurveillanceCameraComponent : Component
// If this camera is active or not. Deactivating a camera // If this camera is active or not. Deactivating a camera
// will not allow it to obtain any new viewers. // will not allow it to obtain any new viewers.
[ViewVariables] [DataField]
public bool Active { get; set; } = true; public bool Active = true;
// This one isn't easy to deal with. Will require a UI // This one isn't easy to deal with. Will require a UI
// to change/set this so mapping these in isn't // to change/set this so mapping these in isn't
// the most terrible thing possible. // the most terrible thing possible.
[ViewVariables(VVAccess.ReadWrite)]
[DataField("id")] [DataField("id")]
public string CameraId { get; set; } = "camera"; public string CameraId = "camera";
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
[DataField("nameSet")] public bool NameSet;
public bool NameSet { get; set; }
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
[DataField("networkSet")] public bool NetworkSet;
public bool NetworkSet { get; set; }
// This has to be device network frequency prototypes. // This has to be device network frequency prototypes.
[DataField("setupAvailableNetworks")] [DataField("setupAvailableNetworks")]

View File

@@ -1,8 +1,56 @@
using Robust.Shared.GameStates; using Content.Shared.Emp;
using Content.Shared.SurveillanceCamera.Components;
using Content.Shared.Verbs;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.SurveillanceCamera; namespace Content.Shared.SurveillanceCamera;
public abstract class SharedSurveillanceCameraSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<SurveillanceCameraComponent, GetVerbsEvent<AlternativeVerb>>(AddVerbs);
SubscribeLocalEvent<SurveillanceCameraComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<SurveillanceCameraComponent, EmpDisabledRemovedEvent>(OnEmpDisabledRemoved);
}
private void AddVerbs(EntityUid uid, SurveillanceCameraComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !args.CanComplexInteract)
return;
if (component.NameSet && component.NetworkSet)
return;
AlternativeVerb verb = new()
{
Text = Loc.GetString("surveillance-camera-setup"),
Act = () => OpenSetupInterface(uid, args.User, component)
};
args.Verbs.Add(verb);
}
private void OnEmpPulse(EntityUid uid, SurveillanceCameraComponent component, ref EmpPulseEvent args)
{
if (component.Active)
{
args.Affected = true;
args.Disabled = true;
SetActive(uid, false);
}
}
private void OnEmpDisabledRemoved(EntityUid uid, SurveillanceCameraComponent component, ref EmpDisabledRemovedEvent args)
{
SetActive(uid, true);
}
// TODO: predict the rest of the server side system
public virtual void SetActive(EntityUid camera, bool setting, SurveillanceCameraComponent? component = null) { }
protected virtual void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) { }
}
[Serializable, NetSerializable] [Serializable, NetSerializable]
public enum SurveillanceCameraVisualsKey : byte public enum SurveillanceCameraVisualsKey : byte
{ {

View File

@@ -75,6 +75,7 @@ public sealed partial class TimerTriggerComponent : Component
/// <summary> /// <summary>
/// The entity that activated this trigger. /// The entity that activated this trigger.
/// TODO: use WeakEntityReference once the engine PR is merged!
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public EntityUid? User; public EntityUid? User;

View File

@@ -6,7 +6,6 @@ namespace Content.Shared.Trigger.Systems;
public sealed class EmpOnTriggerSystem : EntitySystem public sealed class EmpOnTriggerSystem : EntitySystem
{ {
[Dependency] private readonly SharedEmpSystem _emp = default!; [Dependency] private readonly SharedEmpSystem _emp = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -25,7 +24,7 @@ public sealed class EmpOnTriggerSystem : EntitySystem
if (target == null) if (target == null)
return; return;
_emp.EmpPulse(_transform.GetMapCoordinates(target.Value), ent.Comp.Range, ent.Comp.EnergyConsumption, (float)ent.Comp.DisableDuration.TotalSeconds); _emp.EmpPulse(Transform(target.Value).Coordinates, ent.Comp.Range, ent.Comp.EnergyConsumption, ent.Comp.DisableDuration, args.User);
args.Handled = true; args.Handled = true;
} }
} }

View File

@@ -168,7 +168,8 @@ public sealed partial class TriggerSystem
if (timer.NextTrigger <= curTime) if (timer.NextTrigger <= curTime)
{ {
Trigger(uid, timer.User, timer.KeyOut); var user = TerminatingOrDeleted(timer.User) ? null : timer.User;
Trigger(uid, user, timer.KeyOut);
// Remove after triggering to prevent it from starting the timer again // Remove after triggering to prevent it from starting the timer again
RemComp<ActiveTimerTriggerComponent>(uid); RemComp<ActiveTimerTriggerComponent>(uid);
if (TryComp<AppearanceComponent>(uid, out var appearance)) if (TryComp<AppearanceComponent>(uid, out var appearance))

View File

@@ -107,6 +107,7 @@ public sealed partial class TriggerSystem : EntitySystem
ent.Comp.NextTrigger = curTime + ent.Comp.Delay; ent.Comp.NextTrigger = curTime + ent.Comp.Delay;
var delay = ent.Comp.InitialBeepDelay ?? ent.Comp.BeepInterval; var delay = ent.Comp.InitialBeepDelay ?? ent.Comp.BeepInterval;
ent.Comp.NextBeep = curTime + delay; ent.Comp.NextBeep = curTime + delay;
ent.Comp.User = user;
Dirty(ent); Dirty(ent);
var ev = new ActiveTimerTriggerEvent(user); var ev = new ActiveTimerTriggerEvent(user);

View File

@@ -1,19 +1,19 @@
using Content.Shared.Emag.Components;
using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Advertise.Components; using Content.Shared.Advertise.Components;
using Content.Shared.Advertise.Systems; using Content.Shared.Advertise.Systems;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Emp;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Network; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -41,6 +41,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
SubscribeLocalEvent<VendingMachineComponent, ComponentGetState>(OnVendingGetState); SubscribeLocalEvent<VendingMachineComponent, ComponentGetState>(OnVendingGetState);
SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<VendingMachineComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnRestockDoAfter); SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnRestockDoAfter);
SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract); SubscribeLocalEvent<VendingMachineRestockComponent, AfterInteractEvent>(OnAfterInteract);
@@ -83,6 +84,7 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
EjectEnd = component.EjectEnd, EjectEnd = component.EjectEnd,
DenyEnd = component.DenyEnd, DenyEnd = component.DenyEnd,
DispenseOnHitEnd = component.DispenseOnHitEnd, DispenseOnHitEnd = component.DispenseOnHitEnd,
Broken = component.Broken,
}; };
} }
@@ -145,6 +147,16 @@ public abstract partial class SharedVendingMachineSystem : EntitySystem
RestockInventoryFromPrototype(uid, component, component.InitialStockQuality); RestockInventoryFromPrototype(uid, component, component.InitialStockQuality);
} }
private void OnEmpPulse(Entity<VendingMachineComponent> ent, ref EmpPulseEvent args)
{
if (!ent.Comp.Broken && _receiver.IsPowered(ent.Owner))
{
args.Affected = true;
args.Disabled = true;
ent.Comp.NextEmpEject = Timing.CurTime;
}
}
protected virtual void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false) { } protected virtual void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false) { }
/// <summary> /// <summary>

View File

@@ -67,6 +67,7 @@ namespace Content.Shared.VendingMachines
public string? NextItemToEject; public string? NextItemToEject;
[DataField]
public bool Broken; public bool Broken;
/// <summary> /// <summary>
@@ -300,5 +301,7 @@ namespace Content.Shared.VendingMachines
public TimeSpan? DenyEnd; public TimeSpan? DenyEnd;
public TimeSpan? DispenseOnHitEnd; public TimeSpan? DispenseOnHitEnd;
public bool Broken;
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -167,6 +167,9 @@ ui-options-static-storage-ui = Lock storage window to hotbar
ui-options-function-smart-equip-backpack = Smart-equip to backpack ui-options-function-smart-equip-backpack = Smart-equip to backpack
ui-options-function-smart-equip-belt = Smart-equip to belt ui-options-function-smart-equip-belt = Smart-equip to belt
ui-options-function-smart-equip-suit-storage = Smart-equip to suit storage
ui-options-function-smart-equip-pocket1 = Smart-equip to pocket 1
ui-options-function-smart-equip-pocket2 = Smart-equip to pocket 2
ui-options-function-open-backpack = Open backpack ui-options-function-open-backpack = Open backpack
ui-options-function-open-belt = Open belt ui-options-function-open-belt = Open belt
ui-options-function-throw-item-in-hand = Throw item ui-options-function-throw-item-in-hand = Throw item

File diff suppressed because it is too large Load Diff

View File

@@ -288,6 +288,8 @@
tags: tags:
- CorgiWearable - CorgiWearable
- WhitelistChameleon - WhitelistChameleon
- type: AddAccentClothing
accent: BarkAccent
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase

View File

@@ -8,17 +8,14 @@
drawdepth: Effects drawdepth: Effects
noRot: true noRot: true
layers: layers:
- shader: unshaded - shader: unshaded
map: ["enum.EffectLayers.Unshaded"] map: ["enum.EffectLayers.Unshaded"]
sprite: Effects/emp.rsi sprite: Effects/emp.rsi
state: emp_pulse state: emp_pulse
- type: EffectVisuals - type: EffectVisuals
- type: Tag - type: Tag
tags: tags:
- HideContextMenu - HideContextMenu
- type: EmitSoundOnSpawn
sound:
path: /Audio/Effects/Lightning/lightningbolt.ogg
- type: AnimationPlayer - type: AnimationPlayer
- type: entity - type: entity
@@ -31,12 +28,12 @@
drawdepth: Effects drawdepth: Effects
noRot: true noRot: true
layers: layers:
- shader: unshaded - shader: unshaded
map: ["enum.EffectLayers.Unshaded"] map: ["enum.EffectLayers.Unshaded"]
sprite: Effects/emp.rsi sprite: Effects/emp.rsi
state: emp_disable state: emp_disable
- type: EffectVisuals - type: EffectVisuals
- type: Tag - type: Tag
tags: tags:
- HideContextMenu - HideContextMenu
- type: AnimationPlayer - type: AnimationPlayer

View File

@@ -29,8 +29,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 3 Piercing: 14
Heat: 16 Heat: 5
- type: entity - type: entity
id: BulletLightRifleUranium id: BulletLightRifleUranium

View File

@@ -29,8 +29,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 3 Piercing: 26
Heat: 32 Heat: 9
- type: entity - type: entity
id: BulletMagnumAP id: BulletMagnumAP

View File

@@ -29,8 +29,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 2 Piercing: 12
Heat: 14 Heat: 4
- type: entity - type: entity
id: BulletPistolUranium id: BulletPistolUranium

View File

@@ -29,8 +29,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 2 Piercing: 12
Heat: 15 Heat: 5
- type: entity - type: entity
id: BulletRifleUranium id: BulletRifleUranium

View File

@@ -68,8 +68,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 3 Piercing: 7
Heat: 7 Heat: 3
- type: IgnitionSource - type: IgnitionSource
ignited: true ignited: true

View File

@@ -142,7 +142,8 @@
- type: Projectile - type: Projectile
damage: damage:
types: types:
Blunt: 14 Piercing: 10
Heat: 4
- type: PointLight - type: PointLight
enabled: true enabled: true
color: "#ff4300" color: "#ff4300"

View File

@@ -104,7 +104,15 @@
description: A direction sign, pointing out which way evac is. description: A direction sign, pointing out which way evac is.
components: components:
- type: Sprite - type: Sprite
state: direction_evac layers:
- state: direction_evac
- state: direction_evac_glow
shader: unshaded
- state: direction_evac_glow
shader: shaded
#This is a neat trick I found to sort of "hack" an emissive map into ss14. Basically, the direction_evac_glow texture has an alpha channel.
#Alpha doesn't work for unshaded, but for *shaded* it does, and by putting a shaded texture infront of the unshaded, we can dim the unshaded texture, effectively allowing brightness control.
#I am re-using this from my high-vis vest PR, where I go further into detail, https://github.com/space-wizards/space-station-14/pull/37869
- type: entity - type: entity
parent: BaseSignDirectional parent: BaseSignDirectional
@@ -288,6 +296,15 @@
- type: Sprite - type: Sprite
state: armory state: armory
- type: entity
parent: BaseSign
id: SignArrivals
name: arrivals sign
description: A sign indicating where the Arrivals shuttle will dock.
components:
- type: Sprite
state: arrivals
- type: entity - type: entity
parent: BaseSign parent: BaseSign
id: SignToolStorage id: SignToolStorage

View File

@@ -5,6 +5,10 @@
- node: start - node: start
edges: edges:
- to: msstunprod - to: msstunprod
completed:
- !type:AdminLog
message: "Construction"
impact: High
steps: steps:
- material: MetalRod - material: MetalRod
amount: 1 amount: 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -5,7 +5,7 @@
"y": 32 "y": 32
}, },
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/discordia-space/CEV-Eris at commit 4e0bbe682d0a00192d24708fdb7031008aa03f18 and bee station at commit https://github.com/BeeStation/BeeStation-Hornet/commit/13dd5ac712385642574138f6d7b30eea7c2fab9c, Job signs by EmoGarbage404 (github) with inspiration from yogstation and tgstation, 'direction_exam' and 'direction_icu' made by rosieposieeee (github), 'direction_atmos' made by SlamBamActionman, 'vox' based on sprites taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/e7f005f8b8d3f7d89cbee3b87f76c23f9e951c27/icons/obj/decals.dmi, 'direction_pods' derived by WarPigeon from existing directional signs., 'detective' derived by Soupkilove from existing security sign", "copyright": "Taken from https://github.com/discordia-space/CEV-Eris at commit 4e0bbe682d0a00192d24708fdb7031008aa03f18 and bee station at commit https://github.com/BeeStation/BeeStation-Hornet/commit/13dd5ac712385642574138f6d7b30eea7c2fab9c, Job signs by EmoGarbage404 (github) with inspiration from yogstation and tgstation, 'direction_exam' and 'direction_icu' made by rosieposieeee (github), 'direction_atmos' made by SlamBamActionman, 'vox' based on sprites taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/e7f005f8b8d3f7d89cbee3b87f76c23f9e951c27/icons/obj/decals.dmi, 'direction_pods' derived by WarPigeon from existing directional signs., 'detective' derived by Soupkilove from existing security sign, direction_evac_glow made by moomoobeef based on existing direction_evac, 'arrivals' made by SlamBamActionman",
"states": [ "states": [
{ {
"name": "ai" "name": "ai"
@@ -28,6 +28,9 @@
{ {
"name": "armory" "name": "armory"
}, },
{
"name": "arrivals"
},
{ {
"name": "barbershop" "name": "barbershop"
}, },
@@ -165,6 +168,10 @@
"name": "direction_evac", "name": "direction_evac",
"directions": 4 "directions": 4
}, },
{
"name": "direction_evac_glow",
"directions": 4
},
{ {
"name": "direction_supply", "name": "direction_supply",
"directions": 4 "directions": 4

View File

@@ -255,6 +255,18 @@ binds:
type: State type: State
key: E key: E
mod1: Shift mod1: Shift
- function: SmartEquipPocket1
type: State
key: F
mod1: Shift
- function: SmartEquipPocket2
type: State
key: G
mod1: Shift
- function: SmartEquipSuitStorage
type: State
key: H
mod1: Shift
- function: OpenBackpack - function: OpenBackpack
type: State type: State
key: V key: V