merge mini-upstream

This commit is contained in:
Dmitry
2025-10-05 03:20:56 +07:00
333 changed files with 16020 additions and 9067 deletions

View File

@@ -0,0 +1,41 @@
name: Report an issue
description: For issues that are not related to any other issue form.
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue. This template is intended for any issues that do not fit other issue templates.
For feedback or help running Space Station 14, please join our [Discord](https://discord.gg/rGvu9hKffJ).
- type: input
attributes:
label: What version did the issue occur in?
description: You can find the version by opening the changelog in-game and looking at the bottom right corner of the changelog window.
placeholder: wizards/aa1337b
validations:
required: false
- type: textarea
attributes:
label: Description
description: |
A clear and concise description of what the issue is.
If the issue is visual in nature, consider posting a screenshot.
validations:
required: true
- type: textarea
attributes:
label: Reproduction
description: List the steps required to reproduce the issue.
placeholder: |
1.
2.
3.
...
validations:
required: true
- type: textarea
attributes:
label: Additional Context
description: Add any other additional context about the issue here.
validations:
required: false

View File

@@ -0,0 +1,48 @@
name: Report an mapping issue
description: For issues regarding mapping.
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue. This template is intended for any issues related to mapping.
For feedback or help running Space Station 14, please join our [Discord](https://discord.gg/rGvu9hKffJ).
- type: input
attributes:
label: What version did the issue occur in?
description: You can find the version by opening the changelog in-game and looking at the bottom right corner of the changelog window.
placeholder: wizards/aa1337b
validations:
required: false
- type: input
attributes:
label: On what station, shuttle, or grid did the issue occur on?
description: The name of the station, shuttle, or grid. If you do not know the name, try to describe it.
placeholder: Bagel
validations:
required: true
- type: textarea
attributes:
label: Description
description: |
A clear and concise description of what the issue is.
If the issue is visual in nature, consider posting a screenshot.
validations:
required: true
- type: textarea
attributes:
label: Reproduction
description: List the steps required to reproduce the issue.
placeholder: |
1.
2.
3.
...
validations:
required: true
- type: textarea
attributes:
label: Additional Context
description: Add any other additional context about the issue here.
validations:
required: false

View File

@@ -1,3 +1,4 @@
blank_issues_enabled: true
contact_links:
- name: Предложение
url: https://discord.station14.ru

View File

@@ -7,6 +7,18 @@ public sealed class EmpSystem : SharedEmpSystem
{
[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)
{
base.Update(frameTime);
@@ -16,7 +28,7 @@ public sealed class EmpSystem : SharedEmpSystem
{
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);
}
}

View File

@@ -79,18 +79,21 @@ namespace Content.Client.Entry
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!;
public override void Init()
public override void PreInit()
{
ClientContentIoC.Register();
ClientContentIoC.Register(Dependencies);
foreach (var callback in TestingCallbacks)
{
var cast = (ClientModuleTestingCallbacks) callback;
cast.ClientBeforeIoC?.Invoke();
}
}
IoCManager.BuildGraph();
IoCManager.InjectDependencies(this);
public override void Init()
{
Dependencies.BuildGraph();
Dependencies.InjectDependencies(this);
_contentLoc.Initialize();
_componentFactory.DoAutoRegistrations();

View File

@@ -40,7 +40,7 @@ public sealed class FaxBoundUi : BoundUserInterface
_dialogIsOpen = true;
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
await using var file = await _fileDialogManager.OpenFile(filters);
await using var file = await _fileDialogManager.OpenFile(filters, FileAccess.Read);
_dialogIsOpen = false;
if (_window == null || _window.Disposed || file == null)

View File

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

View File

@@ -129,7 +129,7 @@ namespace Content.Client.Instruments.UI
// or focus the previously-opened window.
_isMidiFileDialogueWindowOpen = true;
await using var file = await _dialogs.OpenFile(filters);
await using var file = await _dialogs.OpenFile(filters, FileAccess.Read);
_isMidiFileDialogueWindowOpen = false;

View File

@@ -24,6 +24,7 @@ using Content.Client.Lobby;
using Content.Client.Players.RateLimiting;
using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
using Content.Shared.IoC;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Players.RateLimiting;
@@ -31,10 +32,9 @@ namespace Content.Client.IoC
{
internal static class ClientContentIoC
{
public static void Register()
public static void Register(IDependencyCollection collection)
{
var collection = IoCManager.Instance!;
SharedContentIoC.Register(collection);
collection.Register<IParallaxManager, ParallaxManager>();
collection.Register<GeneratedParallaxCache>();
collection.Register<IChatManager, ChatManager>();

View File

@@ -1602,7 +1602,7 @@ namespace Content.Client.Lobby.UI
return;
StartExport();
await using var file = await _dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")));
await using var file = await _dialogManager.OpenFile(new FileDialogFilters(new FileDialogFilters.Group("yml")), FileAccess.Read);
if (file == null)
{

View File

@@ -190,6 +190,9 @@ namespace Content.Client.Options.UI.Tabs
AddHeader("ui-options-header-interaction-adv");
AddButton(ContentKeyFunctions.SmartEquipBackpack);
AddButton(ContentKeyFunctions.SmartEquipBelt);
AddButton(ContentKeyFunctions.SmartEquipPocket1);
AddButton(ContentKeyFunctions.SmartEquipPocket2);
AddButton(ContentKeyFunctions.SmartEquipSuitStorage);
AddButton(ContentKeyFunctions.OpenBackpack);
AddButton(ContentKeyFunctions.OpenBelt);
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.DenyEnd = state.DenyEnd;
component.DispenseOnHitEnd = state.DispenseOnHitEnd;
component.Broken = state.Broken;
// If all we did was update amounts then we can leave BUI buttons in place.
var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) ||

View File

@@ -27,6 +27,16 @@ public sealed partial class TestPair : RobustIntegrationTest.TestPair
protected override async Task Initialize()
{
await base.Initialize();
// Prevent info log spam in some tests (particularly SpawnAndDeleteAllEntitiesOnDifferentMaps)
Server.System<SharedMapSystem>().Log.Level = LogLevel.Warning;
Client.EntMan.EntitySysManager.SystemLoaded += (_, e) =>
{
if (e.System is SharedMapSystem map)
map.Log.Level = LogLevel.Warning;
};
var settings = (PoolSettings)Settings;
if (!settings.DummyTicker)
{

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

@@ -132,7 +132,7 @@ public sealed class EmbedTest : InteractionTest
"Target has unexpected EmbeddedObjects count.");
// Wait for the cooldown between throws
await RunSeconds(Hands.ThrowCooldown.Seconds);
await RunSeconds(Hands!.ThrowCooldown.Seconds);
// Throw the second projectile
await ThrowItem();

View File

@@ -169,6 +169,12 @@ public abstract partial class InteractionTest
/// <param name="enableToggleable">Whether or not to automatically enable any toggleable items</param>
protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableToggleable = true)
{
if (Hands == null)
{
Assert.Fail("No HandsComponent");
return default;
}
if (Hands.ActiveHandId == null)
{
Assert.Fail("No active hand");
@@ -210,6 +216,12 @@ public abstract partial class InteractionTest
{
entity ??= Target;
if (Hands == null)
{
Assert.Fail("No HandsComponent");
return;
}
if (Hands.ActiveHandId == null)
{
Assert.Fail("No active hand");
@@ -714,7 +726,7 @@ public abstract partial class InteractionTest
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)
@@ -730,6 +742,20 @@ public abstract partial class InteractionTest
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
#region Entity lookups
@@ -860,7 +886,7 @@ public abstract partial class InteractionTest
/// List of currently active DoAfters on the player.
/// </summary>
protected IEnumerable<Shared.DoAfter.DoAfter> ActiveDoAfters
=> DoAfters.DoAfters.Values.Where(x => !x.Cancelled && !x.Completed);
=> DoAfters?.DoAfters.Values.Where(x => !x.Cancelled && !x.Completed) ?? [];
#region Component
@@ -974,9 +1000,9 @@ public abstract partial class InteractionTest
/// <summary>
/// Sends a bui message using the given bui key.
/// </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;
await Client.WaitPost(() => bui.SendMessage(msg));
@@ -988,9 +1014,9 @@ public abstract partial class InteractionTest
/// <summary>
/// Sends a bui message using the given bui key.
/// </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;
await Client.WaitPost(() => bui.Close());
@@ -1412,15 +1438,25 @@ public abstract partial class InteractionTest
protected EntityUid? ToServer(NetEntity? nent) => SEntMan.GetEntity(nent);
protected EntityUid? ToClient(NetEntity? nent) => CEntMan.GetEntity(nent);
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? 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 ToClient(NetCoordinates coords) => CEntMan.GetCoordinates(coords);
protected EntityCoordinates? ToServer(NetCoordinates? coords) => SEntMan.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
#region Metadata & Transforms

View File

@@ -125,8 +125,8 @@ public abstract partial class InteractionTest
protected SharedUserInterfaceSystem CUiSys = default!;
// player components
protected HandsComponent Hands = default!;
protected DoAfterComponent DoAfters = default!;
protected HandsComponent? Hands;
protected DoAfterComponent? DoAfters;
public float TickPeriod => (float)STiming.TickPeriod.TotalSeconds;
@@ -222,8 +222,8 @@ public abstract partial class InteractionTest
SPlayer = SEntMan.SpawnEntity(PlayerPrototype, SEntMan.GetCoordinates(PlayerCoords));
Player = SEntMan.GetNetEntity(SPlayer);
Server.PlayerMan.SetAttachedEntity(ServerSession, SPlayer);
Hands = SEntMan.GetComponent<HandsComponent>(SPlayer);
DoAfters = SEntMan.GetComponent<DoAfterComponent>(SPlayer);
Hands = SEntMan.GetComponentOrNull<HandsComponent>(SPlayer);
DoAfters = SEntMan.GetComponentOrNull<DoAfterComponent>(SPlayer);
});
// Check player got attached.

View File

@@ -0,0 +1,154 @@
using Content.IntegrationTests.Tests.Movement;
using Content.Server.NPC.HTN;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mousetrap;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Mousetrap;
/// <summary>
/// Spawns a mouse and a mousetrap.
/// Makes the mouse cross the inactive mousetrap, then activates the trap and
/// makes the mouse try to cross back over it.
/// </summary>
/// <remarks>
/// Yep, every time the tests run, a virtual mouse dies. Sorry.
/// </remarks>
public sealed class MousetrapMouseMoveOverTest : MovementTest
{
private static readonly EntProtoId MousetrapProtoId = "Mousetrap";
private static readonly EntProtoId MouseProtoId = "MobMouse";
protected override string PlayerPrototype => MouseProtoId.Id; // use a mouse as the player entity
[Test]
public async Task MouseMoveOverTest()
{
// Make sure the mouse doesn't have any AI active
await Server.WaitPost(() => SEntMan.RemoveComponent<HTNComponent>(SPlayer));
// Spawn a mouse trap
await SpawnTarget(MousetrapProtoId);
Assert.That(Delta(), Is.GreaterThan(0.5), "Mouse and mousetrap not in expected positions.");
Assert.That(HasComp<MousetrapComponent>(),
$"{MousetrapProtoId} does not have a MousetrapComponent. If you're refactoring, please update this test!");
Assert.That(TryComp<ItemToggleComponent>(out var itemToggleComp),
$"{MousetrapProtoId} does not have a ItemToggleComponent. If you're refactoring, please update this test!");
Assert.That(itemToggleComp.Activated, Is.False, "Mousetrap started active.");
// The mouse is spawned by the test before the atmosphere is added, so it has some barotrauma damage already
// TODO: fix this since it can have an impact on integration tests
Assert.That(SEntMan.TryGetComponent<DamageableComponent>(SPlayer, out var damageComp),
$"Player does not have a DamageableComponent.");
var startingDamage = damageComp.TotalDamage;
Assert.That(SEntMan.TryGetComponent<MobStateComponent>(SPlayer, out var mouseMobStateComp),
$"{MouseProtoId} does not have a MobStateComponent.");
Assert.That(mouseMobStateComp.CurrentState, Is.EqualTo(MobState.Alive), "Mouse was not alive when spawned.");
// Move mouse over the trap
await Move(DirectionFlag.East, 1f);
Assert.That(Delta(), Is.LessThan(0.5), "Mouse did not move over mousetrap.");
// Walking over an inactive trap does not trigger it
Assert.That(damageComp.TotalDamage, Is.LessThanOrEqualTo(startingDamage), "Mouse took damage from inactive trap!");
Assert.That(itemToggleComp.Activated, Is.False, "Mousetrap was activated.");
// Activate the trap
var itemToggleSystem = Server.System<ItemToggleSystem>();
await Server.WaitAssertion(() =>
{
Assert.That(itemToggleSystem.TrySetActive(STarget.Value, true), "Could not activate the mouse trap.");
});
await Move(DirectionFlag.West, 1f);
Assert.That(Delta(), Is.LessThan(0.1), "Mouse moved past active mousetrap.");
// Walking over an active trap triggers it
Assert.That(damageComp.TotalDamage, Is.GreaterThan(startingDamage), "Mouse did not take damage from active trap!");
Assert.That(itemToggleComp.Activated, Is.False, "Mousetrap was not deactivated after triggering.");
Assert.That(mouseMobStateComp.CurrentState, Is.EqualTo(MobState.Dead), "Mouse was not killed by trap.");
}
}
/// <summary>
/// Spawns a mousetrap and makes the player walk over it without shoes.
/// Gives the player some shoes and makes them walk back over the trap.
/// </summary>
public sealed class MousetrapHumanMoveOverTest : MovementTest
{
private static readonly EntProtoId MousetrapProtoId = "Mousetrap";
private const string ShoesProtoId = "InteractionTestShoes";
[TestPrototypes]
private static readonly string TestPrototypes = $@"
- type: entity
parent: ClothingShoesBase
id: {ShoesProtoId}
components:
- type: Sprite
sprite: Clothing/Shoes/Boots/workboots.rsi
";
[Test]
public async Task HumanMoveOverTest()
{
await SpawnTarget(MousetrapProtoId);
Assert.That(Delta(), Is.GreaterThan(0.5), "Player and mousetrap not in expected positions.");
Assert.That(HasComp<MousetrapComponent>(),
$"{MousetrapProtoId} does not have a MousetrapComponent. If you're refactoring, please update this test!");
Assert.That(TryComp<ItemToggleComponent>(out var itemToggleComp),
$"{MousetrapProtoId} does not have a ItemToggleComponent. If you're refactoring, please update this test!");
// Activate the trap
var itemToggleSystem = Server.System<ItemToggleSystem>();
await Server.WaitAssertion(() =>
{
Assert.That(itemToggleSystem.TrySetActive(STarget.Value, true), "Could not activate the mouse trap.");
});
Assert.That(SEntMan.TryGetComponent<DamageableComponent>(SPlayer, out var damageComp),
$"Player does not have a DamageableComponent.");
var startingDamage = damageComp.TotalDamage;
// Move player over the trap
await Move(DirectionFlag.East, 0.5f);
Assert.That(Delta(), Is.LessThan(0.5), "Player did not move over mousetrap.");
// Walking over the trap without shoes activates it
Assert.That(damageComp.TotalDamage, Is.GreaterThan(startingDamage), "Player did not take damage.");
Assert.That(itemToggleComp.Activated, Is.False, "Mousetrap was not deactivated after triggering.");
// Reactivate the trap
await Server.WaitAssertion(() =>
{
Assert.That(itemToggleSystem.TrySetActive(STarget.Value, true), "Could not activate the mouse trap.");
});
var afterStepDamage = damageComp.TotalDamage;
// Give the player some shoes
await PlaceInHands(ShoesProtoId);
// Thanks to quick-equip, using the shoes will wear them
await UseInHand();
// Move back over the trap
await Move(DirectionFlag.West, 1f);
Assert.That(Delta(), Is.GreaterThan(0.5), "Player did not move back over mousetrap.");
// Walking over the trap with shoes on does not activate it
Assert.That(damageComp.TotalDamage, Is.LessThanOrEqualTo(afterStepDamage), "Player took damage from trap!");
Assert.That(itemToggleComp.Activated, "Mousetrap was deactivated despite the player being protected by shoes.");
}
}

View File

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

View File

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

View File

@@ -19,9 +19,6 @@ public static class ServerPackaging
new PlatformReg("osx-x64", "MacOS", true),
new PlatformReg("osx-arm64", "MacOS", true),
// Non-default platforms (i.e. for Watchdog Git)
new PlatformReg("win-x86", "Windows", false),
new PlatformReg("linux-x86", "Linux", false),
new PlatformReg("linux-arm", "Linux", false),
new PlatformReg("freebsd-x64", "FreeBSD", false),
};

View File

@@ -1,4 +1,3 @@
using System.Threading;
using Content.Server.Administration.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
@@ -6,8 +5,8 @@ using Content.Server.Body.Systems;
using Content.Server.Electrocution;
using Content.Server.Explosion.EntitySystems;
using Content.Server.GhostKick;
using Content.Server.Medical;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Physics.Components;
using Content.Server.Pointing.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
@@ -22,8 +21,8 @@ using Content.Shared.Administration.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
using Content.Shared.Clothing.Components;
using Content.Shared.Clumsy;
using Content.Shared.Cluwne;
using Content.Shared.Damage;
using Content.Shared.Damage.Systems;
@@ -32,6 +31,7 @@ using Content.Shared.Electrocution;
using Content.Shared.Gravity;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.Medical;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
@@ -54,7 +54,10 @@ using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Spawners;
using Robust.Shared.Utility;
using System.Numerics;
using System.Threading;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Administration.Systems;
@@ -63,7 +66,7 @@ public sealed partial class AdminVerbSystem
{
private readonly ProtoId<PolymorphPrototype> LizardSmite = "AdminLizardSmite";
private readonly ProtoId<PolymorphPrototype> VulpkaninSmite = "AdminVulpSmite";
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
@@ -143,7 +146,6 @@ public sealed partial class AdminVerbSystem
{
_sharedGodmodeSystem.EnableGodmode(args.Target); // So they don't suffocate.
EnsureComp<TabletopDraggableComponent>(args.Target);
RemComp<PhysicsComponent>(args.Target); // So they can be dragged around.
var xform = Transform(args.Target);
_popupSystem.PopupEntity(Loc.GetString("admin-smite-chess-self"), args.Target,
args.Target, PopupType.LargeCaution);
@@ -1006,5 +1008,63 @@ public sealed partial class AdminVerbSystem
Message = string.Join(": ", siliconName, Loc.GetString("admin-smite-silicon-laws-bound-description"))
};
args.Verbs.Add(silicon);
var homingRodName = Loc.GetString("admin-smite-homing-rod-name").ToLowerInvariant();
Verb homingRod = new()
{
Text = homingRodName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Security/target.rsi"), "target_s"),
Act = () =>
{
var speed = 25f; // It don't miss brother.
var distance = 350f;
HomingLaunchSequence(args.Target, "ImmovableRodKeepTiles", distance, speed); // todo: swap the proto for an EntityTable GetSpawns once rod rule rework
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", homingRodName, Loc.GetString("admin-smite-homing-rod-description"))
};
args.Verbs.Add(homingRod);
var homingRodSlowName = Loc.GetString("admin-smite-homing-rod-slow-name").ToLowerInvariant();
Verb homingRodSlow = new()
{
Text = homingRodSlowName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Security/target.rsi"), "target_c"),
Act = () =>
{
var speed = 5f; // slightly faster than default sprint speed 4.5
if (TryComp<MovementSpeedModifierComponent>(args.Target, out var movement))
speed = movement.CurrentSprintSpeed + 0.001f;// run
var distance = 200f; // its kinda slow so were just gonna cheat a bit.
HomingLaunchSequence(args.Target, "ImmovableRodKeepTiles", distance, speed);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", homingRodSlowName, Loc.GetString("admin-smite-homing-rod-slow-description"))
};
args.Verbs.Add(homingRodSlow);
}
public void HomingLaunchSequence(EntityUid target, EntProtoId proto, float distance, float speed)
{
// ToDo: Reuse some spawning code from whereever the rod rule ends up.
// I would do it now but theres a massive rod rewrite, and I don't wanna poke it for this.
// find reasonable spawn location (use gamerule and find rod?) but respect map not on grid etc etc
var offset = new Random(target.Id).NextAngle().RotateVec(new Vector2(distance, 0));
var spawnCoords = _transformSystem.GetMapCoordinates(target).Offset(offset);
var rod = Spawn(proto, spawnCoords);
// Here we abuse the ChasingWalkComp by making it skip targetting logic and dialling its frequency up
EnsureComp<ChasingWalkComponent>(rod, out var chasingComp);
chasingComp.NextChangeVectorTime = TimeSpan.MaxValue; // we just want it to never change
chasingComp.ChasingEntity = target;
chasingComp.ImpulseInterval = .1f; // skrrt skrrrrrrt skrrrt
chasingComp.RotateWithImpulse = true;
chasingComp.MaxSpeed = speed;
chasingComp.Speed = speed; // tell me lies, tell me sweet little lies.
if (TryComp<TimedDespawnComponent>(rod, out var despawn))
despawn.Lifetime = offset.Length() / speed * 3; // exists thrice as long as it takes to get to you.
}
}

View File

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

View File

@@ -126,6 +126,9 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
if (_random.Prob(anomaly.Comp.Continuity))
SetBehavior(anomaly, GetRandomBehavior());
}
var ev = new AnomalyAffectedByParticleEvent(anomaly, args.OtherEntity);
RaiseLocalEvent(anomaly, ref ev);
}
/// <summary>

View File

@@ -1,9 +1,9 @@
using Content.Server.Anomaly.Components;
using Content.Shared.Anomaly.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Random;
namespace Content.Server.Anomaly.Effects;
public sealed class ShuffleParticlesAnomalySystem : EntitySystem
{
[Dependency] private readonly AnomalySystem _anomaly = default!;
@@ -12,19 +12,16 @@ public sealed class ShuffleParticlesAnomalySystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, AnomalyAffectedByParticleEvent>(OnAffectedByParticle);
}
private void OnStartCollide(Entity<ShuffleParticlesAnomalyComponent> ent, ref StartCollideEvent args)
private void OnAffectedByParticle(Entity<ShuffleParticlesAnomalyComponent> ent, ref AnomalyAffectedByParticleEvent args)
{
if (!TryComp<AnomalyComponent>(ent, out var anomaly))
return;
if (!HasComp<AnomalousParticleComponent>(args.OtherEntity))
if (!TryComp<AnomalyComponent>(ent, out var anomalyComp))
return;
if (ent.Comp.ShuffleOnParticleHit && _random.Prob(ent.Comp.Prob))
_anomaly.ShuffleParticlesEffect((ent, anomaly));
_anomaly.ShuffleParticlesEffect((args.Anomaly, anomalyComp));
}
private void OnPulse(Entity<ShuffleParticlesAnomalyComponent> ent, ref AnomalyPulseEvent args)

View File

@@ -174,7 +174,10 @@ public sealed partial class AntagSelectionSystem
if (roles.Count == 0)
return false;
var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
if (!_pref.TryGetCachedPreferences(session.UserId, out var pref))
return false;
var character = (HumanoidCharacterProfile) pref.SelectedCharacter;
var valid = false;
@@ -183,8 +186,7 @@ public sealed partial class AntagSelectionSystem
{
var list = new List<ProtoId<AntagPrototype>>{role};
if (pref.AntagPreferences.Contains(role)
if (character.AntagPreferences.Contains(role)
&& !_ban.IsRoleBanned(session, list)
&& _playTime.IsAllowed(session, list))
valid = true;

View File

@@ -1,4 +1,5 @@
using Content.Shared.Arcade;
using Content.Shared.Dataset;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@@ -23,85 +24,73 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca
/// <summary>
/// The sound played when a new session of the SpaceVillain game is begun.
/// </summary>
[DataField("newGameSound")]
[DataField]
public SoundSpecifier NewGameSound = new SoundPathSpecifier("/Audio/Effects/Arcade/newgame.ogg");
/// <summary>
/// The sound played when the player chooses to attack.
/// </summary>
[DataField("playerAttackSound")]
[DataField]
public SoundSpecifier PlayerAttackSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_attack.ogg");
/// <summary>
/// The sound played when the player chooses to heal.
/// </summary>
[DataField("playerHealSound")]
[DataField]
public SoundSpecifier PlayerHealSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_heal.ogg");
/// <summary>
/// The sound played when the player chooses to regain mana.
/// </summary>
[DataField("playerChargeSound")]
[DataField]
public SoundSpecifier PlayerChargeSound = new SoundPathSpecifier("/Audio/Effects/Arcade/player_charge.ogg");
/// <summary>
/// The sound played when the player wins.
/// </summary>
[DataField("winSound")]
[DataField]
public SoundSpecifier WinSound = new SoundPathSpecifier("/Audio/Effects/Arcade/win.ogg");
/// <summary>
/// The sound played when the player loses.
/// </summary>
[DataField("gameOverSound")]
[DataField]
public SoundSpecifier GameOverSound = new SoundPathSpecifier("/Audio/Effects/Arcade/gameover.ogg");
/// <summary>
/// The prefixes that can be used to create the game name.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleFightVerbs")]
public List<string> PossibleFightVerbs = new()
{"Победи", "Аннигилируй", "Спаси", "Ударь", "Останови", "Уничтожь", "Заробасти", "Добейся", "Отымей", "Заовни"};
[DataField]
public ProtoId<LocalizedDatasetPrototype> PossibleFightVerbs = "SpaceVillainVerbsFight";
/// <summary>
/// The first names/titles that can be used to construct the name of the villain.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleFirstEnemyNames")]
public List<string> PossibleFirstEnemyNames = new(){
"Автоматический", "Фермер", "Лорд", "Профессор", "Кубинец", "Злой", "Грозный Король",
"Космический", "Лорд", "Могучий", "Герцог", "Генерал"
};
[DataField]
public ProtoId<LocalizedDatasetPrototype> PossibleFirstEnemyNames = "SpaceVillainNamesEnemyFirst";
/// <summary>
/// The last names that can be used to construct the name of the villain.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleLastEnemyNames")]
public List<string> PossibleLastEnemyNames = new()
{
"Мелоноид", "Киллертрон", "Волшебник", "Руина", "Джефф", "Эктоплазма", "Крушелон", "Ухангоид",
"Вакоид", "Петеоид", "слайм", "Грифер", "ЕРПшер", "Человек-ящерица", "Единорог"
};
[DataField]
public ProtoId<LocalizedDatasetPrototype> PossibleLastEnemyNames = "SpaceVillainNamesEnemyLast";
/// <summary>
/// The prototypes that can be dispensed as a reward for winning the game.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public List<EntProtoId> PossibleRewards = new();
/// <summary>
/// The minimum number of prizes the arcade machine can have.
/// </summary>
[DataField("rewardMinAmount")]
[DataField]
public int RewardMinAmount;
/// <summary>
/// The maximum number of prizes the arcade machine can have.
/// </summary>
[DataField("rewardMaxAmount")]
[DataField]
public int RewardMaxAmount;
/// <summary>

View File

@@ -4,15 +4,18 @@ using Content.Server.Advertise.EntitySystems;
using Content.Shared.Advertise.Components;
using Content.Shared.Arcade;
using Content.Shared.Power;
using Content.Shared.Random.Helpers;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Arcade.SpaceVillain;
public sealed partial class SpaceVillainArcadeSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
@@ -52,7 +55,7 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem
/// <returns>A fight-verb.</returns>
public string GenerateFightVerb(SpaceVillainArcadeComponent arcade)
{
return _random.Pick(arcade.PossibleFightVerbs);
return _random.Pick(_prototypeManager.Index(arcade.PossibleFightVerbs));
}
/// <summary>
@@ -61,7 +64,10 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem
/// <returns>An enemy-name.</returns>
public string GenerateEnemyName(SpaceVillainArcadeComponent arcade)
{
return $"{_random.Pick(arcade.PossibleFirstEnemyNames)} {_random.Pick(arcade.PossibleLastEnemyNames)}";
var possibleFirstEnemyNames = _prototypeManager.Index(arcade.PossibleFirstEnemyNames);
var possibleLastEnemyNames = _prototypeManager.Index(arcade.PossibleLastEnemyNames);
return $"{_random.Pick(possibleFirstEnemyNames)} {_random.Pick(possibleLastEnemyNames)}";
}
private void OnComponentInit(EntityUid uid, SpaceVillainArcadeComponent component, ComponentInit args)

View File

@@ -123,7 +123,7 @@ namespace Content.Server.Atmos.EntitySystems
public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
{
var query = GetEntityQuery<AirtightComponent>();
_explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
_explosionSystem.UpdateAirtightMap(grid, pos, grid);
_atmosphereSystem.InvalidateTile(grid.Owner, pos);
}

View File

@@ -1,14 +1,10 @@
using System.Linq;
using Content.Server.Emp;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Emp;
using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Clothing.Systems;
@@ -16,15 +12,12 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChameleonClothingComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChameleonClothingComponent, ChameleonPrototypeSelectedMessage>(OnSelected);
SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapInitEvent args)
@@ -37,21 +30,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
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)
{
if (!Resolve(uid, ref component))
@@ -64,7 +42,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
/// <summary>
/// Change chameleon items name, description and sprite to mimic other entity prototype.
/// </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)
{
if (!Resolve(uid, ref component, false))
@@ -88,14 +66,6 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
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)
{
base.Update(frameTime);
@@ -106,7 +76,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
if (!chameleon.EmpContinuous)
continue;
if (_timing.CurTime < chameleon.NextEmpChange)
if (Timing.CurTime < chameleon.NextEmpChange)
continue;
// randomly pick cloth element from available and apply it

View File

@@ -1,7 +1,7 @@
using Content.Server.Mech.Systems;
using Content.Server.Power.Components;
using Content.Shared.Construction;
using Content.Shared.Mech.Components;
using Content.Shared.Power.Components;
using JetBrains.Annotations;
using Robust.Server.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;

View File

@@ -361,14 +361,11 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
if (hasLinking && HasComp<DeviceListComponent>(target) || hasLinking == configurator.LinkModeActive)
return;
if (hasLinking)
{
SetMode(configuratorUid, configurator, userUid, true);
return;
}
if (HasComp<DeviceNetworkComponent>(target))
var hasNetworking = HasComp<DeviceNetworkComponent>(target);
if (hasNetworking)
SetMode(configuratorUid, configurator, userUid, false);
else if (hasLinking)
SetMode(configuratorUid, configurator, userUid, true);
}
#endregion

View File

@@ -2,16 +2,11 @@ using Content.Server.Power.EntitySystems;
using Content.Server.Radio;
using Content.Server.SurveillanceCamera;
using Content.Shared.Emp;
using Robust.Shared.Map;
namespace Content.Server.Emp;
public sealed class EmpSystem : SharedEmpSystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
public const string EmpPulseEffectPrototype = "EffectEmpPulse";
public override void Initialize()
{
base.Initialize();
@@ -22,84 +17,6 @@ public sealed class EmpSystem : SharedEmpSystem
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)
{
args.Cancelled = true;
@@ -120,14 +37,3 @@ public sealed class EmpSystem : SharedEmpSystem
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.Fluids.EntitySystems;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Medical;
using Content.Server.Polymorph.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Speech.Components;
@@ -29,6 +28,7 @@ using Content.Shared.EntityEffects.Effects;
using Content.Shared.EntityEffects;
using Content.Shared.Flash;
using Content.Shared.Maps;
using Content.Shared.Medical;
using Content.Shared.Mind.Components;
using Content.Shared.Popups;
using Content.Shared.Random;

View File

@@ -17,8 +17,6 @@ using Content.Server.Info;
using Content.Server.IoC;
using Content.Server.Maps;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Objectives;
using Content.Server.Players;
using Content.Server.Players.JobWhitelist;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Players.RateLimiting;
@@ -44,102 +42,113 @@ namespace Content.Server.Entry
internal const string ConfigPresetsDir = "/ConfigPresets/";
private const string ConfigPresetsDirBuild = $"{ConfigPresetsDir}Build/";
private EuiManager _euiManager = default!;
private IVoteManager _voteManager = default!;
private ServerUpdateManager _updateManager = default!;
private PlayTimeTrackingManager? _playTimeTracking;
private IEntitySystemManager? _sysMan;
private IServerDbManager? _dbManager;
private IWatchlistWebhookManager _watchlistWebhookManager = default!;
private IConnectionManager? _connectionManager;
[Dependency] private readonly CVarControlManager _cvarCtrl = default!;
[Dependency] private readonly ContentLocalizationManager _loc = default!;
[Dependency] private readonly ContentNetworkResourceManager _netResMan = default!;
[Dependency] private readonly DiscordChatLink _discordChatLink = default!;
[Dependency] private readonly DiscordLink _discordLink = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly GhostKickManager _ghostKick = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly IAfkManager _afk = default!;
[Dependency] private readonly IBanManager _ban = default!;
[Dependency] private readonly IChatManager _chatSan = default!;
[Dependency] private readonly IChatSanitizationManager _chat = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IConnectionManager _connection = default!;
[Dependency] private readonly IEntitySystemManager _entSys = default!;
[Dependency] private readonly IGameMapManager _gameMap = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly INodeGroupFactory _nodeFactory = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly IServerDbManager _dbManager = default!;
[Dependency] private readonly IServerPreferencesManager _preferences = default!;
[Dependency] private readonly IStatusHost _host = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly IWatchlistWebhookManager _watchlistWebhookManager = default!;
[Dependency] private readonly JobWhitelistManager _job = default!;
[Dependency] private readonly MultiServerKickManager _multiServerKick = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
[Dependency] private readonly PlayerRateLimitManager _rateLimit = default!;
[Dependency] private readonly RecipeManager _recipe = default!;
[Dependency] private readonly RulesManager _rules = default!;
[Dependency] private readonly ServerApi _serverApi = default!;
[Dependency] private readonly ServerInfoManager _serverInfo = default!;
[Dependency] private readonly ServerUpdateManager _updateManager = default!;
public override void PreInit()
{
ServerContentIoC.Register(Dependencies);
foreach (var callback in TestingCallbacks)
{
var cast = (ServerModuleTestingCallbacks)callback;
cast.ServerBeforeIoC?.Invoke();
}
}
/// <inheritdoc />
public override void Init()
{
base.Init();
Dependencies.BuildGraph();
Dependencies.InjectDependencies(this);
var cfg = IoCManager.Resolve<IConfigurationManager>();
var res = IoCManager.Resolve<IResourceManager>();
var logManager = IoCManager.Resolve<ILogManager>();
LoadConfigPresets(_cfg, _res, _log.GetSawmill("configpreset"));
LoadConfigPresets(cfg, res, logManager.GetSawmill("configpreset"));
var aczProvider = new ContentMagicAczProvider(Dependencies);
_host.SetMagicAczProvider(aczProvider);
var aczProvider = new ContentMagicAczProvider(IoCManager.Resolve<IDependencyCollection>());
IoCManager.Resolve<IStatusHost>().SetMagicAczProvider(aczProvider);
_factory.DoAutoRegistrations();
_factory.IgnoreMissingComponents("Visuals");
_factory.RegisterIgnore(IgnoredComponents.List);
_factory.GenerateNetIds();
var factory = IoCManager.Resolve<IComponentFactory>();
var prototypes = IoCManager.Resolve<IPrototypeManager>();
_proto.RegisterIgnore("parallax");
factory.DoAutoRegistrations();
factory.IgnoreMissingComponents("Visuals");
_loc.Initialize();
factory.RegisterIgnore(IgnoredComponents.List);
var dest = _cfg.GetCVar(CCVars.DestinationFile);
if (!string.IsNullOrEmpty(dest))
return; //hacky but it keeps load times for the generator down.
prototypes.RegisterIgnore("parallax");
_log.GetSawmill("Storage").Level = LogLevel.Info;
_log.GetSawmill("db.ef").Level = LogLevel.Info;
ServerContentIoC.Register();
foreach (var callback in TestingCallbacks)
{
var cast = (ServerModuleTestingCallbacks) callback;
cast.ServerBeforeIoC?.Invoke();
}
IoCManager.BuildGraph();
factory.GenerateNetIds();
var configManager = IoCManager.Resolve<IConfigurationManager>();
var dest = configManager.GetCVar(CCVars.DestinationFile);
IoCManager.Resolve<ContentLocalizationManager>().Initialize();
if (string.IsNullOrEmpty(dest)) //hacky but it keeps load times for the generator down.
{
_euiManager = IoCManager.Resolve<EuiManager>();
_voteManager = IoCManager.Resolve<IVoteManager>();
_updateManager = IoCManager.Resolve<ServerUpdateManager>();
_playTimeTracking = IoCManager.Resolve<PlayTimeTrackingManager>();
_connectionManager = IoCManager.Resolve<IConnectionManager>();
_sysMan = IoCManager.Resolve<IEntitySystemManager>();
_dbManager = IoCManager.Resolve<IServerDbManager>();
_watchlistWebhookManager = IoCManager.Resolve<IWatchlistWebhookManager>();
logManager.GetSawmill("Storage").Level = LogLevel.Info;
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
IoCManager.Resolve<IAdminLogManager>().Initialize();
IoCManager.Resolve<IConnectionManager>().Initialize();
_dbManager.Init();
IoCManager.Resolve<IServerPreferencesManager>().Init();
IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<ContentNetworkResourceManager>().Initialize();
IoCManager.Resolve<GhostKickManager>().Initialize();
_adminLog.Initialize();
_connection.Initialize();
_dbManager.Init();
_preferences.Init();
_nodeFactory.Initialize();
_netResMan.Initialize();
_ghostKick.Initialize();
_serverInfo.Initialize();
_serverApi.Initialize();
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();
_watchlistWebhookManager.Initialize();
_job.Initialize();
_rateLimit.Initialize();
IoCManager.Resolve<TTSManager>().Initialize(); // Corvax-TTS
IoCManager.Resolve<ServerInfoManager>().Initialize();
IoCManager.Resolve<ServerApi>().Initialize();
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();
_watchlistWebhookManager.Initialize();
IoCManager.Resolve<JobWhitelistManager>().Initialize();
IoCManager.Resolve<PlayerRateLimitManager>().Initialize();
}
}
public override void PostInit()
{
base.PostInit();
IoCManager.Resolve<IChatSanitizationManager>().Initialize();
IoCManager.Resolve<IChatManager>().Initialize();
var configManager = IoCManager.Resolve<IConfigurationManager>();
var resourceManager = IoCManager.Resolve<IResourceManager>();
var dest = configManager.GetCVar(CCVars.DestinationFile);
_chatSan.Initialize();
_chat.Initialize();
var dest = _cfg.GetCVar(CCVars.DestinationFile);
if (!string.IsNullOrEmpty(dest))
{
var resPath = new ResPath(dest).ToRootedPath();
var file = resourceManager.UserData.OpenWriteText(resPath.WithName("chem_" + dest));
var file = _res.UserData.OpenWriteText(resPath.WithName("chem_" + dest));
ChemistryJsonGenerator.PublishJson(file);
file.Flush();
file = resourceManager.UserData.OpenWriteText(resPath.WithName("react_" + dest));
file = _res.UserData.OpenWriteText(resPath.WithName("react_" + dest));
ReactionJsonGenerator.PublishJson(file);
file.Flush();
// Corvax-Wiki-Start
@@ -153,27 +162,23 @@ namespace Content.Server.Entry
HealthChangeReagentsJsonGenerator.PublishJson(file);
file.Flush();
// Corvax-Wiki-End
IoCManager.Resolve<IBaseServer>().Shutdown("Data generation done");
Dependencies.Resolve<IBaseServer>().Shutdown("Data generation done");
return;
}
else
{
IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<IAdminManager>().Initialize();
IoCManager.Resolve<IAfkManager>().Initialize();
IoCManager.Resolve<RulesManager>().Initialize();
IoCManager.Resolve<DiscordLink>().Initialize();
IoCManager.Resolve<DiscordChatLink>().Initialize();
_euiManager.Initialize();
IoCManager.Resolve<IGameMapManager>().Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
IoCManager.Resolve<IBanManager>().Initialize();
IoCManager.Resolve<IConnectionManager>().PostInit();
IoCManager.Resolve<MultiServerKickManager>().Initialize();
IoCManager.Resolve<CVarControlManager>().Initialize();
}
_recipe.Initialize();
_admin.Initialize();
_afk.Initialize();
_rules.Initialize();
_discordLink.Initialize();
_discordChatLink.Initialize();
_euiManager.Initialize();
_gameMap.Initialize();
_entSys.GetEntitySystem<GameTicker>().PostInitialize();
_ban.Initialize();
_connection.PostInit();
_multiServerKick.Initialize();
_cvarCtrl.Initialize();
}
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
@@ -191,21 +196,27 @@ namespace Content.Server.Entry
case ModUpdateLevel.FramePostEngine:
_updateManager.Update();
_playTimeTracking?.Update();
_playTimeTracking.Update();
_watchlistWebhookManager.Update();
_connectionManager?.Update();
_connection.Update();
break;
}
}
protected override void Dispose(bool disposing)
{
_playTimeTracking?.Shutdown();
_dbManager?.Shutdown();
IoCManager.Resolve<ServerApi>().Shutdown();
var dest = _cfg.GetCVar(CCVars.DestinationFile);
if (!string.IsNullOrEmpty(dest))
{
_playTimeTracking.Shutdown();
_dbManager.Shutdown();
}
IoCManager.Resolve<DiscordLink>().Shutdown();
IoCManager.Resolve<DiscordChatLink>().Shutdown();
_serverApi.Shutdown();
// TODO Should this be awaited?
_discordLink.Shutdown();
_discordChatLink.Shutdown();
}
private static void LoadConfigPresets(IConfigurationManager cfg, IResourceManager res, ISawmill sawmill)

View File

@@ -0,0 +1,14 @@
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.NodeContainer.NodeGroups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
namespace Content.Server.ExCable;
/// <summary>
/// Dummy Node group class for handling the explosive cables.
/// </summary>
[NodeGroup(NodeGroupID.ExCable)]
public sealed class ExCableNodeGroup : BaseNodeGroup
{
}

View File

@@ -11,8 +11,6 @@ namespace Content.Server.Explosion.EntitySystems;
public sealed partial class ExplosionSystem
{
[Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
private readonly Dictionary<string, int> _explosionTypes = new();
private void InitAirtightMap()
@@ -26,6 +24,8 @@ public sealed partial class ExplosionSystem
int index = 0;
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ExplosionPrototype>())
{
// TODO EXPLOSION
// just make this a field on the prototype
_explosionTypes.Add(prototype.ID, index);
index++;
}
@@ -42,10 +42,10 @@ public sealed partial class ExplosionSystem
// indices to this tile-data struct.
private Dictionary<EntityUid, Dictionary<Vector2i, TileData>> _airtightMap = new();
public void UpdateAirtightMap(EntityUid gridId, Vector2i tile, MapGridComponent? grid = null, EntityQuery<AirtightComponent>? query = null)
public void UpdateAirtightMap(EntityUid gridId, Vector2i tile, MapGridComponent? grid = null)
{
if (Resolve(gridId, ref grid, false))
UpdateAirtightMap(gridId, grid, tile, query);
UpdateAirtightMap(gridId, grid, tile);
}
/// <summary>
@@ -58,7 +58,7 @@ public sealed partial class ExplosionSystem
/// something like a normal and a reinforced windoor on the same tile. But given that this is a pretty rare
/// occurrence, I am fine with this.
/// </remarks>
public void UpdateAirtightMap(EntityUid gridId, MapGridComponent grid, Vector2i tile, EntityQuery<AirtightComponent>? query = null)
public void UpdateAirtightMap(EntityUid gridId, MapGridComponent grid, Vector2i tile)
{
var tolerance = new float[_explosionTypes.Count];
var blockedDirections = AtmosDirection.Invalid;
@@ -66,18 +66,15 @@ public sealed partial class ExplosionSystem
if (!_airtightMap.ContainsKey(gridId))
_airtightMap[gridId] = new();
query ??= GetEntityQuery<AirtightComponent>();
var damageQuery = GetEntityQuery<DamageableComponent>();
var destructibleQuery = GetEntityQuery<DestructibleComponent>();
var anchoredEnumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridId, grid, tile);
var anchoredEnumerator = _map.GetAnchoredEntitiesEnumerator(gridId, grid, tile);
while (anchoredEnumerator.MoveNext(out var uid))
{
if (!query.Value.TryGetComponent(uid, out var airtight) || !airtight.AirBlocked)
if (!_airtightQuery.TryGetComponent(uid, out var airtight) || !airtight.AirBlocked)
continue;
blockedDirections |= airtight.AirBlockedDirection;
var entityTolerances = GetExplosionTolerance(uid.Value, damageQuery, destructibleQuery);
var entityTolerances = GetExplosionTolerance(uid.Value);
for (var i = 0; i < tolerance.Length; i++)
{
tolerance[i] = Math.Max(tolerance[i], entityTolerances[i]);
@@ -105,28 +102,25 @@ public sealed partial class ExplosionSystem
if (!TryComp<MapGridComponent>(transform.GridUid, out var grid))
return;
UpdateAirtightMap(transform.GridUid.Value, grid, _mapSystem.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates));
UpdateAirtightMap(transform.GridUid.Value, grid, _map.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates));
}
/// <summary>
/// Return a dictionary that specifies how intense a given explosion type needs to be in order to destroy an entity.
/// </summary>
public float[] GetExplosionTolerance(
EntityUid uid,
EntityQuery<DamageableComponent> damageQuery,
EntityQuery<DestructibleComponent> destructibleQuery)
public float[] GetExplosionTolerance(EntityUid uid)
{
// How much total damage is needed to destroy this entity? This also includes "break" behaviors. This ASSUMES
// that this will result in a non-airtight entity.Entities that ONLY break via construction graph node changes
// are currently effectively "invincible" as far as this is concerned. This really should be done more rigorously.
var totalDamageTarget = FixedPoint2.MaxValue;
if (destructibleQuery.TryGetComponent(uid, out var destructible))
if (_destructibleQuery.TryGetComponent(uid, out var destructible))
{
totalDamageTarget = _destructibleSystem.DestroyedAt(uid, destructible);
}
var explosionTolerance = new float[_explosionTypes.Count];
if (totalDamageTarget == FixedPoint2.MaxValue || !damageQuery.TryGetComponent(uid, out var damageable))
if (totalDamageTarget == FixedPoint2.MaxValue || !_damageableQuery.TryGetComponent(uid, out var damageable))
{
for (var i = 0; i < explosionTolerance.Length; i++)
{
@@ -139,9 +133,12 @@ public sealed partial class ExplosionSystem
// does not support entities dynamically changing explosive resistances (e.g. via clothing). But these probably
// shouldn't be airtight structures anyways....
var mod = _damageableSystem.UniversalAllDamageModifier * _damageableSystem.UniversalExplosionDamageModifier;
foreach (var (id, index) in _explosionTypes)
{
if (!_prototypeManager.TryIndex<ExplosionPrototype>(id, out var explosionType))
// TODO EXPLOSION SYSTEM
// cache explosion type damage.
if (!_prototypeManager.Resolve(id, out ExplosionPrototype? explosionType))
continue;
// evaluate the damage that this damage type would do to this entity
@@ -151,10 +148,15 @@ public sealed partial class ExplosionSystem
if (!damageable.Damage.DamageDict.ContainsKey(type))
continue;
// TODO EXPLOSION SYSTEM
// add a variant of the event that gets raised once, instead of once per prototype.
// Or better yet, just calculate this manually w/o the event.
// The event mainly exists for indirect resistances via things like inventory & clothing
// But this shouldn't matter for airtight entities.
var ev = new GetExplosionResistanceEvent(explosionType.ID);
RaiseLocalEvent(uid, ref ev);
damagePerIntensity += value * Math.Max(0, ev.DamageCoefficient);
damagePerIntensity += value * mod * Math.Max(0, ev.DamageCoefficient);
}
explosionTolerance[index] = damagePerIntensity > 0
@@ -179,4 +181,16 @@ public sealed partial class ExplosionSystem
public float[] ExplosionTolerance;
public AtmosDirection BlockedDirections = AtmosDirection.Invalid;
}
public override void ReloadMap()
{
foreach (var(grid, dict) in _airtightMap)
{
var comp = Comp<MapGridComponent>(grid);
foreach (var index in dict.Keys)
{
UpdateAirtightMap(grid, comp, index);
}
}
}
}

View File

@@ -258,7 +258,7 @@ public sealed partial class ExplosionSystem
{
var neighbourIndex = change.GridIndices + NeighbourVectors[i];
if (_mapSystem.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
if (_map.TryGetTileRef(ev.Entity, grid, neighbourIndex, out var neighbourTile) && !neighbourTile.Tile.IsEmpty)
{
var oppositeDirection = (NeighborFlag)(1 << ((i + 4) % 8));
edges[neighbourIndex] = edges.GetValueOrDefault(neighbourIndex) | oppositeDirection;
@@ -307,7 +307,7 @@ public sealed partial class ExplosionSystem
spaceDirections = NeighborFlag.Invalid;
for (var i = 0; i < NeighbourVectors.Length; i++)
{
if (!_mapSystem.TryGetTileRef(grid, grid.Comp, index + NeighbourVectors[i], out var neighborTile) || neighborTile.Tile.IsEmpty)
if (!_map.TryGetTileRef(grid, grid.Comp, index + NeighbourVectors[i], out var neighborTile) || neighborTile.Tile.IsEmpty)
spaceDirections |= (NeighborFlag) (1 << i);
}

View File

@@ -27,8 +27,6 @@ namespace Content.Server.Explosion.EntitySystems;
public sealed partial class ExplosionSystem
{
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
/// <summary>
/// Used to limit explosion processing time. See <see cref="MaxProcessingTime"/>.
/// </summary>
@@ -446,7 +444,7 @@ public sealed partial class ExplosionSystem
GetEntitiesToDamage(uid, originalDamage, id);
foreach (var (entity, damage) in _toDamage)
{
if (damage.GetTotal() > 0 && TryComp<ActorComponent>(entity, out var actorComponent))
if (_actorQuery.HasComp(entity))
{
// Log damage to player entities only, cause this will create a massive amount of log spam otherwise.
if (cause != null)
@@ -461,7 +459,7 @@ public sealed partial class ExplosionSystem
}
// TODO EXPLOSIONS turn explosions into entities, and pass the the entity in as the damage origin.
_damageableSystem.TryChangeDamage(entity, damage * _damageableSystem.UniversalExplosionDamageModifier, ignoreResistances: true);
_damageableSystem.TryChangeDamage(entity, damage, ignoreResistances: true, ignoreGlobalModifiers: true);
}
}
@@ -668,6 +666,7 @@ sealed class Explosion
private readonly IEntityManager _entMan;
private readonly ExplosionSystem _system;
private readonly SharedMapSystem _mapSystem;
private readonly DamageableSystem _damageable;
public readonly EntityUid VisualEnt;
@@ -688,10 +687,10 @@ sealed class Explosion
int maxTileBreak,
bool canCreateVacuum,
IEntityManager entMan,
IMapManager mapMan,
EntityUid visualEnt,
EntityUid? cause,
SharedMapSystem mapSystem)
SharedMapSystem mapSystem,
DamageableSystem damageable)
{
VisualEnt = visualEnt;
Cause = cause;
@@ -706,6 +705,7 @@ sealed class Explosion
_maxTileBreak = maxTileBreak;
_canCreateVacuum = canCreateVacuum;
_entMan = entMan;
_damageable = damageable;
_xformQuery = entMan.GetEntityQuery<TransformComponent>();
_physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
@@ -760,8 +760,10 @@ sealed class Explosion
_expectedDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
}
#endif
_currentDamage = ExplosionType.DamagePerIntensity * _currentIntensity;
var modifier = _currentIntensity
* _damageable.UniversalExplosionDamageModifier
* _damageable.UniversalAllDamageModifier;
_currentDamage = ExplosionType.DamagePerIntensity * modifier;
// only throw if either the explosion is small, or if this is the outer ring of a large explosion.
var doThrow = Area < _system.ThrowLimit || CurrentIteration > _tileSetIntensity.Count - 6;

View File

@@ -52,7 +52,7 @@ public sealed partial class ExplosionSystem
// get the epicenter tile indices
if (_mapManager.TryFindGridAt(epicenter, out var gridUid, out var candidateGrid) &&
_mapSystem.TryGetTileRef(gridUid, candidateGrid, _mapSystem.WorldToTile(gridUid, candidateGrid, epicenter.Position), out var tileRef) &&
_map.TryGetTileRef(gridUid, candidateGrid, _map.WorldToTile(gridUid, candidateGrid, epicenter.Position), out var tileRef) &&
!tileRef.Tile.IsEmpty)
{
epicentreGrid = gridUid;
@@ -62,7 +62,7 @@ public sealed partial class ExplosionSystem
{
// reference grid defines coordinate system that the explosion in space will use
var gridComp = Comp<MapGridComponent>(referenceGrid.Value);
initialTile = _mapSystem.WorldToTile(referenceGrid.Value, gridComp, epicenter.Position);
initialTile = _map.WorldToTile(referenceGrid.Value, gridComp, epicenter.Position);
}
else
{

View File

@@ -2,6 +2,8 @@ using System.Linq;
using System.Numerics;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Destructible;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Atmos.Components;
@@ -16,7 +18,6 @@ using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.Projectiles;
using Content.Shared.Throwing;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Audio.Systems;
@@ -38,23 +39,28 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!;
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
[Dependency] private readonly SharedCameraRecoilSystem _recoilSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly PvsOverrideSystem _pvsSys = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly FlammableSystem _flammableSystem = default!;
[Dependency] private readonly DestructibleSystem _destructibleSystem = default!;
private EntityQuery<FlammableComponent> _flammableQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<ProjectileComponent> _projectileQuery;
private EntityQuery<ActorComponent> _actorQuery;
private EntityQuery<DestructibleComponent> _destructibleQuery;
private EntityQuery<DamageableComponent> _damageableQuery;
private EntityQuery<AirtightComponent> _airtightQuery;
/// <summary>
/// "Tile-size" for space when there are no nearby grids to use as a reference.
@@ -93,6 +99,10 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
_flammableQuery = GetEntityQuery<FlammableComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_projectileQuery = GetEntityQuery<ProjectileComponent>();
_actorQuery = GetEntityQuery<ActorComponent>();
_destructibleQuery = GetEntityQuery<DestructibleComponent>();
_damageableQuery = GetEntityQuery<DamageableComponent>();
_airtightQuery = GetEntityQuery<AirtightComponent>();
}
private void OnReset(RoundRestartCleanupEvent ev)
@@ -317,7 +327,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
private Explosion? SpawnExplosion(QueuedExplosion queued)
{
var pos = queued.Epicenter;
if (!_mapSystem.MapExists(pos.MapId))
if (!_map.MapExists(pos.MapId))
return null;
var results = GetExplosionTiles(pos, queued.Proto.ID, queued.TotalIntensity, queued.Slope, queued.MaxTileIntensity);
@@ -333,7 +343,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
//For whatever bloody reason, sound system requires ENTITY coordinates.
var mapEntityCoords = _transformSystem.ToCoordinates(_mapSystem.GetMap(pos.MapId), pos);
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
// play sound.
// for the normal audio, we want everyone in pvs range
@@ -376,10 +386,10 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
queued.MaxTileBreak,
queued.CanCreateVacuum,
EntityManager,
_mapManager,
visualEnt,
queued.Cause,
_map);
_map,
_damageableSystem);
}
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)

View File

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

View File

@@ -1,12 +1,6 @@
using Content.Shared.Interaction;
using Content.Shared.Storage;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Interaction
{
// TODO Remove Shared prefix
public sealed class InteractionSystem : SharedInteractionSystem;
}

View File

@@ -27,60 +27,60 @@ using Content.Server.Worldgen.Tools;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
using Content.Shared.IoC;
using Content.Shared.Kitchen;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Players.RateLimiting;
namespace Content.Server.IoC
{
internal static class ServerContentIoC
{
public static void Register()
{
IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<ISharedChatManager, ChatManager>();
IoCManager.Register<IChatSanitizationManager, ChatSanitizationManager>();
IoCManager.Register<IServerPreferencesManager, ServerPreferencesManager>();
IoCManager.Register<IServerDbManager, ServerDbManager>();
IoCManager.Register<RecipeManager, RecipeManager>();
IoCManager.Register<INodeGroupFactory, NodeGroupFactory>();
IoCManager.Register<IConnectionManager, ConnectionManager>();
IoCManager.Register<ServerUpdateManager>();
IoCManager.Register<IAdminManager, AdminManager>();
IoCManager.Register<ISharedAdminManager, AdminManager>();
IoCManager.Register<EuiManager, EuiManager>();
IoCManager.Register<IVoteManager, VoteManager>();
IoCManager.Register<IPlayerLocator, PlayerLocator>();
IoCManager.Register<IAfkManager, AfkManager>();
IoCManager.Register<IGameMapManager, GameMapManager>();
IoCManager.Register<RulesManager, RulesManager>();
IoCManager.Register<IBanManager, BanManager>();
IoCManager.Register<ContentNetworkResourceManager>();
IoCManager.Register<IAdminNotesManager, AdminNotesManager>();
IoCManager.Register<GhostKickManager>();
IoCManager.Register<ISharedAdminLogManager, AdminLogManager>();
IoCManager.Register<IAdminLogManager, AdminLogManager>();
IoCManager.Register<PlayTimeTrackingManager>();
IoCManager.Register<UserDbDataManager>();
IoCManager.Register<TTSManager>(); // Corvax-TTS
IoCManager.Register<ServerInfoManager>();
IoCManager.Register<PoissonDiskSampler>();
IoCManager.Register<DiscordWebhook>();
IoCManager.Register<VoteWebhooks>();
IoCManager.Register<ServerDbEntryManager>();
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
IoCManager.Register<ServerApi>();
IoCManager.Register<JobWhitelistManager>();
IoCManager.Register<PlayerRateLimitManager>();
IoCManager.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
IoCManager.Register<MappingManager>();
IoCManager.Register<IWatchlistWebhookManager, WatchlistWebhookManager>();
IoCManager.Register<ConnectionManager>();
IoCManager.Register<MultiServerKickManager>();
IoCManager.Register<CVarControlManager>();
namespace Content.Server.IoC;
IoCManager.Register<DiscordLink>();
IoCManager.Register<DiscordChatLink>();
}
internal static class ServerContentIoC
{
public static void Register(IDependencyCollection deps)
{
SharedContentIoC.Register(deps);
deps.Register<IChatManager, ChatManager>();
deps.Register<ISharedChatManager, ChatManager>();
deps.Register<IChatSanitizationManager, ChatSanitizationManager>();
deps.Register<IServerPreferencesManager, ServerPreferencesManager>();
deps.Register<IServerDbManager, ServerDbManager>();
deps.Register<RecipeManager, RecipeManager>();
deps.Register<INodeGroupFactory, NodeGroupFactory>();
deps.Register<IConnectionManager, ConnectionManager>();
deps.Register<ServerUpdateManager>();
deps.Register<IAdminManager, AdminManager>();
deps.Register<ISharedAdminManager, AdminManager>();
deps.Register<EuiManager, EuiManager>();
deps.Register<IVoteManager, VoteManager>();
deps.Register<IPlayerLocator, PlayerLocator>();
deps.Register<IAfkManager, AfkManager>();
deps.Register<IGameMapManager, GameMapManager>();
deps.Register<RulesManager, RulesManager>();
deps.Register<IBanManager, BanManager>();
deps.Register<ContentNetworkResourceManager>();
deps.Register<IAdminNotesManager, AdminNotesManager>();
deps.Register<GhostKickManager>();
deps.Register<ISharedAdminLogManager, AdminLogManager>();
deps.Register<IAdminLogManager, AdminLogManager>();
deps.Register<PlayTimeTrackingManager>();
deps.Register<UserDbDataManager>();
deps.Register<ServerInfoManager>();
deps.Register<PoissonDiskSampler>();
deps.Register<DiscordWebhook>();
deps.Register<VoteWebhooks>();
deps.Register<ServerDbEntryManager>();
deps.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
deps.Register<ServerApi>();
deps.Register<JobWhitelistManager>();
deps.Register<PlayerRateLimitManager>();
deps.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
deps.Register<MappingManager>();
deps.Register<IWatchlistWebhookManager, WatchlistWebhookManager>();
deps.Register<ConnectionManager>();
deps.Register<MultiServerKickManager>();
deps.Register<CVarControlManager>();
deps.Register<DiscordLink>();
deps.Register<DiscordChatLink>();
IoCManager.Register<TTSManager>(); // Corvax-TTS
}
}

View File

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

View File

@@ -1,4 +1,3 @@
using Content.Server.Emp;
using Content.Server.Ghost;
using Content.Shared.Light.Components;
using Content.Shared.Light.EntitySystems;
@@ -16,8 +15,6 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem
SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
@@ -55,10 +52,4 @@ public sealed class PoweredLightSystem : SharedPoweredLightSystem
// need this to update visualizers
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.Body.Systems;
using Content.Server.Mech.Components;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
@@ -14,6 +13,7 @@ using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Power.Components;
using Content.Shared.Tools;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;

View File

@@ -1,8 +1,6 @@
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Emp;
using Content.Server.Medical.CrewMonitoring;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Medical.SuitSensors;
using Robust.Shared.Timing;
@@ -14,14 +12,6 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = 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)
{
base.Update(frameTime);
@@ -70,22 +60,4 @@ public sealed class SuitSensorSystem : SharedSuitSensorSystem
_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.Systems;
using Content.Shared.Popups;
using Robust.Shared.Audio;
using Content.Shared.Power.Components;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Ninja.Systems;

View File

@@ -1,11 +1,11 @@
using Content.Server.Emp;
using Content.Server.Ninja.Events;
using Content.Server.Power.Components;
using Content.Server.PowerCell;
using Content.Shared.Emp;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems;
using Content.Shared.Popups;
using Content.Shared.Power.Components;
using Content.Shared.PowerCell.Components;
using Robust.Shared.Containers;
@@ -16,7 +16,7 @@ namespace Content.Server.Ninja.Systems;
/// </summary>
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 SpaceNinjaSystem _ninja = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -30,7 +30,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
base.Initialize();
SubscribeLocalEvent<NinjaSuitComponent, ContainerIsInsertingAttemptEvent>(OnSuitInsertAttempt);
SubscribeLocalEvent<NinjaSuitComponent, EmpAttemptEvent>(OnEmpAttempt);
SubscribeLocalEvent<NinjaSuitComponent, RecallKatanaEvent>(OnRecallKatana);
SubscribeLocalEvent<NinjaSuitComponent, NinjaEmpEvent>(OnEmp);
}
@@ -44,7 +43,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
// raise event to let ninja components get starting battery
_ninja.GetNinjaBattery(user.Owner, out var uid, out var _);
if (uid is not {} battery_uid)
if (uid is not { } battery_uid)
return;
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,
// 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)
return battcomp.MaxCharge + (selfcomp.AutoRechargeRate*AutoRechargeValue);
return battcomp.MaxCharge + selfcomp.AutoRechargeRate * AutoRechargeValue;
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)
{
base.UserUnequippedSuit(ent, user);
@@ -144,6 +136,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
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)
{
var (uid, comp) = ent;
@@ -159,7 +152,6 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
if (CheckDisabled(ent, user))
return;
var coords = _transform.GetMapCoordinates(user);
_emp.EmpPulse(coords, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration);
_emp.EmpPulse(Transform(user).Coordinates, comp.EmpRange, comp.EmpConsumption, comp.EmpDuration, user);
}
}

View File

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

View File

@@ -447,6 +447,7 @@ namespace Content.Server.NodeContainer.EntitySystems
NodeGroupID.Pipe => Color.Blue,
NodeGroupID.WireNet => Color.DarkMagenta,
NodeGroupID.Teg => Color.Red,
NodeGroupID.ExCable => Color.Pink,
_ => Color.White
};
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Administration.Systems;
using Content.Server.Physics.Controllers;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
@@ -8,7 +9,7 @@ namespace Content.Server.Physics.Components;
/// <summary>
/// A component which makes its entity chasing entity with selected component.
/// </summary>
[RegisterComponent, Access(typeof(ChasingWalkSystem)), AutoGenerateComponentPause]
[RegisterComponent, Access(typeof(ChasingWalkSystem), typeof(AdminVerbSystem)), AutoGenerateComponentPause]
public sealed partial class ChasingWalkComponent : Component
{
/// <summary>
@@ -78,4 +79,16 @@ public sealed partial class ChasingWalkComponent : Component
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public EntityUid? ChasingEntity;
/// <summary>
/// whether the entity should point in the direction its moving
/// </summary>
[DataField]
public bool RotateWithImpulse;
/// <summary>
/// Sprite rotation offset.
/// </summary>
[DataField]
public Angle RotationAngleOffset = Angle.Zero;
}

View File

@@ -101,5 +101,11 @@ public sealed class ChasingWalkSystem : VirtualController
_physics.SetLinearVelocity(uid, speed);
_physics.SetBodyStatus(uid, physics, BodyStatus.InAir); //If this is not done, from the explosion up close, the tesla will "Fall" to the ground, and almost stop moving.
if (component.RotateWithImpulse)
{
var ang = speed.ToAngle() + Angle.FromDegrees(90); // we want "Up" to be forward, bullet convention.
_transform.SetWorldRotation(uid, ang + component.RotationAngleOffset);
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Power.NodeGroups;
using Content.Shared.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,16 +1,35 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Content.Shared.Power;
using Content.Shared.Whitelist;
namespace Content.Server.Power.Components
{
[RegisterComponent]
public sealed partial class CablePlacerComponent : Component
{
/// <summary>
/// The structure prototype for the cable coil to place.
/// </summary>
[DataField("cablePrototypeID", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? CablePrototypeId = "CableHV";
/// <summary>
/// What kind of wire prevents placing this wire over it as CableType.
/// </summary>
[DataField("blockingWireType")]
public CableType BlockingCableType = CableType.HighVoltage;
/// <summary>
/// Blacklist for things the cable cannot be placed over. For things that arent cables with CableTypes.
/// </summary>
[DataField]
public EntityWhitelist Blacklist = new();
/// <summary>
/// Whether the placed cable should go over tiles or not.
/// </summary>
[DataField]
public bool OverTile;
}
}

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.Power.Components;
using Content.Server.Power.Pow3r;
using Content.Shared.Access.Systems;
using Content.Shared.APC;
using Content.Shared.Emag.Systems;
using Content.Shared.Emp;
using Content.Shared.Popups;
using Content.Shared.Power;
using Content.Shared.Rounding;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -203,6 +204,9 @@ public sealed class ApcSystem : EntitySystem
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)
{
if (component.MainBreakerEnabled)

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Maps;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
using Robust.Shared.Map.Components;
namespace Content.Server.Power.EntitySystems;
@@ -13,6 +14,7 @@ public sealed partial class CableSystem
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
private void InitializeCablePlacer()
{
@@ -35,12 +37,14 @@ public sealed partial class CableSystem
var snapPos = _map.TileIndicesFor((gridUid, grid), args.ClickLocation);
var tileDef = (ContentTileDefinition)_tileManager[_map.GetTileRef(gridUid, grid, snapPos).Tile.TypeId];
if (!tileDef.IsSubFloor || !tileDef.Sturdy)
if ((!component.OverTile && !tileDef.IsSubFloor) || !tileDef.Sturdy)
return;
foreach (var anchored in _map.GetAnchoredEntities((gridUid, grid), snapPos))
{
if (_whitelistSystem.IsBlacklistPass(component.Blacklist, anchored))
return;
if (TryComp<CableComponent>(anchored, out var wire) && wire.CableType == component.BlockingCableType)
return;
}

View File

@@ -1,8 +1,9 @@
using Content.Server.Power.Components;
using Content.Server.Emp;
using Content.Server.PowerCell;
using Content.Shared.Examine;
using Content.Server.PowerCell;
using Content.Shared.Power;
using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.PowerCell.Components;
using Content.Shared.Emp;
using JetBrains.Annotations;
@@ -15,7 +16,7 @@ using Content.Shared.Whitelist;
namespace Content.Server.Power.EntitySystems;
[UsedImplicitly]
internal sealed class ChargerSystem : EntitySystem
public sealed class ChargerSystem : SharedChargerSystem
{
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!;
@@ -25,6 +26,8 @@ internal sealed class ChargerSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChargerComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
@@ -32,8 +35,6 @@ internal sealed class ChargerSystem : EntitySystem
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args)
@@ -46,7 +47,7 @@ internal sealed class ChargerSystem : EntitySystem
using (args.PushGroup(nameof(ChargerComponent)))
{
// 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
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
@@ -70,7 +71,7 @@ internal sealed class ChargerSystem : EntitySystem
continue;
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)
{
if (!component.Portable)

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
using Content.Server.Administration;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration;
using Content.Shared.Power.Components;
using Robust.Shared.Console;
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.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.EntitySystems;
using Content.Shared.Containers.ItemSlots;
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.Components;
using Content.Shared.Rounding;
using Content.Shared.UserInterface;
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;
@@ -34,7 +34,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(OnCellEmpAttempt);
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);
@@ -221,14 +220,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
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)
{
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.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.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
{

View File

@@ -1,5 +1,4 @@
using Content.Server.Chat.Systems;
using Content.Server.Emp;
using Content.Shared.Inventory.Events;
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
@@ -21,8 +20,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
SubscribeLocalEvent<HeadsetComponent, EmpPulseEvent>(OnEmpPulse);
}
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
@@ -69,7 +66,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
protected override void OnGotUnequipped(EntityUid uid, HeadsetComponent component, GotUnequippedEvent args)
{
base.OnGotUnequipped(uid, component, args);
component.IsEquipped = false;
RemComp<ActiveRadioComponent>(uid);
RemComp<WearingHeadsetComponent>(args.Equipee);
}
@@ -82,6 +78,9 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
if (component.Enabled == value)
return;
component.Enabled = value;
Dirty(uid, component);
if (!value)
{
RemCompDeferred<ActiveRadioComponent>(uid);
@@ -113,13 +112,4 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
if (TryComp(parent, out ActorComponent? actor))
_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.Shared.Power.Components;
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.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power.Components;
namespace Content.Server.SensorMonitoring;

View File

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

View File

@@ -1,19 +1,11 @@
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Explosion;
using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Lock;
using Content.Shared.Storage;
using Content.Shared.Storage.Components;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Storage.EntitySystems;
public sealed partial class StorageSystem : SharedStorageSystem
@@ -24,7 +16,6 @@ public sealed partial class StorageSystem : SharedStorageSystem
{
base.Initialize();
SubscribeLocalEvent<StorageComponent, BeforeExplodeEvent>(OnExploded);
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}

View File

@@ -27,6 +27,7 @@ internal sealed class StunOnCollideSystem : EntitySystem
if (ent.Comp.Refresh)
{
_stunSystem.TryUpdateStunDuration(target, ent.Comp.StunAmount);
_movementMod.TryUpdateMovementSpeedModDuration(
target,
MovementModStatusSystem.TaserSlowdown,

View File

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

View File

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

View File

@@ -1,13 +1,12 @@
using Content.Server.Administration.Logs;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Emp;
using Content.Shared.ActionBlocker;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power;
using Content.Shared.SurveillanceCamera;
using Content.Shared.Verbs;
using Content.Shared.SurveillanceCamera.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
@@ -15,7 +14,7 @@ using Content.Shared.DeviceNetwork.Components;
namespace Content.Server.SurveillanceCamera;
public sealed class SurveillanceCameraSystem : EntitySystem
public sealed class SurveillanceCameraSystem : SharedSurveillanceCameraSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
@@ -57,15 +56,13 @@ public sealed class SurveillanceCameraSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SurveillanceCameraComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SurveillanceCameraComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<SurveillanceCameraComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<SurveillanceCameraComponent, SurveillanceCameraSetupSetName>(OnSetName);
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)
@@ -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)
{
SetActive(camera, args.Powered, component);
@@ -173,6 +150,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem
component.CameraId = args.Name;
component.NameSet = true;
Dirty(uid, component);
UpdateSetupInterface(uid, component);
_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);
component.NetworkSet = true;
Dirty(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))
return;
@@ -271,7 +250,7 @@ public sealed class SurveillanceCameraSystem : EntitySystem
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))
{
@@ -418,21 +397,6 @@ public sealed class SurveillanceCameraSystem : EntitySystem
_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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
using Content.Server.Power.Components;
using Content.Shared.Damage;
using Content.Shared.Damage.Events;
using Content.Shared.FixedPoint;
using Content.Shared.Power;
using Content.Shared.PowerCell.Components;
using Content.Shared.Projectiles;
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.
/// </summary>
[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.Xenoarchaeology.Artifact.XAE.Components;
using Content.Shared.Power.Components;
using Content.Shared.Xenoarchaeology.Artifact;
using Content.Shared.Xenoarchaeology.Artifact.XAE;

View File

@@ -54,7 +54,7 @@ public sealed class XATMagnetSystem : BaseQueryUpdateXATSystem<XATMagnetComponen
if (node.Attached == null)
continue;
var artifact = _xenoArtifactQuery.Get(GetEntity(node.Attached.Value));
var artifact = _xenoArtifactQuery.Get(node.Attached.Value);
if (!CanTrigger(artifact, (uid, node)))
continue;

View File

@@ -70,9 +70,10 @@ public sealed class AccessReaderSystem : EntitySystem
}
}
var examiner = args.Examiner;
var canSeeAccessModification = accessHasBeenModified &&
(HasComp<ShowAccessReaderSettingsComponent>(ent) ||
_inventorySystem.TryGetInventoryEntity<ShowAccessReaderSettingsComponent>(args.Examiner, out _));
(HasComp<ShowAccessReaderSettingsComponent>(examiner) ||
_inventorySystem.TryGetInventoryEntity<ShowAccessReaderSettingsComponent>(examiner, out _));
if (canSeeAccessModification)
{

View File

@@ -1,7 +1,4 @@
using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
namespace Content.Server.Anomaly.Components;
namespace Content.Shared.Anomaly.Components;
/// <summary>
/// This is used for projectiles which affect anomalies through colliding with them.

View File

@@ -324,3 +324,10 @@ public readonly record struct AnomalyHealthChangedEvent(EntityUid Anomaly, float
/// </summary>
[ByRefEvent]
public readonly record struct AnomalyBehaviorChangedEvent(EntityUid Anomaly, ProtoId<AnomalyBehaviorPrototype>? Old, ProtoId<AnomalyBehaviorPrototype>? New);
/// <summary>
/// Event of anomaly being affected by exotic particle.
/// Is raised when particle collides with artifact.
/// </summary>
[ByRefEvent]
public record struct AnomalyAffectedByParticleEvent(EntityUid Anomaly, EntityUid Particle);

View File

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

View File

@@ -14,6 +14,7 @@ using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Pointing;
using Content.Shared.Popups;
using Content.Shared.Rejuvenate;
using Content.Shared.Slippery;
using Content.Shared.Sound;
using Content.Shared.Sound.Components;
@@ -57,7 +58,7 @@ public sealed partial class SleepingSystem : EntitySystem
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<SleepingComponent, EntityZombifiedEvent>(OnZombified);
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SleepingComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
@@ -68,6 +69,7 @@ public sealed partial class SleepingSystem : EntitySystem
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SleepingComponent, StunEndAttemptEvent>(OnStunEndAttempt);
SubscribeLocalEvent<SleepingComponent, StandUpAttemptEvent>(OnStandUpAttempt);
SubscribeLocalEvent<SleepingComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<ForcedSleepingStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusEffectApplied);
SubscribeLocalEvent<SleepingComponent, UnbuckleAttemptEvent>(OnUnbuckleAttempt);
@@ -133,7 +135,7 @@ public sealed partial class SleepingSystem : EntitySystem
RemComp<SpamEmitSoundComponent>(ent);
}
private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
private void OnCompInit(Entity<SleepingComponent> ent, ref ComponentInit args)
{
var ev = new SleepStateChangedEvent(true);
RaiseLocalEvent(ent, ref ev);
@@ -185,6 +187,11 @@ public sealed partial class SleepingSystem : EntitySystem
args.Cancelled = true;
}
private void OnRejuvenate(Entity<SleepingComponent> ent, ref RejuvenateEvent args)
{
TryWaking((ent.Owner, ent.Comp), true);
}
private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
{
if (args.IsInDetailsRange)

View File

@@ -2,13 +2,16 @@ using System.Linq;
using Content.Shared.Access.Components;
using Content.Shared.Clothing.Components;
using Content.Shared.Contraband;
using Content.Shared.Emp;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Lock;
using Content.Shared.Tag;
using Content.Shared.Verbs;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -23,8 +26,11 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
[Dependency] private readonly SharedItemSystem _itemSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = 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 IRobustRandom _random = default!;
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
[Dependency] private readonly INetManager _net = default!;
private static readonly SlotFlags[] IgnoredSlots =
{
@@ -32,12 +38,12 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
SlotFlags.PREVENTEQUIP,
SlotFlags.NONE
};
private static readonly SlotFlags[] Slots = Enum.GetValues<SlotFlags>().Except(IgnoredSlots).ToArray();
private readonly Dictionary<SlotFlags, List<EntProtoId>> _data = new();
public readonly Dictionary<SlotFlags, List<string>> ValidVariants = new();
[Dependency] protected readonly SharedUserInterfaceSystem UI = default!;
private static readonly ProtoId<TagPrototype> WhitelistChameleonTag = "WhitelistChameleon";
@@ -47,6 +53,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
SubscribeLocalEvent<ChameleonClothingComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<ChameleonClothingComponent, GotUnequippedEvent>(OnGotUnequipped);
SubscribeLocalEvent<ChameleonClothingComponent, GetVerbsEvent<InteractionVerb>>(OnVerb);
SubscribeLocalEvent<ChameleonClothingComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<ChameleonClothingComponent, PrototypesReloadedEventArgs>(OnPrototypeReload);
PrepareAllVariants();
@@ -97,21 +104,21 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
// clothing sprite logic
if (TryComp(uid, out ClothingComponent? clothing) &&
proto.TryGetComponent("Clothing", out ClothingComponent? otherClothing))
proto.TryGetComponent(out ClothingComponent? otherClothing, Factory))
{
_clothingSystem.CopyVisuals(uid, otherClothing, clothing);
}
// appearance data logic
if (TryComp(uid, out AppearanceComponent? appearance) &&
proto.TryGetComponent("Appearance", out AppearanceComponent? appearanceOther))
proto.TryGetComponent(out AppearanceComponent? appearanceOther, Factory))
{
_appearance.AppendData(appearanceOther, uid);
Dirty(uid, appearance);
}
// properly mark contraband
if (proto.TryGetComponent("Contraband", out ContrabandComponent? contra))
if (proto.TryGetComponent(out ContrabandComponent? contra, Factory))
{
EnsureComp<ContrabandComponent>(uid, out var 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) { }
/// <summary>
@@ -157,7 +182,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
return false;
// check if it's valid clothing
if (!proto.TryGetComponent("Clothing", out ClothingComponent? clothing))
if (!proto.TryGetComponent(out ClothingComponent? clothing, Factory))
return false;
if (!clothing.Slots.HasFlag(chameleonSlot))
return false;
@@ -187,6 +212,14 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
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()
{
_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

@@ -44,7 +44,7 @@ namespace Content.Shared.Damage
/// <remarks>
/// If this data-field is specified, this allows damageable components to be initialized with non-zero damage.
/// </remarks>
[DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier
[DataField(readOnly: true)] // TODO FULL GAME SAVE
public DamageSpecifier Damage = new();
/// <summary>

View File

@@ -20,6 +20,11 @@ namespace Content.Shared.Damage
[DataDefinition, Serializable, NetSerializable]
public sealed partial class DamageSpecifier : IEquatable<DamageSpecifier>
{
// For the record I regret so many of the decisions i made when rewriting damageable
// Why is it just shitting out dictionaries left and right
// One day Arrays, stackalloc spans, and SIMD will save the day.
// TODO DAMAGEABLE REFACTOR
// These exist solely so the wiki works. Please do not touch them or use them.
[JsonPropertyName("types")]
[DataField("types", customTypeSerializer: typeof(PrototypeIdDictionarySerializer<FixedPoint2, DamageTypePrototype>))]

View File

@@ -2,9 +2,9 @@ using System.Linq;
using Content.Shared.CCVar;
using Content.Shared.Chemistry;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Explosion.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Radiation.Events;
@@ -25,10 +25,10 @@ namespace Content.Shared.Damage
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly SharedChemistryGuideDataSystem _chemistryGuideData = default!;
[Dependency] private readonly SharedExplosionSystem _explosion = default!;
private EntityQuery<AppearanceComponent> _appearanceQuery;
private EntityQuery<DamageableComponent> _damageableQuery;
private EntityQuery<MindContainerComponent> _mindContainerQuery;
public float UniversalAllDamageModifier { get; private set; } = 1f;
public float UniversalAllHealModifier { get; private set; } = 1f;
@@ -52,7 +52,6 @@ namespace Content.Shared.Damage
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
_damageableQuery = GetEntityQuery<DamageableComponent>();
_mindContainerQuery = GetEntityQuery<MindContainerComponent>();
// Damage modifier CVars are updated and stored here to be queried in other systems.
// Note that certain modifiers requires reloading the guidebook.
@@ -60,6 +59,7 @@ namespace Content.Shared.Damage
{
UniversalAllDamageModifier = value;
_chemistryGuideData.ReloadAllReagentPrototypes();
_explosion.ReloadMap();
}, true);
Subs.CVar(_config, CCVars.PlaytestAllHealModifier, value =>
{
@@ -80,7 +80,11 @@ namespace Content.Shared.Damage
UniversalReagentHealModifier = value;
_chemistryGuideData.ReloadAllReagentPrototypes();
}, true);
Subs.CVar(_config, CCVars.PlaytestExplosionDamageModifier, value => UniversalExplosionDamageModifier = value, true);
Subs.CVar(_config, CCVars.PlaytestExplosionDamageModifier, value =>
{
UniversalExplosionDamageModifier = value;
_explosion.ReloadMap();
}, true);
Subs.CVar(_config, CCVars.PlaytestThrownDamageModifier, value => UniversalThrownDamageModifier = value, true);
Subs.CVar(_config, CCVars.PlaytestTopicalsHealModifier, value => UniversalTopicalsHealModifier = value, true);
Subs.CVar(_config, CCVars.PlaytestMobDamageModifier, value => UniversalMobDamageModifier = value, true);
@@ -156,6 +160,9 @@ namespace Content.Shared.Damage
var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList());
_appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance);
}
// TODO DAMAGE
// byref struct event.
RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin));
}
@@ -171,12 +178,24 @@ namespace Content.Shared.Damage
/// Returns a <see cref="DamageSpecifier"/> with information about the actual damage changes. This will be
/// null if the user had no applicable components that can take damage.
/// </returns>
public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false,
bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null)
/// <param name="ignoreResistances">If true, this will ignore the entity's damage modifier (<see cref="DamageableComponent.DamageModifierSetId"/> and skip raising a <see cref="DamageModifyEvent"/>.</param>
/// <param name="interruptsDoAfters">Whether the damage should cancel any damage sensitive do-afters</param>
/// <param name="origin">The entity that is causing this damage</param>
/// <param name="ignoreGlobalModifiers">If true, this will skip over applying the universal damage modifiers (see <see cref="ApplyUniversalAllModifiers"/>).</param>
/// <returns></returns>
public DamageSpecifier? TryChangeDamage(
EntityUid? uid,
DamageSpecifier damage,
bool ignoreResistances = false,
bool interruptsDoAfters = true,
DamageableComponent? damageable = null,
EntityUid? origin = null,
bool ignoreGlobalModifiers = false)
{
if (!uid.HasValue || !_damageableQuery.Resolve(uid.Value, ref damageable, false))
{
// TODO BODY SYSTEM pass damage onto body system
// BOBBY WHEN?
return null;
}
@@ -195,13 +214,13 @@ namespace Content.Shared.Damage
if (!ignoreResistances)
{
if (damageable.DamageModifierSetId != null &&
_prototypeManager.Resolve<DamageModifierSetPrototype>(damageable.DamageModifierSetId, out var modifierSet))
_prototypeManager.Resolve(damageable.DamageModifierSetId, out var modifierSet))
{
// TODO DAMAGE PERFORMANCE
// use a local private field instead of creating a new dictionary here..
damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet);
}
// TODO DAMAGE
// byref struct event.
var ev = new DamageModifyEvent(damage, origin);
RaiseLocalEvent(uid.Value, ev);
damage = ev.Damage;
@@ -212,11 +231,9 @@ namespace Content.Shared.Damage
}
}
damage = ApplyUniversalAllModifiers(damage);
if (!ignoreGlobalModifiers)
damage = ApplyUniversalAllModifiers(damage);
// TODO DAMAGE PERFORMANCE
// Consider using a local private field instead of creating a new dictionary here.
// Would need to check that nothing ever tries to cache the delta.
var delta = new DamageSpecifier();
delta.DamageDict.EnsureCapacity(damage.DamageDict.Count);

View File

@@ -5,24 +5,30 @@ namespace Content.Shared.Emp;
/// <summary>
/// While entity has this component it is "disabled" by EMP.
/// Add desired behaviour in other systems
/// Add desired behaviour in other systems.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause]
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState, AutoGenerateComponentPause]
[Access(typeof(SharedEmpSystem))]
public sealed partial class EmpDisabledComponent : Component
{
/// <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>
[DataField("timeLeft", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan DisabledUntil;
[DataField("effectCoolDown"), ViewVariables(VVAccess.ReadWrite)]
public float EffectCooldown = 3f;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoNetworkedField, AutoPausedField]
public TimeSpan DisabledUntil = TimeSpan.Zero;
/// <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>
[AutoPausedField]
public TimeSpan TargetTime = TimeSpan.Zero;

Some files were not shown because too many files have changed in this diff Show More