Merge remote-tracking branch 'upstream/master' into upstream-sync

# Conflicts:
#	Content.Client/Preferences/ClientPreferencesManager.cs
#	Content.Server/Connection/ConnectionManager.cs
#	Content.Server/Preferences/Managers/ServerPreferencesManager.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Preferences/ICharacterProfile.cs
#	Resources/Prototypes/Catalog/VendingMachines/Inventories/janidrobe.yml
#	Resources/Prototypes/Datasets/Names/fortunes.yml
#	Resources/Prototypes/Entities/Clothing/Shoes/specific.yml
#	Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json
#	SpaceStation14.sln
This commit is contained in:
Morb0
2024-02-26 15:14:44 +03:00
323 changed files with 2470 additions and 2495 deletions

View File

@@ -2,6 +2,7 @@ using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -14,6 +15,10 @@ public sealed class InjectorStatusControl : Control
private readonly SharedSolutionContainerSystem _solutionContainers;
private readonly RichTextLabel _label;
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
private InjectorToggleMode PrevToggleState;
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers)
{
_parent = parent;
@@ -29,6 +34,16 @@ public sealed class InjectorStatusControl : Control
if (!_solutionContainers.TryGetSolution(_parent.Owner, InjectorComponent.SolutionName, out _, out var solution))
return;
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
&& PrevToggleState == _parent.Comp.ToggleState)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
PrevToggleState = _parent.Comp.ToggleState;
// Update current volume and injector state
var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
{

View File

@@ -32,19 +32,22 @@
StyleClasses="OpenLeft"/>
</BoxContainer>
<Control MinSize="0 10"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True" MinSize="0 160">
<PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="6"
MinSize="0 150">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1b1b1e" />
</PanelContainer.PanelOverride>
<BoxContainer Name="ContainerInfo"
Orientation="Vertical"
HorizontalExpand="True">
<Label Text="{Loc 'reagent-dispenser-window-no-container-loaded-text'}"/>
</BoxContainer>
</PanelContainer>
</ScrollContainer>
<BoxContainer Orientation="Horizontal">
<SpriteView Name="View" Scale="4 4" MinSize="150 150"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True" MinSize="0 160">
<PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="6"
MinSize="0 150">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1b1b1e" />
</PanelContainer.PanelOverride>
<BoxContainer Name="ContainerInfo"
Orientation="Vertical"
HorizontalExpand="True">
<Label Text="{Loc 'reagent-dispenser-window-no-container-loaded-text'}"/>
</BoxContainer>
</PanelContainer>
</ScrollContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
@@ -19,6 +18,7 @@ namespace Content.Client.Chemistry.UI
public sealed partial class ReagentDispenserWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action<BaseButton.ButtonEventArgs, DispenseReagentButton>? OnDispenseReagentButtonPressed;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseExited;
@@ -52,7 +52,7 @@ namespace Content.Client.Chemistry.UI
/// Update the button grid of reagents which can be dispensed.
/// </summary>
/// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
public void UpdateReagentsList(List<KeyValuePair<string, KeyValuePair<string,string>>> inventory)
public void UpdateReagentsList(List<KeyValuePair<string, KeyValuePair<string, string>>> inventory)
{
if (ChemicalList == null)
return;
@@ -86,6 +86,9 @@ namespace Content.Client.Chemistry.UI
UpdateContainerInfo(castState);
UpdateReagentsList(castState.Inventory);
_entityManager.TryGetEntity(castState.OutputContainerEntity, out var outputContainerEnt);
View.SetEntity(outputContainerEnt);
// Disable the Clear & Eject button if no beaker
ClearButton.Disabled = castState.OutputContainer is null;
EjectButton.Disabled = castState.OutputContainer is null;
@@ -134,7 +137,7 @@ namespace Content.Client.Chemistry.UI
if (state.OutputContainer is null)
{
ContainerInfo.Children.Add(new Label {Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
ContainerInfo.Children.Add(new Label { Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
return;
}
@@ -159,11 +162,11 @@ namespace Content.Client.Chemistry.UI
? p.LocalizedName
: Loc.GetString("reagent-dispenser-window-reagent-name-not-found-text");
var nameLabel = new Label {Text = $"{localizedName}: "};
var nameLabel = new Label { Text = $"{localizedName}: " };
var quantityLabel = new Label
{
Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity)),
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor},
StyleClasses = { StyleNano.StyleClassLabelSecondaryColor },
};
ContainerInfo.Children.Add(new BoxContainer

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Client\</OutputPath>

View File

@@ -1,18 +1,14 @@
using Content.Shared.CCVar;
using Content.Shared.CrewManifest;
using Content.Shared.CrewManifest;
using Content.Shared.Roles;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
namespace Content.Client.CrewManifest.UI;
public sealed class CrewManifestListing : BoxContainer
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SpriteSystem _spriteSystem;
@@ -25,7 +21,7 @@ public sealed class CrewManifestListing : BoxContainer
public void AddCrewManifestEntries(CrewManifestEntries entries)
{
var entryDict = new Dictionary<string, List<CrewManifestEntry>>();
var entryDict = new Dictionary<DepartmentPrototype, List<CrewManifestEntry>>();
foreach (var entry in entries.Entries)
{
@@ -34,37 +30,19 @@ public sealed class CrewManifestListing : BoxContainer
// this is a little expensive, and could be better
if (department.Roles.Contains(entry.JobPrototype))
{
entryDict.GetOrNew(department.ID).Add(entry);
entryDict.GetOrNew(department).Add(entry);
}
}
}
var entryList = new List<(string section, List<CrewManifestEntry> entries)>();
var entryList = new List<(DepartmentPrototype section, List<CrewManifestEntry> entries)>();
foreach (var (section, listing) in entryDict)
{
entryList.Add((section, listing));
}
var sortOrder = _configManager.GetCVar(CCVars.CrewManifestOrdering).Split(",").ToList();
entryList.Sort((a, b) =>
{
var ai = sortOrder.IndexOf(a.section);
var bi = sortOrder.IndexOf(b.section);
// this is up here so -1 == -1 occurs first
if (ai == bi)
return 0;
if (ai == -1)
return -1;
if (bi == -1)
return 1;
return ai.CompareTo(bi);
});
entryList.Sort((a, b) => DepartmentUIComparer.Instance.Compare(a.section, b.section));
foreach (var item in entryList)
{

View File

@@ -4,24 +4,25 @@ using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using System.Numerics;
using Content.Shared.Roles;
namespace Content.Client.CrewManifest.UI;
public sealed class CrewManifestSection : BoxContainer
{
public CrewManifestSection(IPrototypeManager prototypeManager, SpriteSystem spriteSystem, string sectionTitle,
public CrewManifestSection(
IPrototypeManager prototypeManager,
SpriteSystem spriteSystem,
DepartmentPrototype section,
List<CrewManifestEntry> entries)
{
Orientation = LayoutOrientation.Vertical;
HorizontalExpand = true;
if (Loc.TryGetString($"department-{sectionTitle}", out var localizedDepart))
sectionTitle = localizedDepart;
AddChild(new Label()
{
StyleClasses = { "LabelBig" },
Text = Loc.GetString(sectionTitle)
Text = Loc.GetString($"department-{section.ID}")
});
var gridContainer = new GridContainer()
@@ -55,8 +56,9 @@ public sealed class CrewManifestSection : BoxContainer
var icon = new TextureRect()
{
TextureScale = new Vector2(2, 2),
Stretch = TextureRect.StretchMode.KeepCentered,
VerticalAlignment = VAlignment.Center,
Texture = spriteSystem.Frame0(jobIcon.Icon),
Margin = new Thickness(0, 0, 4, 0)
};
titleContainer.AddChild(icon);

View File

@@ -50,16 +50,8 @@ namespace Content.Client.Decals
protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGridComponent component, Vector2i indices, DecalChunk chunk)
{
base.OnDecalRemoved(gridId, decalId, component, indices, chunk);
if (!component.DecalZIndexIndex.Remove(decalId, out var zIndex))
return;
if (!component.DecalRenderIndex.TryGetValue(zIndex, out var renderIndex))
return;
renderIndex.Remove(decalId);
if (renderIndex.Count == 0)
component.DecalRenderIndex.Remove(zIndex);
DebugTools.Assert(chunk.Decals.ContainsKey(decalId));
chunk.Decals.Remove(decalId);
}
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
@@ -133,8 +125,6 @@ namespace Content.Client.Decals
private void UpdateChunks(EntityUid gridId, DecalGridComponent gridComp, Dictionary<Vector2i, DecalChunk> updatedGridChunks)
{
var chunkCollection = gridComp.ChunkCollection.ChunkCollection;
var renderIndex = gridComp.DecalRenderIndex;
var zIndexIndex = gridComp.DecalZIndexIndex;
// Update any existing data / remove decals we didn't receive data for.
foreach (var (indices, newChunkData) in updatedGridChunks)
@@ -155,11 +145,6 @@ namespace Content.Client.Decals
foreach (var (uid, decal) in newChunkData.Decals)
{
if (zIndexIndex.TryGetValue(uid, out var zIndex))
renderIndex[zIndex].Remove(uid);
renderIndex.GetOrNew(decal.ZIndex)[uid] = decal;
zIndexIndex[uid] = decal.ZIndex;
gridComp.DecalIndex[uid] = indices;
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Prototypes;
namespace Content.Client.Decals.Overlays
@@ -15,6 +16,8 @@ namespace Content.Client.Decals.Overlays
private readonly Dictionary<string, (Texture Texture, bool SnapCardinals)> _cachedTextures = new(64);
private readonly List<(uint Id, Decal Decal)> _decals = new();
public DecalOverlay(
SpriteSystem sprites,
IEntityManager entManager,
@@ -30,10 +33,10 @@ namespace Content.Client.Decals.Overlays
if (args.MapId == MapId.Nullspace)
return;
var grid = Grid;
var owner = Grid.Owner;
if (!_entManager.TryGetComponent(grid, out DecalGridComponent? decalGrid) ||
!_entManager.TryGetComponent(grid, out TransformComponent? xform))
if (!_entManager.TryGetComponent(owner, out DecalGridComponent? decalGrid) ||
!_entManager.TryGetComponent(owner, out TransformComponent? xform))
{
return;
}
@@ -46,46 +49,68 @@ namespace Content.Client.Decals.Overlays
var xformSystem = _entManager.System<TransformSystem>();
var eyeAngle = args.Viewport.Eye?.Rotation ?? Angle.Zero;
var zIndexDictionary = decalGrid.DecalRenderIndex;
var gridAABB = xformSystem.GetInvWorldMatrix(xform).TransformBox(args.WorldBounds.Enlarged(1f));
var chunkEnumerator = new ChunkIndicesEnumerator(gridAABB, SharedDecalSystem.ChunkSize);
_decals.Clear();
if (zIndexDictionary.Count == 0)
while (chunkEnumerator.MoveNext(out var index))
{
if (!decalGrid.ChunkCollection.ChunkCollection.TryGetValue(index.Value, out var chunk))
continue;
foreach (var (id, decal) in chunk.Decals)
{
if (!gridAABB.Contains(decal.Coordinates))
continue;
_decals.Add((id, decal));
}
}
if (_decals.Count == 0)
return;
var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
_decals.Sort((x, y) =>
{
var zComp = x.Decal.ZIndex.CompareTo(y.Decal.ZIndex);
if (zComp != 0)
return zComp;
return x.Id.CompareTo(y.Id);
});
var (_, worldRot, worldMatrix) = xformSystem.GetWorldPositionRotationMatrix(xform);
handle.SetTransform(worldMatrix);
foreach (var decals in zIndexDictionary.Values)
foreach (var (_, decal) in _decals)
{
foreach (var decal in decals.Values)
if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
{
if (!_cachedTextures.TryGetValue(decal.Id, out var cache))
// Nothing to cache someone messed up
if (!_prototypeManager.TryIndex<DecalPrototype>(decal.Id, out var decalProto))
{
// Nothing to cache someone messed up
if (!_prototypeManager.TryIndex<DecalPrototype>(decal.Id, out var decalProto))
{
continue;
}
cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
_cachedTextures[decal.Id] = cache;
continue;
}
var cardinal = Angle.Zero;
if (cache.SnapCardinals)
{
var worldAngle = eyeAngle + worldRot;
cardinal = worldAngle.GetCardinalDir().ToAngle();
}
var angle = decal.Angle - cardinal;
if (angle.Equals(Angle.Zero))
handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
else
handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
cache = (_sprites.Frame0(decalProto.Sprite), decalProto.SnapCardinals);
_cachedTextures[decal.Id] = cache;
}
var cardinal = Angle.Zero;
if (cache.SnapCardinals)
{
var worldAngle = eyeAngle + worldRot;
cardinal = worldAngle.GetCardinalDir().ToAngle();
}
var angle = decal.Angle - cardinal;
if (angle.Equals(Angle.Zero))
handle.DrawTexture(cache.Texture, decal.Coordinates, decal.Color);
else
handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color);
}
handle.SetTransform(Matrix3.Identity);

View File

@@ -1,7 +1,6 @@
using Content.Client.Wires.Visualizers;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Prying.Components;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
@@ -16,13 +15,6 @@ public sealed class AirlockSystem : SharedAirlockSystem
base.Initialize();
SubscribeLocalEvent<AirlockComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<AirlockComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<AirlockComponent, BeforePryEvent>(OnAirlockPryAttempt);
}
private void OnAirlockPryAttempt(EntityUid uid, AirlockComponent component, ref BeforePryEvent args)
{
// TODO: Temporary until airlocks predicted.
args.Cancelled = true;
}
private void OnComponentStartup(EntityUid uid, AirlockComponent comp, ComponentStartup args)
@@ -104,7 +96,7 @@ public sealed class AirlockSystem : SharedAirlockSystem
|| state == DoorState.Denying
|| (state == DoorState.Open && comp.OpenUnlitVisible)
|| (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights))
&& !boltedVisible && !emergencyLightsVisible; ;
&& !boltedVisible && !emergencyLightsVisible;
}
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
@@ -120,5 +112,17 @@ public sealed class AirlockSystem : SharedAirlockSystem
&& !boltedVisible
);
}
switch (state)
{
case DoorState.Open:
args.Sprite.LayerSetState(DoorVisualLayers.BaseUnlit, comp.ClosingSpriteState);
args.Sprite.LayerSetAnimationTime(DoorVisualLayers.BaseUnlit, 0);
break;
case DoorState.Closed:
args.Sprite.LayerSetState(DoorVisualLayers.BaseUnlit, comp.OpeningSpriteState);
args.Sprite.LayerSetAnimationTime(DoorVisualLayers.BaseUnlit, 0);
break;
}
}
}

View File

@@ -1,12 +0,0 @@
using Content.Client.Wires.Visualizers;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
namespace Content.Client.Doors;
public sealed class DoorBoltSystem : SharedDoorBoltSystem
{
// Instantiate sub-class on client for prediction.
}

View File

@@ -3,8 +3,6 @@ using Content.Shared.Doors.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Timing;
@@ -73,7 +71,7 @@ public sealed class DoorSystem : SharedDoorSystem
private void OnAppearanceChange(EntityUid uid, DoorComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null || !_gameTiming.IsFirstTimePredicted)
if (args.Sprite == null)
return;
if(!AppearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
@@ -83,9 +81,9 @@ public sealed class DoorSystem : SharedDoorSystem
{
if (!_resourceCache.TryGetResource<RSIResource>(SpriteSpecifierSerializer.TextureRoot / baseRsi, out var res))
{
Logger.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
Log.Error("Unable to load RSI '{0}'. Trace:\n{1}", baseRsi, Environment.StackTrace);
}
foreach (ISpriteLayer layer in args.Sprite.AllLayers)
foreach (var layer in args.Sprite.AllLayers)
{
layer.Rsi = res?.RSI;
}
@@ -113,31 +111,24 @@ public sealed class DoorSystem : SharedDoorSystem
break;
case DoorState.Opening:
if (animPlayer != null && comp.OpeningAnimationTime != 0.0)
_animationSystem.Play(uid, animPlayer, (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.OpeningAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Closing:
if (animPlayer != null && comp.ClosingAnimationTime != 0.0 && comp.CurrentlyCrushing.Count == 0)
_animationSystem.Play(uid, animPlayer, (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
_animationSystem.Play((uid, animPlayer), (Animation)comp.ClosingAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Denying:
if (animPlayer != null && comp.DenyingAnimation != default)
_animationSystem.Play(uid, animPlayer, (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
if (animPlayer != null)
_animationSystem.Play((uid, animPlayer), (Animation)comp.DenyingAnimation, DoorComponent.AnimationKey);
break;
case DoorState.Welded:
break;
case DoorState.Emagging:
if (animPlayer != null && comp.EmaggingAnimation != default)
_animationSystem.Play(uid, animPlayer, (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
if (animPlayer != null)
_animationSystem.Play((uid, animPlayer), (Animation)comp.EmaggingAnimation, DoorComponent.AnimationKey);
break;
default:
throw new ArgumentOutOfRangeException($"Invalid door visual state {state}");
}
}
// TODO AUDIO PREDICT see comments in server-side PlaySound()
protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
{
if (GameTiming.InPrediction && GameTiming.IsFirstTimePredicted)
Audio.PlayEntity(soundSpecifier, Filter.Local(), uid, false, audioParams);
}
}

View File

@@ -1,3 +1,4 @@
using System.Linq;
using System.Numerics;
using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
@@ -159,8 +160,10 @@ namespace Content.Client.LateJoin
};
var firstCategory = true;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary<string, BoxContainer>();
@@ -176,7 +179,7 @@ namespace Content.Client.LateJoin
jobsAvailable.Add(_prototypeManager.Index<JobPrototype>(jobId));
}
jobsAvailable.Sort((x, y) => -string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCultureIgnoreCase));
jobsAvailable.Sort(JobUIComparer.Instance);
// Do not display departments with no jobs available.
if (jobsAvailable.Count == 0)
@@ -231,7 +234,7 @@ namespace Content.Client.LateJoin
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
Stretch = TextureRect.StretchMode.KeepCentered
VerticalAlignment = VAlignment.Center
};
var jobIcon = _prototypeManager.Index<StatusIconPrototype>(prototype.Icon);

View File

@@ -265,7 +265,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
var jobIcon = new TextureRect()
{
TextureScale = new Vector2(2f, 2f),
Stretch = TextureRect.StretchMode.KeepCentered,
VerticalAlignment = VAlignment.Center,
Texture = _spriteSystem.Frame0(proto.Icon),
Margin = new Thickness(5, 0, 5, 0),
};

View File

@@ -0,0 +1,7 @@
using Content.Shared.Nutrition.EntitySystems;
namespace Content.Client.Nutrition.EntitySystems;
public sealed class OpenableSystem : SharedOpenableSystem
{
}

View File

@@ -4,8 +4,10 @@ using System.Linq;
using Content.Corvax.Interfaces.Client;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Preferences
@@ -19,6 +21,8 @@ namespace Content.Client.Preferences
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
private IClientSponsorsManager? _sponsorsManager; // Corvax-Sponsors
public event Action? OnServerDataLoaded;
@@ -65,7 +69,7 @@ namespace Content.Client.Preferences
{
// Corvax-Sponsors-Start
var sponsorPrototypes = _sponsorsManager?.Prototypes.ToArray() ?? new string[]{};
profile.EnsureValid(sponsorPrototypes);
profile.EnsureValid(_cfg, _prototypes, sponsorPrototypes);
// Corvax-Sponsors-End
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);

View File

@@ -553,10 +553,8 @@ namespace Content.Client.Preferences.UI
_jobCategories.Clear();
var firstCategory = true;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>()
.OrderByDescending(department => department.Weight)
.ThenBy(department => Loc.GetString($"department-{department.ID}"))
.ToList();
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
foreach (var department in departments)
{
@@ -604,9 +602,8 @@ namespace Content.Client.Preferences.UI
var jobs = department.Roles.Select(jobId => _prototypeManager.Index<JobPrototype>(jobId))
.Where(job => job.SetPreference)
.OrderByDescending(job => job.Weight)
.ThenBy(job => job.LocalizedName)
.ToList();
.ToArray();
Array.Sort(jobs, JobUIComparer.Instance);
foreach (var job in jobs)
{
@@ -1339,7 +1336,7 @@ namespace Content.Client.Preferences.UI
var icon = new TextureRect
{
TextureScale = new Vector2(2, 2),
Stretch = TextureRect.StretchMode.KeepCentered
VerticalAlignment = VAlignment.Center
};
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
icon.Texture = jobIcon.Icon.Frame0();

View File

@@ -233,6 +233,8 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
if (args.Function == ContentKeyFunctions.MoveStoredItem)
{
DraggingRotation = control.Location.Rotation;
_menuDragHelper.MouseDown(control);
_menuDragHelper.Update(0f);

View File

@@ -5,7 +5,7 @@
<OutputPath>..\bin\Content.IntegrationTests\</OutputPath>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" />

View File

@@ -239,25 +239,13 @@ namespace Content.IntegrationTests.Tests
if (entManager.HasComponent<StationJobsComponent>(station))
{
// Test that the map has valid latejoin spawn points
// Test that the map has valid latejoin spawn points or container spawn points
if (!NoSpawnMaps.Contains(mapProto))
{
var lateSpawns = 0;
var query = entManager.AllEntityQueryEnumerator<SpawnPointComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.SpawnType != SpawnPointType.LateJoin
|| !xformQuery.TryGetComponent(uid, out var xform)
|| xform.GridUid == null
|| !gridUids.Contains(xform.GridUid.Value))
{
continue;
}
lateSpawns++;
break;
}
lateSpawns += GetCountLateSpawn<SpawnPointComponent>(gridUids, entManager);
lateSpawns += GetCountLateSpawn<ContainerSpawnPointComponent>(gridUids, entManager);
Assert.That(lateSpawns, Is.GreaterThan(0), $"Found no latejoin spawn points on {mapProto}");
}
@@ -296,6 +284,32 @@ namespace Content.IntegrationTests.Tests
await pair.CleanReturnAsync();
}
private static int GetCountLateSpawn<T>(List<EntityUid> gridUids, IEntityManager entManager)
where T : ISpawnPoint, IComponent
{
var resultCount = 0;
var queryPoint = entManager.AllEntityQueryEnumerator<T, TransformComponent>();
#nullable enable
while (queryPoint.MoveNext(out T? comp, out var xform))
{
var spawner = (ISpawnPoint) comp;
if (spawner.SpawnType is not SpawnPointType.LateJoin
|| xform.GridUid == null
|| !gridUids.Contains(xform.GridUid.Value))
{
continue;
}
#nullable disable
resultCount++;
break;
}
return resultCount;
}
[Test]
public async Task AllMapsTested()
{

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>10</LangVersion>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Replay\</OutputPath>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Server.Database\</OutputPath>

View File

@@ -41,7 +41,7 @@ namespace Content.Server.Administration.Systems;
public sealed partial class AdminVerbSystem
{
[Dependency] private readonly DoorBoltSystem _boltsSystem = default!;
[Dependency] private readonly DoorSystem _door = default!;
[Dependency] private readonly AirlockSystem _airlockSystem = default!;
[Dependency] private readonly StackSystem _stackSystem = default!;
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
@@ -78,7 +78,7 @@ public sealed partial class AdminVerbSystem
: new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/bolt.png")),
Act = () =>
{
_boltsSystem.SetBoltsWithAudio(args.Target, bolts, !bolts.BoltsDown);
_door.SetBoltsDown((args.Target, bolts), !bolts.BoltsDown);
},
Impact = LogImpact.Medium,
Message = Loc.GetString(bolts.BoltsDown

View File

@@ -33,9 +33,7 @@ public sealed partial class AnomalySystem
SubscribeLocalEvent<AnomalyGeneratorComponent, MaterialAmountChangedEvent>(OnGeneratorMaterialAmountChanged);
SubscribeLocalEvent<AnomalyGeneratorComponent, AnomalyGeneratorGenerateButtonPressedEvent>(OnGenerateButtonPressed);
SubscribeLocalEvent<AnomalyGeneratorComponent, PowerChangedEvent>(OnGeneratorPowerChanged);
SubscribeLocalEvent<AnomalyGeneratorComponent, EntityUnpausedEvent>(OnGeneratorUnpaused);
SubscribeLocalEvent<GeneratingAnomalyGeneratorComponent, ComponentStartup>(OnGeneratingStartup);
SubscribeLocalEvent<GeneratingAnomalyGeneratorComponent, EntityUnpausedEvent>(OnGeneratingUnpaused);
}
private void OnGeneratorPowerChanged(EntityUid uid, AnomalyGeneratorComponent component, ref PowerChangedEvent args)
@@ -58,11 +56,6 @@ public sealed partial class AnomalySystem
TryGeneratorCreateAnomaly(uid, component);
}
private void OnGeneratorUnpaused(EntityUid uid, AnomalyGeneratorComponent component, ref EntityUnpausedEvent args)
{
component.CooldownEndTime += args.PausedTime;
}
public void UpdateGeneratorUi(EntityUid uid, AnomalyGeneratorComponent component)
{
var materialAmount = _material.GetMaterialAmount(uid, component.RequiredMaterial);
@@ -169,11 +162,6 @@ public sealed partial class AnomalySystem
Appearance.SetData(uid, AnomalyGeneratorVisuals.Generating, true);
}
private void OnGeneratingUnpaused(EntityUid uid, GeneratingAnomalyGeneratorComponent component, ref EntityUnpausedEvent args)
{
component.EndTime += args.PausedTime;
}
private void OnGeneratingFinished(EntityUid uid, AnomalyGeneratorComponent component)
{
var xform = Transform(uid);

View File

@@ -22,7 +22,6 @@ public sealed partial class AnomalySystem
SubscribeLocalEvent<AnomalyVesselComponent, InteractUsingEvent>(OnVesselInteractUsing);
SubscribeLocalEvent<AnomalyVesselComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<AnomalyVesselComponent, ResearchServerGetPointsPerSecondEvent>(OnVesselGetPointsPerSecond);
SubscribeLocalEvent<AnomalyVesselComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<AnomalyShutdownEvent>(OnShutdown);
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnStabilityChanged);
}
@@ -92,11 +91,6 @@ public sealed partial class AnomalySystem
args.Points += (int) (GetAnomalyPointValue(anomaly) * component.PointMultiplier);
}
private void OnUnpaused(EntityUid uid, AnomalyVesselComponent component, ref EntityUnpausedEvent args)
{
component.NextBeep += args.PausedTime;
}
private void OnVesselAnomalyShutdown(ref AnomalyShutdownEvent args)
{
var query = EntityQueryEnumerator<AnomalyVesselComponent>();

View File

@@ -12,13 +12,14 @@ namespace Content.Server.Anomaly.Components;
/// This is used for a machine that is able to generate
/// anomalies randomly on the station.
/// </summary>
[RegisterComponent, Access(typeof(SharedAnomalySystem))]
[RegisterComponent, Access(typeof(SharedAnomalySystem)), AutoGenerateComponentPause]
public sealed partial class AnomalyGeneratorComponent : Component
{
/// <summary>
/// The time at which the cooldown for generating another anomaly will be over
/// </summary>
[DataField("cooldownEndTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan CooldownEndTime = TimeSpan.Zero;
/// <summary>

View File

@@ -10,7 +10,7 @@ namespace Content.Server.Anomaly.Components;
/// they generate points for the selected server based on
/// the anomaly's stability and severity.
/// </summary>
[RegisterComponent, Access(typeof(SharedAnomalySystem))]
[RegisterComponent, Access(typeof(SharedAnomalySystem)), AutoGenerateComponentPause]
public sealed partial class AnomalyVesselComponent : Component
{
/// <summary>
@@ -42,6 +42,7 @@ public sealed partial class AnomalyVesselComponent : Component
/// When the next beep sound will play
/// </summary>
[DataField("nextBeep", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextBeep = TimeSpan.Zero;
/// <summary>

View File

@@ -4,13 +4,14 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Anomaly.Components;
[RegisterComponent, Access(typeof(SharedAnomalySystem))]
[RegisterComponent, Access(typeof(SharedAnomalySystem)), AutoGenerateComponentPause]
public sealed partial class GeneratingAnomalyGeneratorComponent : Component
{
/// <summary>
/// When the generating period will end.
/// </summary>
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan EndTime = TimeSpan.Zero;
public EntityUid? AudioStream;

View File

@@ -29,11 +29,9 @@ public sealed class RottingSystem : SharedRottingSystem
base.Initialize();
SubscribeLocalEvent<PerishableComponent, MapInitEvent>(OnPerishableMapInit);
SubscribeLocalEvent<PerishableComponent, EntityUnpausedEvent>(OnPerishableUnpaused);
SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnPerishableExamined);
SubscribeLocalEvent<RottingComponent, EntityUnpausedEvent>(OnRottingUnpaused);
SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<RottingComponent, MobStateChangedEvent>(OnRottingMobStateChanged);
SubscribeLocalEvent<RottingComponent, BeingGibbedEvent>(OnGibbed);
@@ -47,11 +45,6 @@ public sealed class RottingSystem : SharedRottingSystem
component.RotNextUpdate = _timing.CurTime + component.PerishUpdateRate;
}
private void OnPerishableUnpaused(EntityUid uid, PerishableComponent component, ref EntityUnpausedEvent args)
{
component.RotNextUpdate += args.PausedTime;
}
private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args)
{
if (args.NewMobState != MobState.Dead && args.OldMobState != MobState.Dead)
@@ -64,11 +57,6 @@ public sealed class RottingSystem : SharedRottingSystem
component.RotNextUpdate = _timing.CurTime + component.PerishUpdateRate;
}
private void OnRottingUnpaused(EntityUid uid, RottingComponent component, ref EntityUnpausedEvent args)
{
component.NextRotUpdate += args.PausedTime;
}
private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args)
{
if (TryComp<PerishableComponent>(uid, out var perishable))

View File

@@ -209,15 +209,19 @@ namespace Content.Server.Cargo.Systems
_random.Shuffle(tradePads);
var freePads = GetFreeCargoPallets(trade, tradePads);
foreach (var pad in freePads)
if (freePads.Count >= order.OrderQuantity) //check if the station has enough free pallets
{
var coordinates = new EntityCoordinates(trade, pad.Transform.LocalPosition);
if (FulfillOrder(order, coordinates, orderDatabase.PrinterOutput))
foreach (var pad in freePads)
{
tradeDestination = trade;
break;
var coordinates = new EntityCoordinates(trade, pad.Transform.LocalPosition);
if (FulfillOrder(order, coordinates, orderDatabase.PrinterOutput))
{
tradeDestination = trade;
order.NumDispatched++;
if (order.OrderQuantity <= order.NumDispatched) //Spawn a crate on free pellets until the order is fulfilled.
break;
}
}
}

View File

@@ -196,12 +196,14 @@ public sealed partial class CargoSystem
return _pads;
}
private IEnumerable<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)>
private List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)>
GetFreeCargoPallets(EntityUid gridUid,
List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)> pallets)
{
_setEnts.Clear();
List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)> outList = new();
foreach (var pallet in pallets)
{
var aabb = _lookup.GetAABBNoContainer(pallet.Entity, pallet.Transform.LocalPosition, pallet.Transform.LocalRotation);
@@ -209,8 +211,10 @@ public sealed partial class CargoSystem
if (_lookup.AnyLocalEntitiesIntersecting(gridUid, aabb, LookupFlags.Dynamic))
continue;
yield return pallet;
outList.Add(pallet);
}
return outList;
}
#endregion

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Charges.Components;
/// Something with limited charges that can be recharged automatically.
/// Requires LimitedChargesComponent to function.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(ChargesSystem))]
public sealed partial class AutoRechargeComponent : Component
{
@@ -21,5 +21,6 @@ public sealed partial class AutoRechargeComponent : Component
/// The time when the next charge will be added
/// </summary>
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextChargeTime;
}

View File

@@ -10,13 +10,6 @@ public sealed class ChargesSystem : SharedChargesSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AutoRechargeComponent, EntityUnpausedEvent>(OnUnpaused);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -32,11 +25,6 @@ public sealed class ChargesSystem : SharedChargesSystem
}
}
private void OnUnpaused(EntityUid uid, AutoRechargeComponent comp, ref EntityUnpausedEvent args)
{
comp.NextChargeTime += args.PausedTime;
}
protected override void OnExamine(EntityUid uid, LimitedChargesComponent comp, ExaminedEvent args)
{
base.OnExamine(uid, comp, args);

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
/// <summary>
/// Causes an entity to automatically emote when taking damage.
/// </summary>
[RegisterComponent, Access(typeof(EmoteOnDamageSystem))]
[RegisterComponent, Access(typeof(EmoteOnDamageSystem)), AutoGenerateComponentPause]
public sealed partial class EmoteOnDamageComponent : Component
{
/// <summary>
@@ -41,6 +41,7 @@ public sealed partial class EmoteOnDamageComponent : Component
/// The simulation time of the last emote preformed due to taking damage.
/// </summary>
[DataField("lastEmoteTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan LastEmoteTime = TimeSpan.Zero;
/// <summary>

View File

@@ -18,15 +18,9 @@ public sealed class EmoteOnDamageSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<EmoteOnDamageComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<EmoteOnDamageComponent, DamageChangedEvent>(OnDamage);
}
private void OnUnpaused(EntityUid uid, EmoteOnDamageComponent emoteOnDamage, ref EntityUnpausedEvent args)
{
emoteOnDamage.LastEmoteTime += args.PausedTime;
}
private void OnDamage(EntityUid uid, EmoteOnDamageComponent emoteOnDamage, DamageChangedEvent args)
{
if (!args.DamageIncreased)

View File

@@ -9,7 +9,7 @@ namespace Content.Server.Chemistry.Components;
/// <summary>
/// Passively decreases a solution's quantity of reagent(s).
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(SolutionPurgeSystem))]
public sealed partial class SolutionPurgeComponent : Component
{
@@ -42,5 +42,6 @@ public sealed partial class SolutionPurgeComponent : Component
/// The time when the next purge will occur.
/// </summary>
[DataField("nextPurgeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextPurgeTime = TimeSpan.FromSeconds(0);
}

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Chemistry.Components;
/// <summary>
/// Passively increases a solution's quantity of a reagent.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(SolutionRegenerationSystem))]
public sealed partial class SolutionRegenerationComponent : Component
{
@@ -39,5 +39,6 @@ public sealed partial class SolutionRegenerationComponent : Component
/// The time when the next regeneration will occur.
/// </summary>
[DataField("nextChargeTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextRegenTime = TimeSpan.FromSeconds(0);
}

View File

@@ -68,7 +68,7 @@ namespace Content.Server.Chemistry.EntitySystems
var inventory = GetInventory(reagentDispenser);
var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, inventory, reagentDispenser.Comp.DispenseAmount);
var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, GetNetEntity(outputContainer), inventory, reagentDispenser.Comp.DispenseAmount);
_userInterfaceSystem.TrySetUiState(reagentDispenser, ReagentDispenserUiKey.Key, state);
}

View File

@@ -10,13 +10,6 @@ public sealed class SolutionPurgeSystem : EntitySystem
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionPurgeComponent, EntityUnpausedEvent>(OnUnpaused);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -33,9 +26,4 @@ public sealed class SolutionPurgeSystem : EntitySystem
_solutionContainer.SplitSolutionWithout(solution.Value, purge.Quantity, purge.Preserve.ToArray());
}
}
private void OnUnpaused(Entity<SolutionPurgeComponent> entity, ref EntityUnpausedEvent args)
{
entity.Comp.NextPurgeTime += args.PausedTime;
}
}

View File

@@ -12,13 +12,6 @@ public sealed class SolutionRegenerationSystem : EntitySystem
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionRegenerationComponent, EntityUnpausedEvent>(OnUnpaused);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -52,9 +45,4 @@ public sealed class SolutionRegenerationSystem : EntitySystem
}
}
}
private void OnUnpaused(Entity<SolutionRegenerationComponent> entity, ref EntityUnpausedEvent args)
{
entity.Comp.NextRegenTime += args.PausedTime;
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Utility;
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
namespace Content.Shared.Chunking;

View File

@@ -114,7 +114,7 @@ namespace Content.Server.Connection
// Corvax-Start: Allow privileged players bypass bunker
var isPrivileged = await HavePrivilegedJoin(e.UserId);
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && !isPrivileged)
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null && !isPrivileged)
// Corvax-End
{
var showReason = _cfg.GetCVar(CCVars.PanicBunkerShowReason);
@@ -236,13 +236,11 @@ namespace Content.Server.Connection
// Corvax-Queue-Start: Make these conditions in one place, for checks in the connection and in the queue
public async Task<bool> HavePrivilegedJoin(NetUserId userId)
{
var isAdmin = await _dbManager.GetAdminDataForAsync(userId) != null;
var havePriorityJoin = _sponsorsMgr != null && _sponsorsMgr.HavePriorityJoin(userId); // Corvax-Sponsors
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return isAdmin ||
havePriorityJoin || // Corvax-Sponsors
return havePriorityJoin || // Corvax-Sponsors
wasInGame;
}
// Corvax-Queue-End

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
<LangVersion>11</LangVersion>
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<OutputPath>..\bin\Content.Server\</OutputPath>

View File

@@ -9,10 +9,12 @@ using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.CrewManifest;
using Content.Shared.GameTicking;
using Content.Shared.Roles;
using Content.Shared.StationRecords;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.CrewManifest;
@@ -23,6 +25,7 @@ public sealed class CrewManifestSystem : EntitySystem
[Dependency] private readonly StationRecordsSystem _recordsSystem = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
/// <summary>
/// Cached crew manifest entries. The alternative is to outright
@@ -223,15 +226,26 @@ public sealed class CrewManifestSystem : EntitySystem
var entries = new CrewManifestEntries();
var entriesSort = new List<(JobPrototype? job, CrewManifestEntry entry)>();
foreach (var recordObject in iter)
{
var record = recordObject.Item2;
var entry = new CrewManifestEntry(record.Name, record.JobTitle, record.JobIcon, record.JobPrototype);
entries.Entries.Add(entry);
_prototypeManager.TryIndex(record.JobPrototype, out JobPrototype? job);
entriesSort.Add((job, entry));
}
entries.Entries = entries.Entries.OrderBy(e => e.JobTitle).ThenBy(e => e.Name).ToList();
entriesSort.Sort((a, b) =>
{
var cmp = JobUIComparer.Instance.Compare(a.job, b.job);
if (cmp != 0)
return cmp;
return string.Compare(a.entry.Name, b.entry.Name, StringComparison.CurrentCultureIgnoreCase);
});
entries.Entries = entriesSort.Select(x => x.entry).ToArray();
_cachedEntries[station] = entries;
}
}

View File

@@ -21,6 +21,7 @@ using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Content.Shared.Decals.DecalGridComponent;
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
namespace Content.Server.Decals
{

View File

@@ -12,7 +12,6 @@ namespace Content.Server.DeviceLinking.Systems
[UsedImplicitly]
public sealed class DoorSignalControlSystem : EntitySystem
{
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly DoorSystem _doorSystem = default!;
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
@@ -79,7 +78,7 @@ namespace Content.Server.DeviceLinking.Systems
bolt = state == SignalState.High;
}
_bolts.SetBoltsWithAudio(uid, bolts, bolt);
_doorSystem.SetBoltsDown((uid, bolts), bolt);
}
}

View File

@@ -68,7 +68,6 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
// Shouldn't need re-anchoring.
SubscribeLocalEvent<DisposalUnitComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<DisposalUnitComponent, EntityUnpausedEvent>(OnUnpaused);
// TODO: Predict me when hands predicted
SubscribeLocalEvent<DisposalUnitComponent, ContainerRelayMovementEntityEvent>(OnMovement);
SubscribeLocalEvent<DisposalUnitComponent, PowerChangedEvent>(OnPowerChange);
@@ -103,14 +102,6 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
GetNetEntityList(component.RecentlyEjected));
}
private void OnUnpaused(EntityUid uid, SharedDisposalUnitComponent component, ref EntityUnpausedEvent args)
{
if (component.NextFlush != null)
component.NextFlush = component.NextFlush.Value + args.PausedTime;
component.NextPressurized += args.PausedTime;
}
private void AddDisposalAltVerbs(EntityUid uid, SharedDisposalUnitComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract)

View File

@@ -1,13 +1,10 @@
using Content.Server.DeviceLinking.Events;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Wires;
using Content.Shared.Doors;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction;
using Content.Shared.Wires;
using Content.Shared.Prying.Components;
using Robust.Shared.Player;
namespace Content.Server.Doors.Systems;
@@ -15,8 +12,6 @@ namespace Content.Server.Doors.Systems;
public sealed class AirlockSystem : SharedAirlockSystem
{
[Dependency] private readonly WiresSystem _wiresSystem = default!;
[Dependency] private readonly PowerReceiverSystem _power = default!;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
public override void Initialize()
{
@@ -26,13 +21,7 @@ public sealed class AirlockSystem : SharedAirlockSystem
SubscribeLocalEvent<AirlockComponent, SignalReceivedEvent>(OnSignalReceived);
SubscribeLocalEvent<AirlockComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<AirlockComponent, DoorStateChangedEvent>(OnStateChanged);
SubscribeLocalEvent<AirlockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<AirlockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
SubscribeLocalEvent<AirlockComponent, ActivateInWorldEvent>(OnActivate, before: new[] { typeof(DoorSystem) });
SubscribeLocalEvent<AirlockComponent, GetPryTimeModifierEvent>(OnGetPryMod);
SubscribeLocalEvent<AirlockComponent, BeforePryEvent>(OnBeforePry);
}
private void OnAirlockInit(EntityUid uid, AirlockComponent component, ComponentInit args)
@@ -54,6 +43,9 @@ public sealed class AirlockSystem : SharedAirlockSystem
private void OnPowerChanged(EntityUid uid, AirlockComponent component, ref PowerChangedEvent args)
{
component.Powered = args.Powered;
Dirty(uid, component);
if (TryComp<AppearanceComponent>(uid, out var appearanceComponent))
{
Appearance.SetData(uid, DoorVisuals.Powered, args.Powered, appearanceComponent);
@@ -74,80 +66,6 @@ public sealed class AirlockSystem : SharedAirlockSystem
}
}
private void OnStateChanged(EntityUid uid, AirlockComponent component, DoorStateChangedEvent args)
{
// TODO move to shared? having this be server-side, but having client-side door opening/closing & prediction
// means that sometimes the panels & bolt lights may be visible despite a door being completely open.
// Only show the maintenance panel if the airlock is closed
if (TryComp<WiresPanelComponent>(uid, out var wiresPanel))
{
_wiresSystem.ChangePanelVisibility(uid, wiresPanel, component.OpenPanelVisible || args.State != DoorState.Open);
}
// If the door is closed, we should look if the bolt was locked while closing
UpdateAutoClose(uid, component);
// Make sure the airlock auto closes again next time it is opened
if (args.State == DoorState.Closed)
component.AutoClose = true;
}
/// <summary>
/// Updates the auto close timer.
/// </summary>
public void UpdateAutoClose(EntityUid uid, AirlockComponent? airlock = null, DoorComponent? door = null)
{
if (!Resolve(uid, ref airlock, ref door))
return;
if (door.State != DoorState.Open)
return;
if (!airlock.AutoClose)
return;
if (!CanChangeState(uid, airlock))
return;
var autoev = new BeforeDoorAutoCloseEvent();
RaiseLocalEvent(uid, autoev, false);
if (autoev.Cancelled)
return;
DoorSystem.SetNextStateChange(uid, airlock.AutoCloseDelay * airlock.AutoCloseDelayModifier);
}
private void OnBeforeDoorOpened(EntityUid uid, AirlockComponent component, BeforeDoorOpenedEvent args)
{
if (!CanChangeState(uid, component))
args.Cancel();
}
protected override void OnBeforeDoorClosed(EntityUid uid, AirlockComponent component, BeforeDoorClosedEvent args)
{
base.OnBeforeDoorClosed(uid, component, args);
if (args.Cancelled)
return;
// only block based on bolts / power status when initially closing the door, not when its already
// mid-transition. Particularly relevant for when the door was pried-closed with a crowbar, which bypasses
// the initial power-check.
if (TryComp(uid, out DoorComponent? door)
&& !door.Partial
&& !CanChangeState(uid, component))
{
args.Cancel();
}
}
private void OnBeforeDoorDenied(EntityUid uid, AirlockComponent component, BeforeDoorDeniedEvent args)
{
if (!CanChangeState(uid, component))
args.Cancel();
}
private void OnActivate(EntityUid uid, AirlockComponent component, ActivateInWorldEvent args)
{
if (TryComp<WiresPanelComponent>(uid, out var panel) &&
@@ -169,32 +87,4 @@ public sealed class AirlockSystem : SharedAirlockSystem
component.AutoClose = false;
}
}
private void OnGetPryMod(EntityUid uid, AirlockComponent component, ref GetPryTimeModifierEvent args)
{
if (_power.IsPowered(uid))
args.PryTimeModifier *= component.PoweredPryModifier;
if (_bolts.IsBolted(uid))
args.PryTimeModifier *= component.BoltedPryModifier;
}
private void OnBeforePry(EntityUid uid, AirlockComponent component, ref BeforePryEvent args)
{
if (args.Cancelled)
return;
if (!this.IsPowered(uid, EntityManager) || args.PryPowered)
return;
args.Message = "airlock-component-cannot-pry-is-powered-message";
args.Cancelled = true;
}
public bool CanChangeState(EntityUid uid, AirlockComponent component)
{
return this.IsPowered(uid, EntityManager) && !_bolts.IsBolted(uid);
}
}

View File

@@ -1,90 +0,0 @@
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Doors;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
namespace Content.Server.Doors.Systems;
public sealed class DoorBoltSystem : SharedDoorBoltSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoorBoltComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<DoorBoltComponent, DoorStateChangedEvent>(OnStateChanged);
}
private void OnPowerChanged(EntityUid uid, DoorBoltComponent component, ref PowerChangedEvent args)
{
if (args.Powered)
{
if (component.BoltWireCut)
SetBoltsWithAudio(uid, component, true);
}
UpdateBoltLightStatus(uid, component);
}
public void UpdateBoltLightStatus(EntityUid uid, DoorBoltComponent component)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
Appearance.SetData(uid, DoorVisuals.BoltLights, GetBoltLightsVisible(uid, component), appearance);
}
public bool GetBoltLightsVisible(EntityUid uid, DoorBoltComponent component)
{
return component.BoltLightsEnabled &&
component.BoltsDown &&
this.IsPowered(uid, EntityManager);
}
public void SetBoltLightsEnabled(EntityUid uid, DoorBoltComponent component, bool value)
{
if (component.BoltLightsEnabled == value)
return;
component.BoltLightsEnabled = value;
UpdateBoltLightStatus(uid, component);
}
public void SetBoltsDown(EntityUid uid, DoorBoltComponent component, bool value)
{
if (component.BoltsDown == value)
return;
component.BoltsDown = value;
UpdateBoltLightStatus(uid, component);
}
private void OnStateChanged(EntityUid uid, DoorBoltComponent component, DoorStateChangedEvent args)
{
// If the door is closed, we should look if the bolt was locked while closing
UpdateBoltLightStatus(uid, component);
}
public void SetBoltsWithAudio(EntityUid uid, DoorBoltComponent component, bool newBolts)
{
if (newBolts == component.BoltsDown)
return;
component.BoltsDown = newBolts;
Audio.PlayPvs(newBolts ? component.BoltDownSound : component.BoltUpSound, uid);
UpdateBoltLightStatus(uid, component);
}
public bool IsBolted(EntityUid uid, DoorBoltComponent? component = null)
{
if (!Resolve(uid, ref component))
{
return false;
}
return component.BoltsDown;
}
}

View File

@@ -1,49 +1,22 @@
using Content.Server.Access;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Power.EntitySystems;
using Content.Shared.Database;
using Content.Server.Power.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Emag.Systems;
using Content.Shared.Interaction;
using Content.Shared.Prying.Components;
using Content.Shared.Prying.Systems;
using Content.Shared.Tools.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
namespace Content.Server.Doors.Systems;
public sealed class DoorSystem : SharedDoorSystem
{
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
[Dependency] private readonly PryingSystem _pryingSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
SubscribeLocalEvent<DoorComponent, WeldableChangedEvent>(OnWeldChanged);
SubscribeLocalEvent<DoorComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<DoorComponent, PriedEvent>(OnAfterPry);
}
protected override void OnActivate(EntityUid uid, DoorComponent door, ActivateInWorldEvent args)
{
// TODO once access permissions are shared, move this back to shared.
if (args.Handled || !door.ClickOpen)
return;
if (!TryToggleDoor(uid, door, args.User))
_pryingSystem.TryPry(uid, args.User, out _);
args.Handled = true;
SubscribeLocalEvent<DoorBoltComponent, PowerChangedEvent>(OnBoltPowerChanged);
}
protected override void SetCollidable(
@@ -65,144 +38,16 @@ public sealed class DoorSystem : SharedDoorSystem
base.SetCollidable(uid, collidable, door, physics, occluder);
}
// TODO AUDIO PREDICT Figure out a better way to handle sound and prediction. For now, this works well enough?
//
// Currently a client will predict when a door is going to close automatically. So any client in PVS range can just
// play their audio locally. Playing it server-side causes an odd delay, while in shared it causes double-audio.
//
// But if we just do that, then if a door is closed prematurely as the result of an interaction (i.e., using "E" on
// an open door), then the audio would only be played for the client performing the interaction.
//
// So we do this:
// - Play audio client-side IF the closing is being predicted (auto-close or predicted interaction)
// - Server assumes automated closing is predicted by clients and does not play audio unless otherwise specified.
// - Major exception is player interactions, which other players cannot predict
// - In that case, send audio to all players, except possibly the interacting player if it was a predicted
// interaction.
/// <summary>
/// Selectively send sound to clients, taking care to not send the double-audio.
/// </summary>
/// <param name="uid">The audio source</param>
/// <param name="soundSpecifier">The sound</param>
/// <param name="audioParams">The audio parameters.</param>
/// <param name="predictingPlayer">The user (if any) that instigated an interaction</param>
/// <param name="predicted">Whether this interaction would have been predicted. If the predicting player is null,
/// this assumes it would have been predicted by all players in PVS range.</param>
protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
private void OnBoltPowerChanged(Entity<DoorBoltComponent> ent, ref PowerChangedEvent args)
{
// If this sound would have been predicted by all clients, do not play any audio.
if (predicted && predictingPlayer == null)
return;
if (predicted)
Audio.PlayPredicted(soundSpecifier, uid, predictingPlayer, audioParams);
else
Audio.PlayPvs(soundSpecifier, uid, audioParams);
}
#region DoAfters
private void OnWeldAttempt(EntityUid uid, DoorComponent component, WeldableAttemptEvent args)
{
if (component.CurrentlyCrushing.Count > 0)
if (args.Powered)
{
args.Cancel();
return;
if (ent.Comp.BoltWireCut)
SetBoltsDown(ent, true);
}
if (component.State != DoorState.Closed && component.State != DoorState.Welded)
{
args.Cancel();
}
}
private void OnWeldChanged(EntityUid uid, DoorComponent component, ref WeldableChangedEvent args)
{
if (component.State == DoorState.Closed)
SetState(uid, DoorState.Welded, component);
else if (component.State == DoorState.Welded)
SetState(uid, DoorState.Closed, component);
}
#endregion
/// <summary>
/// Open a door if a player or door-bumper (PDA, ID-card) collide with the door. Sadly, bullets no longer
/// generate "access denied" sounds as you fire at a door.
/// </summary>
protected override void HandleCollide(EntityUid uid, DoorComponent door, ref StartCollideEvent args)
{
// TODO ACCESS READER move access reader to shared and predict door opening/closing
// Then this can be moved to the shared system without mispredicting.
if (!door.BumpOpen)
return;
if (door.State is not (DoorState.Closed or DoorState.Denying))
return;
var otherUid = args.OtherEntity;
if (Tags.HasTag(otherUid, "DoorBumpOpener"))
TryOpen(uid, door, otherUid, quiet: door.State == DoorState.Denying);
}
private void OnEmagged(EntityUid uid, DoorComponent door, ref GotEmaggedEvent args)
{
if (TryComp<AirlockComponent>(uid, out var airlockComponent))
{
if (_bolts.IsBolted(uid) || !this.IsPowered(uid, EntityManager))
return;
if (door.State == DoorState.Closed)
{
SetState(uid, DoorState.Emagging, door);
PlaySound(uid, door.SparkSound, AudioParams.Default.WithVolume(8), args.UserUid, false);
args.Handled = true;
}
}
}
public override void StartOpening(EntityUid uid, DoorComponent? door = null, EntityUid? user = null, bool predicted = false)
{
if (!Resolve(uid, ref door))
return;
var lastState = door.State;
SetState(uid, DoorState.Opening, door);
if (door.OpenSound != null)
PlaySound(uid, door.OpenSound, AudioParams.Default.WithVolume(-5), user, predicted);
if (lastState == DoorState.Emagging && TryComp<DoorBoltComponent>(uid, out var doorBoltComponent))
_bolts.SetBoltsWithAudio(uid, doorBoltComponent, !doorBoltComponent.BoltsDown);
}
/// <summary>
/// Open or close a door after it has been successfuly pried.
/// </summary>
private void OnAfterPry(EntityUid uid, DoorComponent door, ref PriedEvent args)
{
if (door.State == DoorState.Closed)
{
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} open");
StartOpening(uid, door, args.User);
}
else if (door.State == DoorState.Open)
{
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User)} pried {ToPrettyString(uid)} closed");
StartClosing(uid, door, args.User);
}
}
protected override void CheckDoorBump(Entity<DoorComponent, PhysicsComponent> ent)
{
var (uid, door, physics) = ent;
if (door.BumpOpen)
{
foreach (var other in PhysicsSystem.GetContactingEntities(uid, physics, approximate: true))
{
if (Tags.HasTag(other, "DoorBumpOpener") && TryOpen(uid, door, other, quiet: true))
break;
}
}
UpdateBoltLightStatus(ent);
ent.Comp.Powered = args.Powered;
Dirty(ent, ent.Comp);
}
}

View File

@@ -18,19 +18,18 @@ public sealed partial class DoorBoltLightWireAction : ComponentWireAction<DoorBo
public override bool Cut(EntityUid user, Wire wire, DoorBoltComponent door)
{
EntityManager.System<DoorBoltSystem>().SetBoltLightsEnabled(wire.Owner, door, false);
EntityManager.System<DoorSystem>().SetBoltLightsEnabled((wire.Owner, door), false);
return true;
}
public override bool Mend(EntityUid user, Wire wire, DoorBoltComponent door)
{
EntityManager.System<DoorBoltSystem>().SetBoltLightsEnabled(wire.Owner, door, true);
EntityManager.System<DoorSystem>().SetBoltLightsEnabled((wire.Owner, door), true);
return true;
}
public override void Pulse(EntityUid user, Wire wire, DoorBoltComponent door)
{
EntityManager.System<DoorBoltSystem>().SetBoltLightsEnabled(wire.Owner, door, !door.BoltLightsEnabled);
EntityManager.System<DoorSystem>().SetBoltLightsEnabled((wire.Owner, door), !door.BoltLightsEnabled);
}
}

View File

@@ -19,24 +19,24 @@ public sealed partial class DoorBoltWireAction : ComponentWireAction<DoorBoltCom
public override bool Cut(EntityUid user, Wire wire, DoorBoltComponent airlock)
{
EntityManager.System<DoorBoltSystem>().SetBoltWireCut(airlock, true);
EntityManager.System<DoorSystem>().SetBoltWireCut((wire.Owner, airlock), true);
if (!airlock.BoltsDown && IsPowered(wire.Owner))
EntityManager.System<DoorBoltSystem>().SetBoltsWithAudio(wire.Owner, airlock, true);
EntityManager.System<DoorSystem>().SetBoltsDown((wire.Owner, airlock), true, user);
return true;
}
public override bool Mend(EntityUid user, Wire wire, DoorBoltComponent door)
{
EntityManager.System<DoorBoltSystem>().SetBoltWireCut(door, false);
EntityManager.System<DoorSystem>().SetBoltWireCut((wire.Owner, door), false);
return true;
}
public override void Pulse(EntityUid user, Wire wire, DoorBoltComponent door)
{
if (IsPowered(wire.Owner))
EntityManager.System<DoorBoltSystem>().SetBoltsWithAudio(wire.Owner, door, !door.BoltsDown);
EntityManager.System<DoorSystem>().SetBoltsDown((wire.Owner, door), !door.BoltsDown);
else if (!door.BoltsDown)
EntityManager.System<DoorBoltSystem>().SetBoltsWithAudio(wire.Owner, door, true);
EntityManager.System<DoorSystem>().SetBoltsDown((wire.Owner, door), true);
}
}

View File

@@ -17,7 +17,6 @@ public sealed class EmpSystem : SharedEmpSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EmpDisabledComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(HandleEmpTrigger);
@@ -96,12 +95,6 @@ public sealed class EmpSystem : SharedEmpSystem
}
}
private void OnUnpaused(EntityUid uid, EmpDisabledComponent component, ref EntityUnpausedEvent args)
{
component.DisabledUntil += args.PausedTime;
component.TargetTime += args.PausedTime;
}
private void OnExamine(EntityUid uid, EmpDisabledComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine"));

View File

@@ -1,397 +0,0 @@
using Content.Server.Interaction;
using Content.Server.Kitchen.Components;
using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Execution;
using Content.Shared.Interaction.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Execution;
/// <summary>
/// Verb for violently murdering cuffed creatures.
/// </summary>
public sealed class ExecutionSystem : EntitySystem
{
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly GunSystem _gunSystem = default!;
private const float MeleeExecutionTimeModifier = 5.0f;
private const float GunExecutionTime = 6.0f;
private const float DamageModifier = 9.0f;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharpComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionVerbsMelee);
SubscribeLocalEvent<GunComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionVerbsGun);
SubscribeLocalEvent<SharpComponent, ExecutionDoAfterEvent>(OnDoafterMelee);
SubscribeLocalEvent<GunComponent, ExecutionDoAfterEvent>(OnDoafterGun);
}
private void OnGetInteractionVerbsMelee(
EntityUid uid,
SharpComponent component,
GetVerbsEvent<UtilityVerb> args)
{
if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract)
return;
var attacker = args.User;
var weapon = args.Using!.Value;
var victim = args.Target;
if (!CanExecuteWithMelee(weapon, victim, attacker))
return;
UtilityVerb verb = new()
{
Act = () =>
{
TryStartMeleeExecutionDoafter(weapon, victim, attacker);
},
Impact = LogImpact.High,
Text = Loc.GetString("execution-verb-name"),
Message = Loc.GetString("execution-verb-message"),
};
args.Verbs.Add(verb);
}
private void OnGetInteractionVerbsGun(
EntityUid uid,
GunComponent component,
GetVerbsEvent<UtilityVerb> args)
{
if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract)
return;
var attacker = args.User;
var weapon = args.Using!.Value;
var victim = args.Target;
if (!CanExecuteWithGun(weapon, victim, attacker))
return;
UtilityVerb verb = new()
{
Act = () =>
{
TryStartGunExecutionDoafter(weapon, victim, attacker);
},
Impact = LogImpact.High,
Text = Loc.GetString("execution-verb-name"),
Message = Loc.GetString("execution-verb-message"),
};
args.Verbs.Add(verb);
}
private bool CanExecuteWithAny(EntityUid weapon, EntityUid victim, EntityUid attacker)
{
// No point executing someone if they can't take damage
if (!TryComp<DamageableComponent>(victim, out var damage))
return false;
// You can't execute something that cannot die
if (!TryComp<MobStateComponent>(victim, out var mobState))
return false;
// You're not allowed to execute dead people (no fun allowed)
if (_mobStateSystem.IsDead(victim, mobState))
return false;
// You must be able to attack people to execute
if (!_actionBlockerSystem.CanAttack(attacker, victim))
return false;
// The victim must be incapacitated to be executed
if (victim != attacker && _actionBlockerSystem.CanInteract(victim, null))
return false;
// All checks passed
return true;
}
private bool CanExecuteWithMelee(EntityUid weapon, EntityUid victim, EntityUid user)
{
if (!CanExecuteWithAny(weapon, victim, user)) return false;
// We must be able to actually hurt people with the weapon
if (!TryComp<MeleeWeaponComponent>(weapon, out var melee) && melee!.Damage.GetTotal() > 0.0f)
return false;
return true;
}
private bool CanExecuteWithGun(EntityUid weapon, EntityUid victim, EntityUid user)
{
if (!CanExecuteWithAny(weapon, victim, user)) return false;
// We must be able to actually fire the gun
if (!TryComp<GunComponent>(weapon, out var gun) && _gunSystem.CanShoot(gun!))
return false;
return true;
}
private void TryStartMeleeExecutionDoafter(EntityUid weapon, EntityUid victim, EntityUid attacker)
{
if (!CanExecuteWithMelee(weapon, victim, attacker))
return;
var executionTime = (1.0f / Comp<MeleeWeaponComponent>(weapon).AttackRate) * MeleeExecutionTimeModifier;
if (attacker == victim)
{
ShowExecutionPopup("suicide-popup-melee-initial-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("suicide-popup-melee-initial-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
else
{
ShowExecutionPopup("execution-popup-melee-initial-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("execution-popup-melee-initial-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
var doAfter =
new DoAfterArgs(EntityManager, attacker, executionTime, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
NeedHand = true
};
_doAfterSystem.TryStartDoAfter(doAfter);
}
private void TryStartGunExecutionDoafter(EntityUid weapon, EntityUid victim, EntityUid attacker)
{
if (!CanExecuteWithGun(weapon, victim, attacker))
return;
if (attacker == victim)
{
ShowExecutionPopup("suicide-popup-gun-initial-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("suicide-popup-gun-initial-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
else
{
ShowExecutionPopup("execution-popup-gun-initial-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("execution-popup-gun-initial-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
var doAfter =
new DoAfterArgs(EntityManager, attacker, GunExecutionTime, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
NeedHand = true
};
_doAfterSystem.TryStartDoAfter(doAfter);
}
private bool OnDoafterChecks(EntityUid uid, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
return false;
if (!CanExecuteWithAny(args.Used.Value, args.Target.Value, uid))
return false;
// All checks passed
return true;
}
private void OnDoafterMelee(EntityUid uid, SharpComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
return;
var attacker = args.User;
var victim = args.Target!.Value;
var weapon = args.Used!.Value;
if (!CanExecuteWithMelee(weapon, victim, attacker)) return;
if (!TryComp<MeleeWeaponComponent>(weapon, out var melee) && melee!.Damage.GetTotal() > 0.0f)
return;
_damageableSystem.TryChangeDamage(victim, melee.Damage * DamageModifier, true);
_audioSystem.PlayEntity(melee.HitSound, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
if (attacker == victim)
{
ShowExecutionPopup("suicide-popup-melee-complete-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("suicide-popup-melee-complete-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
else
{
ShowExecutionPopup("execution-popup-melee-complete-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("execution-popup-melee-complete-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
}
}
// TODO: This repeats a lot of the code of the serverside GunSystem, make it not do that
private void OnDoafterGun(EntityUid uid, GunComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
return;
var attacker = args.User;
var weapon = args.Used!.Value;
var victim = args.Target!.Value;
if (!CanExecuteWithGun(weapon, victim, attacker)) return;
// Check if any systems want to block our shot
var prevention = new ShotAttemptedEvent
{
User = attacker,
Used = weapon
};
RaiseLocalEvent(weapon, ref prevention);
if (prevention.Cancelled)
return;
RaiseLocalEvent(attacker, ref prevention);
if (prevention.Cancelled)
return;
// Not sure what this is for but gunsystem uses it so ehhh
var attemptEv = new AttemptShootEvent(attacker, null);
RaiseLocalEvent(weapon, ref attemptEv);
if (attemptEv.Cancelled)
{
if (attemptEv.Message != null)
{
_popupSystem.PopupClient(attemptEv.Message, weapon, attacker);
return;
}
}
// Take some ammunition for the shot (one bullet)
var fromCoordinates = Transform(attacker).Coordinates;
var ev = new TakeAmmoEvent(1, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, attacker);
RaiseLocalEvent(weapon, ev);
// Check if there's any ammo left
if (ev.Ammo.Count <= 0)
{
_audioSystem.PlayEntity(component.SoundEmpty, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
ShowExecutionPopup("execution-popup-gun-empty", Filter.Pvs(weapon), PopupType.Medium, attacker, victim, weapon);
return;
}
// Information about the ammo like damage
DamageSpecifier damage = new DamageSpecifier();
// Get some information from IShootable
var ammoUid = ev.Ammo[0].Entity;
switch (ev.Ammo[0].Shootable)
{
case CartridgeAmmoComponent cartridge:
// Get the damage value
var prototype = _prototypeManager.Index<EntityPrototype>(cartridge.Prototype);
prototype.TryGetComponent<ProjectileComponent>(out var projectileA, _componentFactory); // sloth forgive me
if (projectileA != null)
{
damage = projectileA.Damage * cartridge.Count;
}
// Expend the cartridge
cartridge.Spent = true;
_appearanceSystem.SetData(ammoUid!.Value, AmmoVisuals.Spent, true);
Dirty(ammoUid.Value, cartridge);
break;
case AmmoComponent newAmmo:
TryComp<ProjectileComponent>(ammoUid, out var projectileB);
if (projectileB != null)
{
damage = projectileB.Damage;
}
Del(ammoUid);
break;
case HitscanPrototype hitscan:
damage = hitscan.Damage!;
break;
default:
throw new ArgumentOutOfRangeException();
}
// Clumsy people have a chance to shoot themselves
if (TryComp<ClumsyComponent>(attacker, out var clumsy) && component.ClumsyProof == false)
{
if (_interactionSystem.TryRollClumsy(attacker, 0.33333333f, clumsy))
{
ShowExecutionPopup("execution-popup-gun-clumsy-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("execution-popup-gun-clumsy-external", Filter.PvsExcept(attacker), PopupType.MediumCaution, attacker, victim, weapon);
// You shoot yourself with the gun (no damage multiplier)
_damageableSystem.TryChangeDamage(attacker, damage, origin: attacker);
_audioSystem.PlayEntity(component.SoundGunshot, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
return;
}
}
// Gun successfully fired, deal damage
_damageableSystem.TryChangeDamage(victim, damage * DamageModifier, true);
_audioSystem.PlayEntity(component.SoundGunshot, Filter.Pvs(weapon), weapon, false, AudioParams.Default);
// Popups
if (attacker != victim)
{
ShowExecutionPopup("execution-popup-gun-complete-internal", Filter.Entities(attacker), PopupType.Medium, attacker, victim, weapon);
ShowExecutionPopup("execution-popup-gun-complete-external", Filter.PvsExcept(attacker), PopupType.LargeCaution, attacker, victim, weapon);
}
else
{
ShowExecutionPopup("suicide-popup-gun-complete-internal", Filter.Entities(attacker), PopupType.LargeCaution, attacker, victim, weapon);
ShowExecutionPopup("suicide-popup-gun-complete-external", Filter.PvsExcept(attacker), PopupType.LargeCaution, attacker, victim, weapon);
}
}
private void ShowExecutionPopup(string locString, Filter filter, PopupType type,
EntityUid attacker, EntityUid victim, EntityUid weapon)
{
_popupSystem.PopupEntity(Loc.GetString(
locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
attacker, filter, true, type);
}
}

View File

@@ -6,7 +6,7 @@ namespace Content.Server.Explosion.Components.OnTrigger;
/// <summary>
/// After being triggered applies the specified components and runs triggers again.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class TwoStageTriggerComponent : Component
{
/// <summary>
@@ -23,6 +23,7 @@ public sealed partial class TwoStageTriggerComponent : Component
public ComponentRegistry SecondStageComponents = new();
[DataField("nextTriggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan? NextTriggerTime;
[DataField("triggered")]

View File

@@ -13,7 +13,7 @@ namespace Content.Server.Explosion.Components
/// <summary>
/// Raises a <see cref="TriggerEvent"/> whenever an entity collides with a fixture attached to the owner of this component.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent
{
public const string FixtureID = "trigger-on-proximity-fixture";
@@ -58,6 +58,7 @@ namespace Content.Server.Explosion.Components
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextTrigger = TimeSpan.Zero;
/// <summary>
@@ -65,6 +66,7 @@ namespace Content.Server.Explosion.Components
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextVisualUpdate = TimeSpan.Zero;
/// <summary>

View File

@@ -17,7 +17,6 @@ public sealed partial class TriggerSystem
SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TriggerOnProximityComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown);
// Shouldn't need re-anchoring.
SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
@@ -65,12 +64,6 @@ public sealed partial class TriggerSystem
collisionLayer: component.Layer);
}
private void OnUnpaused(EntityUid uid, TriggerOnProximityComponent component, ref EntityUnpausedEvent args)
{
component.NextTrigger += args.PausedTime;
component.NextVisualUpdate += args.PausedTime;
}
private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
{
if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)

View File

@@ -14,16 +14,9 @@ public sealed class TwoStageTriggerSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TwoStageTriggerComponent, EntityUnpausedEvent>(OnTriggerUnpaused);
SubscribeLocalEvent<TwoStageTriggerComponent, TriggerEvent>(OnTrigger);
}
private void OnTriggerUnpaused(EntityUid uid, TwoStageTriggerComponent component, ref EntityUnpausedEvent args)
{
if (component.NextTriggerTime != null)
component.NextTriggerTime = component.NextTriggerTime.Value + args.PausedTime;
}
private void OnTrigger(EntityUid uid, TwoStageTriggerComponent component, TriggerEvent args)
{
if (component.Triggered)

View File

@@ -93,7 +93,7 @@ public sealed class FireExtinguisherSystem : EntitySystem
private void OnGetInteractionVerbs(Entity<FireExtinguisherComponent> entity, ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanInteract)
if (!args.CanAccess || !args.CanInteract)
return;
var user = args.User;

View File

@@ -63,7 +63,6 @@ public sealed class SmokeSystem : EntitySystem
SubscribeLocalEvent<SmokeComponent, ReactionAttemptEvent>(OnReactionAttempt);
SubscribeLocalEvent<SmokeComponent, SolutionRelayEvent<ReactionAttemptEvent>>(OnReactionAttempt);
SubscribeLocalEvent<SmokeComponent, SpreadNeighborsEvent>(OnSmokeSpread);
SubscribeLocalEvent<SmokeAffectedComponent, EntityUnpausedEvent>(OnAffectedUnpaused);
}
/// <inheritdoc/>
@@ -124,11 +123,6 @@ public sealed class SmokeSystem : EntitySystem
RemComp(args.OtherEntity, smokeAffectedComponent);
}
private void OnAffectedUnpaused(Entity<SmokeAffectedComponent> entity, ref EntityUnpausedEvent args)
{
entity.Comp.NextSecond += args.PausedTime;
}
private void OnSmokeSpread(Entity<SmokeComponent> entity, ref SpreadNeighborsEvent args)
{
if (entity.Comp.SpreadAmount == 0 || !_solutionContainerSystem.ResolveSolution(entity.Owner, SmokeComponent.SolutionName, ref entity.Comp.Solution, out var solution))

View File

@@ -128,6 +128,7 @@ public sealed partial class GameTicker
metadata["roundEndPlayers"] = _serialman.WriteValue(_replayRoundPlayerInfo);
metadata["roundEndText"] = new ValueDataNode(_replayRoundText);
metadata["server_id"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.ServerId));
metadata["server_name"] = new ValueDataNode(_configurationManager.GetCVar(CCVars.AdminLogsServerName));
metadata["roundId"] = new ValueDataNode(RoundId.ToString());
}

View File

@@ -7,6 +7,7 @@ using Content.Server.GameTicking.Rules.Components;
using Content.Server.Popups;
using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.RoundEnd;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
@@ -49,6 +50,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly JobSystem _jobs = default!;
public override void Initialize()
{
@@ -271,7 +273,8 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
var prefList = new List<ICommonSession>();
foreach (var player in allPlayers)
{
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity) || HasComp<ZombieImmuneComponent>(player.AttachedEntity))
if (player.AttachedEntity == null || !HasComp<HumanoidAppearanceComponent>(player.AttachedEntity) ||
HasComp<ZombieImmuneComponent>(player.AttachedEntity) || !_jobs.CanBeAntag(player))
continue;
if (HasComp<InitialInfectedExemptComponent>(player.AttachedEntity))

View File

@@ -8,7 +8,7 @@ namespace Content.Server.Gateway.Components;
/// <summary>
/// Controlling gateway that links to other gateway destinations on the server.
/// </summary>
[RegisterComponent, Access(typeof(GatewaySystem))]
[RegisterComponent, Access(typeof(GatewaySystem)), AutoGenerateComponentPause]
public sealed partial class GatewayComponent : Component
{
/// <summary>
@@ -61,5 +61,6 @@ public sealed partial class GatewayComponent : Component
/// The time at which the portal can next be opened.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextReady;
}

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Gateway.Components;
/// <summary>
/// Generates gateway destinations at a regular interval.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class GatewayGeneratorComponent : Component
{
/// <summary>
@@ -20,6 +20,7 @@ public sealed partial class GatewayGeneratorComponent : Component
/// Next time another seed unlocks.
/// </summary>
[DataField(customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextUnlock;
/// <summary>

View File

@@ -67,7 +67,6 @@ public sealed class GatewayGeneratorSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GatewayGeneratorComponent, EntityUnpausedEvent>(OnGeneratorUnpaused);
SubscribeLocalEvent<GatewayGeneratorComponent, MapInitEvent>(OnGeneratorMapInit);
SubscribeLocalEvent<GatewayGeneratorComponent, ComponentShutdown>(OnGeneratorShutdown);
SubscribeLocalEvent<GatewayGeneratorDestinationComponent, AttemptGatewayOpenEvent>(OnGeneratorAttemptOpen);
@@ -85,11 +84,6 @@ public sealed class GatewayGeneratorSystem : EntitySystem
}
}
private void OnGeneratorUnpaused(Entity<GatewayGeneratorComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUnlock += args.PausedTime;
}
private void OnGeneratorMapInit(EntityUid uid, GatewayGeneratorComponent generator, MapInitEvent args)
{
if (!_cfgManager.GetCVar(CCVars.GatewayGeneratorEnabled))

View File

@@ -32,7 +32,6 @@ public sealed class GatewaySystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<GatewayComponent, EntityUnpausedEvent>(OnGatewayUnpaused);
SubscribeLocalEvent<GatewayComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<GatewayComponent, ActivatableUIOpenAttemptEvent>(OnGatewayOpenAttempt);
SubscribeLocalEvent<GatewayComponent, BoundUIOpenedEvent>(UpdateUserInterface);
@@ -48,11 +47,6 @@ public sealed class GatewaySystem : EntitySystem
UpdateAllGateways();
}
private void OnGatewayUnpaused(EntityUid uid, GatewayComponent component, ref EntityUnpausedEvent args)
{
component.NextReady += args.PausedTime;
}
private void OnStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
{
// no need to update ui since its just been created, just do portal

View File

@@ -52,7 +52,6 @@ namespace Content.Server.Hands.Systems
SubscribeLocalEvent<HandsComponent, BodyPartRemovedEvent>(HandleBodyPartRemoved);
SubscribeLocalEvent<HandsComponent, ComponentGetState>(GetComponentState);
SubscribeLocalEvent<HandsComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<HandsComponent, BeforeExplodeEvent>(OnExploded);
@@ -73,10 +72,6 @@ namespace Content.Server.Hands.Systems
args.State = new HandsComponentState(hands);
}
private void OnUnpaused(Entity<HandsComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextThrowTime += args.PausedTime;
}
private void OnExploded(Entity<HandsComponent> ent, ref BeforeExplodeEvent args)
{

View File

@@ -6,7 +6,7 @@ namespace Content.Server.Kitchen.Components;
/// <summary>
/// Attached to a microwave that is currently in the process of cooking
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class ActiveMicrowaveComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
@@ -17,6 +17,7 @@ public sealed partial class ActiveMicrowaveComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan MalfunctionTime = TimeSpan.Zero;
[ViewVariables]

View File

@@ -86,7 +86,6 @@ namespace Content.Server.Kitchen.EntitySystems
SubscribeLocalEvent<ActiveMicrowaveComponent, ComponentStartup>(OnCookStart);
SubscribeLocalEvent<ActiveMicrowaveComponent, ComponentShutdown>(OnCookStop);
SubscribeLocalEvent<ActiveMicrowaveComponent, EntityUnpausedEvent>(OnEntityUnpaused);
SubscribeLocalEvent<ActiveMicrowaveComponent, EntInsertedIntoContainerMessage>(OnActiveMicrowaveInsert);
SubscribeLocalEvent<ActiveMicrowaveComponent, EntRemovedFromContainerMessage>(OnActiveMicrowaveRemove);
@@ -112,11 +111,6 @@ namespace Content.Server.Kitchen.EntitySystems
microwaveComponent.PlayingStream = _audio.Stop(microwaveComponent.PlayingStream);
}
private void OnEntityUnpaused(Entity<ActiveMicrowaveComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.MalfunctionTime += args.PausedTime;
}
private void OnActiveMicrowaveInsert(Entity<ActiveMicrowaveComponent> ent, ref EntInsertedIntoContainerMessage args)
{
AddComp<ActivelyMicrowavedComponent>(args.Entity);

View File

@@ -36,7 +36,6 @@ public sealed class MagicSystem : EntitySystem
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DoorBoltSystem _boltsSystem = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
@@ -307,7 +306,7 @@ public sealed class MagicSystem : EntitySystem
foreach (var entity in _lookup.GetEntitiesInRange(coords, args.Range))
{
if (TryComp<DoorBoltComponent>(entity, out var bolts))
_boltsSystem.SetBoltsDown(entity, bolts, false);
_doorSystem.SetBoltsDown((entity, bolts), false);
if (TryComp<DoorComponent>(entity, out var doorComp) && doorComp.State is not DoorState.Open)
_doorSystem.StartOpening(entity);

View File

@@ -6,7 +6,7 @@ namespace Content.Server.Medical.Components;
/// <summary>
/// After scanning, retrieves the target Uid to use with its related UI.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(HealthAnalyzerSystem), typeof(CryoPodSystem))]
public sealed partial class HealthAnalyzerComponent : Component
{
@@ -14,6 +14,7 @@ public sealed partial class HealthAnalyzerComponent : Component
/// When should the next update be sent for the patient
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextUpdate = TimeSpan.Zero;
/// <summary>

View File

@@ -50,21 +50,12 @@ public sealed class DefibrillatorSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<DefibrillatorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<DefibrillatorComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<DefibrillatorComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<DefibrillatorComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DefibrillatorComponent, DefibrillatorZapDoAfterEvent>(OnDoAfter);
}
private void OnUnpaused(EntityUid uid, DefibrillatorComponent component, ref EntityUnpausedEvent args)
{
if (component.NextZapTime == null)
return;
component.NextZapTime = component.NextZapTime.Value + args.PausedTime;
}
private void OnUseInHand(EntityUid uid, DefibrillatorComponent component, UseInHandEvent args)
{
if (args.Handled || !TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay)))

View File

@@ -30,7 +30,6 @@ public sealed class HealthAnalyzerSystem : EntitySystem
public override void Initialize()
{
SubscribeLocalEvent<HealthAnalyzerComponent, EntityUnpausedEvent>(OnEntityUnpaused);
SubscribeLocalEvent<HealthAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<HealthAnalyzerComponent, HealthAnalyzerDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<HealthAnalyzerComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoContainer);
@@ -65,11 +64,6 @@ public sealed class HealthAnalyzerSystem : EntitySystem
}
}
private void OnEntityUnpaused(Entity<HealthAnalyzerComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
/// <summary>
/// Trigger the doafter for scanning
/// </summary>

View File

@@ -7,7 +7,7 @@ namespace Content.Server.Medical.SuitSensors;
/// Tracking device, embedded in almost all uniforms and jumpsuits.
/// If enabled, will report to crew monitoring console owners position and status.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
[Access(typeof(SuitSensorSystem))]
public sealed partial class SuitSensorComponent : Component
{
@@ -57,6 +57,7 @@ public sealed partial class SuitSensorComponent : Component
/// Next time when sensor updated owners status
/// </summary>
[DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextUpdate = TimeSpan.Zero;
/// <summary>

View File

@@ -40,7 +40,6 @@ public sealed class SuitSensorSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SuitSensorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<SuitSensorComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<SuitSensorComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
@@ -51,11 +50,6 @@ public sealed class SuitSensorSystem : EntitySystem
SubscribeLocalEvent<SuitSensorComponent, EmpDisabledRemoved>(OnEmpFinished);
}
private void OnUnpaused(EntityUid uid, SuitSensorComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdate += args.PausedTime;
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.NPC.Components;
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class NPCJukeComponent : Component
{
[DataField("jukeType")]
@@ -13,6 +13,7 @@ public sealed partial class NPCJukeComponent : Component
public float JukeDuration = 0.5f;
[DataField("nextJuke", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextJuke;
[DataField("targetTile")]

View File

@@ -11,7 +11,7 @@ namespace Content.Server.NPC.Components;
/// <summary>
/// Added to NPCs that are moving.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class NPCSteeringComponent : Component
{
#region Context Steering
@@ -49,6 +49,7 @@ public sealed partial class NPCSteeringComponent : Component
/// Next time we can change our steering direction.
/// </summary>
[DataField("nextSteer", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextSteer = TimeSpan.Zero;
[DataField("lastSteerIndex")]
@@ -66,6 +67,7 @@ public sealed partial class NPCSteeringComponent : Component
public EntityCoordinates LastStuckCoordinates;
[DataField("lastStuckTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan LastStuckTime;
public const float StuckDistance = 1f;

View File

@@ -5,7 +5,7 @@ namespace Content.Server.NPC.Pathfinding;
/// <summary>
/// Stores the relevant pathfinding data for grids.
/// </summary>
[RegisterComponent, Access(typeof(PathfindingSystem))]
[RegisterComponent, Access(typeof(PathfindingSystem)), AutoGenerateComponentPause]
public sealed partial class GridPathfindingComponent : Component
{
[ViewVariables]
@@ -15,6 +15,7 @@ public sealed partial class GridPathfindingComponent : Component
/// Next time the graph is allowed to update.
/// </summary>
/// Removing this datafield is the lazy fix HOWEVER I want to purge this anyway and do pathfinding at runtime.
[AutoPausedField]
public TimeSpan NextUpdate;
[ViewVariables]

View File

@@ -44,7 +44,6 @@ public sealed partial class PathfindingSystem
{
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
SubscribeLocalEvent<GridPathfindingComponent, EntityUnpausedEvent>(OnGridPathPause);
SubscribeLocalEvent<GridPathfindingComponent, ComponentShutdown>(OnGridPathShutdown);
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
SubscribeLocalEvent<CollisionLayerChangeEvent>(OnCollisionLayerChange);
@@ -61,10 +60,6 @@ public sealed partial class PathfindingSystem
DirtyChunk(ev.Entity, Comp<MapGridComponent>(ev.Entity).GridTileToLocal(ev.NewTile.GridIndices));
}
private void OnGridPathPause(EntityUid uid, GridPathfindingComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdate += args.PausedTime;
}
private void OnGridPathShutdown(EntityUid uid, GridPathfindingComponent component, ComponentShutdown args)
{

View File

@@ -33,15 +33,9 @@ public sealed class NPCJukeSystem : EntitySystem
_npcRangedQuery = GetEntityQuery<NPCRangedCombatComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
SubscribeLocalEvent<NPCJukeComponent, EntityUnpausedEvent>(OnJukeUnpaused);
SubscribeLocalEvent<NPCJukeComponent, NPCSteeringEvent>(OnJukeSteering);
}
private void OnJukeUnpaused(EntityUid uid, NPCJukeComponent component, ref EntityUnpausedEvent args)
{
component.NextJuke += args.PausedTime;
}
private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSteeringEvent args)
{
if (component.JukeType == JukeType.AdjacentTile)

View File

@@ -107,7 +107,6 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
Subs.CVar(_configManager, CCVars.NPCPathfinding, SetNPCPathfinding, true);
SubscribeLocalEvent<NPCSteeringComponent, ComponentShutdown>(OnSteeringShutdown);
SubscribeLocalEvent<NPCSteeringComponent, EntityUnpausedEvent>(OnSteeringUnpaused);
SubscribeNetworkEvent<RequestNPCSteeringDebugEvent>(OnDebugRequest);
}
@@ -158,12 +157,6 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
component.PathfindToken = null;
}
private void OnSteeringUnpaused(EntityUid uid, NPCSteeringComponent component, ref EntityUnpausedEvent args)
{
component.LastStuckTime += args.PausedTime;
component.NextSteer += args.PausedTime;
}
/// <summary>
/// Adds the AI to the steering system to move towards a specific target
/// </summary>

View File

@@ -10,7 +10,7 @@ namespace Content.Server.Nutrition.Components;
/// <summary>
/// This is used for a machine that extracts hunger from entities and creates meat. Yum!
/// </summary>
[RegisterComponent, Access(typeof(FatExtractorSystem))]
[RegisterComponent, Access(typeof(FatExtractorSystem)), AutoGenerateComponentPause]
public sealed partial class FatExtractorComponent : Component
{
/// <summary>
@@ -48,6 +48,7 @@ public sealed partial class FatExtractorComponent : Component
/// When the next update will occur
/// </summary>
[DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextUpdate;
/// <summary>

View File

@@ -40,23 +40,11 @@ public sealed class AnimalHusbandrySystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<ReproductiveComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<ReproductiveComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<InfantComponent, EntityUnpausedEvent>(OnInfantUnpaused);
SubscribeLocalEvent<InfantComponent, ComponentStartup>(OnInfantStartup);
SubscribeLocalEvent<InfantComponent, ComponentShutdown>(OnInfantShutdown);
}
private void OnUnpaused(EntityUid uid, ReproductiveComponent component, ref EntityUnpausedEvent args)
{
component.NextBreedAttempt += args.PausedTime;
}
private void OnInfantUnpaused(EntityUid uid, InfantComponent component, ref EntityUnpausedEvent args)
{
component.InfantEndTime += args.PausedTime;
}
// we express EZ-pass terminate the pregnancy if a player takes the role
private void OnMindAdded(EntityUid uid, ReproductiveComponent component, MindAddedMessage args)
{

View File

@@ -130,21 +130,18 @@ public sealed class DrinkSystem : EntitySystem
private void OnExamined(Entity<DrinkComponent> entity, ref ExaminedEvent args)
{
var hasOpenable = TryComp<OpenableComponent>(entity, out var openable);
TryComp<OpenableComponent>(entity, out var openable);
if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable)
return;
// put Empty / Xu after Opened, or start a new line
args.AddMarkup(hasOpenable ? " - " : "\n");
var empty = IsEmpty(entity, entity.Comp);
if (empty)
{
args.AddMarkup(Loc.GetString("drink-component-on-examine-is-empty"));
args.PushMarkup(Loc.GetString("drink-component-on-examine-is-empty"));
return;
}
if (TryComp<ExaminableSolutionComponent>(entity, out var comp))
if (HasComp<ExaminableSolutionComponent>(entity))
{
//provide exact measurement for beakers
args.PushText(Loc.GetString("drink-component-on-examine-exact-volume", ("amount", DrinkVolume(entity, entity.Comp))));
@@ -159,7 +156,7 @@ public sealed class DrinkSystem : EntitySystem
> 33 => HalfEmptyOrHalfFull(args),
_ => "drink-component-on-examine-is-mostly-empty",
};
args.AddMarkup(Loc.GetString(remainingString));
args.PushMarkup(Loc.GetString(remainingString));
}
}

View File

@@ -27,18 +27,12 @@ public sealed class FatExtractorSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<FatExtractorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<FatExtractorComponent, GotEmaggedEvent>(OnGotEmagged);
SubscribeLocalEvent<FatExtractorComponent, StorageAfterCloseEvent>(OnClosed);
SubscribeLocalEvent<FatExtractorComponent, StorageAfterOpenEvent>(OnOpen);
SubscribeLocalEvent<FatExtractorComponent, PowerChangedEvent>(OnPowerChanged);
}
private void OnUnpaused(EntityUid uid, FatExtractorComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdate += args.PausedTime;
}
private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args)
{
args.Handled = true;

View File

@@ -1,16 +1,6 @@
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Shared.Nutrition.EntitySystems;
using Content.Server.Nutrition.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Nutrition.EntitySystems;
@@ -19,43 +9,11 @@ namespace Content.Server.Nutrition.EntitySystems;
/// </summary>
public sealed class OpenableSystem : SharedOpenableSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<OpenableComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<OpenableComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<OpenableComponent, ExaminedEvent>(OnExamined, after: new[] { typeof(PuddleSystem) });
SubscribeLocalEvent<OpenableComponent, SolutionTransferAttemptEvent>(OnTransferAttempt);
SubscribeLocalEvent<OpenableComponent, MeleeHitEvent>(HandleIfClosed);
SubscribeLocalEvent<OpenableComponent, AfterInteractEvent>(HandleIfClosed);
SubscribeLocalEvent<OpenableComponent, GetVerbsEvent<Verb>>(AddOpenCloseVerbs);
}
private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args)
{
UpdateAppearance(uid, comp);
}
private void OnUse(EntityUid uid, OpenableComponent comp, UseInHandEvent args)
{
if (args.Handled || !comp.OpenableByHand)
return;
args.Handled = TryOpen(uid, comp);
}
private void OnExamined(EntityUid uid, OpenableComponent comp, ExaminedEvent args)
{
if (!comp.Opened || !args.IsInDetailsRange)
return;
var text = Loc.GetString(comp.ExamineText);
args.PushMarkup(text);
}
private void OnTransferAttempt(EntityUid uid, OpenableComponent comp, SolutionTransferAttemptEvent args)
@@ -66,135 +24,4 @@ public sealed class OpenableSystem : SharedOpenableSystem
args.Cancel(Loc.GetString("drink-component-try-use-drink-not-open", ("owner", uid)));
}
}
private void HandleIfClosed(EntityUid uid, OpenableComponent comp, HandledEntityEventArgs args)
{
// prevent spilling/pouring/whatever drinks when closed
args.Handled = !comp.Opened;
}
private void AddOpenCloseVerbs(EntityUid uid, OpenableComponent comp, GetVerbsEvent<Verb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
Verb verb;
if (comp.Opened)
{
if (!comp.Closeable)
return;
verb = new()
{
Text = Loc.GetString(comp.CloseVerbText),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")),
Act = () => TryClose(args.Target, comp)
};
}
else
{
verb = new()
{
Text = Loc.GetString(comp.OpenVerbText),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")),
Act = () => TryOpen(args.Target, comp)
};
}
args.Verbs.Add(verb);
}
/// <summary>
/// Returns true if the entity either does not have OpenableComponent or it is opened.
/// Drinks that don't have OpenableComponent are automatically open, so it returns true.
/// </summary>
public bool IsOpen(EntityUid uid, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false))
return true;
return comp.Opened;
}
/// <summary>
/// Returns true if the entity both has OpenableComponent and is not opened.
/// Drinks that don't have OpenableComponent are automatically open, so it returns false.
/// If user is not null a popup will be shown to them.
/// </summary>
public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false))
return false;
if (comp.Opened)
return false;
if (user != null)
_popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value);
return true;
}
/// <summary>
/// Update open visuals to the current value.
/// </summary>
public void UpdateAppearance(EntityUid uid, OpenableComponent? comp = null, AppearanceComponent? appearance = null)
{
if (!Resolve(uid, ref comp))
return;
_appearance.SetData(uid, OpenableVisuals.Opened, comp.Opened, appearance);
}
/// <summary>
/// Sets the opened field and updates open visuals.
/// </summary>
public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false) || opened == comp.Opened)
return;
comp.Opened = opened;
if (opened)
{
var ev = new OpenableOpenedEvent();
RaiseLocalEvent(uid, ref ev);
}
else
{
var ev = new OpenableClosedEvent();
RaiseLocalEvent(uid, ref ev);
}
UpdateAppearance(uid, comp);
}
/// <summary>
/// If closed, opens it and plays the sound.
/// </summary>
/// <returns>Whether it got opened</returns>
public bool TryOpen(EntityUid uid, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false) || comp.Opened)
return false;
SetOpen(uid, true, comp);
_audio.PlayPvs(comp.Sound, uid);
return true;
}
/// <summary>
/// If opened, closes it and plays the close sound, if one is defined.
/// </summary>
/// <returns>Whether it got closed</returns>
public bool TryClose(EntityUid uid, OpenableComponent? comp = null)
{
if (!Resolve(uid, ref comp, false) || !comp.Opened || !comp.Closeable)
return false;
SetOpen(uid, false, comp);
if (comp.CloseSound != null)
_audio.PlayPvs(comp.CloseSound, uid);
return true;
}
}

View File

@@ -29,6 +29,7 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
namespace Content.Server.Parallax;

View File

@@ -8,13 +8,14 @@ namespace Content.Server.Physics.Components;
/// <summary>
/// A component which makes its entity chasing entity with selected component.
/// </summary>
[RegisterComponent, Access(typeof(ChasingWalkSystem))]
[RegisterComponent, Access(typeof(ChasingWalkSystem)), AutoGenerateComponentPause]
public sealed partial class ChasingWalkComponent : Component
{
/// <summary>
/// The next moment in time when the entity is pushed toward its goal
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextImpulseTime;
/// <summary>
@@ -57,6 +58,7 @@ public sealed partial class ChasingWalkComponent : Component
/// The next change of direction time.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextChangeVectorTime;
/// <summary>

View File

@@ -27,7 +27,6 @@ public sealed class ChasingWalkSystem : VirtualController
base.Initialize();
SubscribeLocalEvent<ChasingWalkComponent, MapInitEvent>(OnChasingMapInit);
SubscribeLocalEvent<ChasingWalkComponent, EntityUnpausedEvent>(OnChasingUnpaused);
}
private void OnChasingMapInit(EntityUid uid, ChasingWalkComponent component, MapInitEvent args)
@@ -36,12 +35,6 @@ public sealed class ChasingWalkSystem : VirtualController
component.NextChangeVectorTime = _gameTiming.CurTime;
}
private void OnChasingUnpaused(EntityUid uid, ChasingWalkComponent component, ref EntityUnpausedEvent args)
{
component.NextImpulseTime += args.PausedTime;
component.NextChangeVectorTime += args.PausedTime;
}
public override void UpdateBeforeSolve(bool prediction, float frameTime)
{
base.UpdateBeforeSolve(prediction, frameTime);

View File

@@ -39,11 +39,6 @@ public sealed partial class PowerCellSystem
}
}
private void OnUnpaused(EntityUid uid, PowerCellDrawComponent component, ref EntityUnpausedEvent args)
{
component.NextUpdateTime += args.PausedTime;
}
private void OnDrawChargeChanged(EntityUid uid, PowerCellDrawComponent component, ref ChargeChangedEvent args)
{
// Update the bools for client prediction.

View File

@@ -35,7 +35,6 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(OnCellEmpAttempt);
SubscribeLocalEvent<PowerCellDrawComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<PowerCellDrawComponent, ChargeChangedEvent>(OnDrawChargeChanged);
SubscribeLocalEvent<PowerCellDrawComponent, PowerCellChangedEvent>(OnDrawCellChanged);

View File

@@ -6,7 +6,7 @@ namespace Content.Server.PowerSink
/// <summary>
/// Absorbs power up to its capacity when anchored then explodes.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class PowerSinkComponent : Component
{
/// <summary>
@@ -21,6 +21,7 @@ namespace Content.Server.PowerSink
/// If explosion has been triggered, time at which to explode.
/// </summary>
[DataField("explosionTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public System.TimeSpan? ExplosionTime = null;
/// <summary>

View File

@@ -37,7 +37,6 @@ namespace Content.Server.PowerSink
base.Initialize();
SubscribeLocalEvent<PowerSinkComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<PowerSinkComponent, EntityUnpausedEvent>(OnUnpaused);
}
private void OnExamine(EntityUid uid, PowerSinkComponent component, ExaminedEvent args)
@@ -54,14 +53,6 @@ namespace Content.Server.PowerSink
);
}
private void OnUnpaused(EntityUid uid, PowerSinkComponent component, ref EntityUnpausedEvent args)
{
if (component.ExplosionTime == null)
return;
component.ExplosionTime = component.ExplosionTime + args.PausedTime;
}
public override void Update(float frameTime)
{
var toRemove = new RemQueue<(EntityUid Entity, PowerSinkComponent Sink)>();

View File

@@ -107,7 +107,7 @@ namespace Content.Server.Preferences.Managers
var sponsorPrototypes = _sponsors != null && _sponsors.TryGetPrototypes(message.MsgChannel.UserId, out var prototypes)
? prototypes.ToArray()
: new string[]{};
profile.EnsureValid(sponsorPrototypes);
profile.EnsureValid(_cfg, _protos, sponsorPrototypes);
// Corvax-Sponsors-End
var profiles = new Dictionary<int, ICharacterProfile>(curPrefs.Characters)
{
@@ -295,34 +295,7 @@ namespace Content.Server.Preferences.Managers
return new PlayerPreferences(prefs.Characters.Select(p =>
{
ICharacterProfile newProf;
switch (p.Value)
{
case HumanoidCharacterProfile hp:
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
var selectedSpecies = HumanoidAppearanceSystem.DefaultSpecies;
if (prototypeManager.TryIndex<SpeciesPrototype>(hp.Species, out var species) && species.RoundStart)
{
selectedSpecies = hp.Species;
}
newProf = hp
.WithJobPriorities(
hp.JobPriorities.Where(job =>
_protos.HasIndex<JobPrototype>(job.Key)))
.WithAntagPreferences(
hp.AntagPreferences.Where(antag =>
_protos.HasIndex<AntagPrototype>(antag)))
.WithSpecies(selectedSpecies);
break;
}
default:
throw new NotSupportedException();
}
return new KeyValuePair<int, ICharacterProfile>(p.Key, newProf);
return new KeyValuePair<int, ICharacterProfile>(p.Key, p.Value.Validated(_cfg, _protos));
}), prefs.SelectedCharacterIndex, prefs.AdminOOCColor);
}

View File

@@ -18,7 +18,6 @@ namespace Content.Server.Remotes
public sealed class DoorRemoteSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly DoorBoltSystem _bolts = default!;
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly DoorSystem _doorSystem = default!;
@@ -105,7 +104,7 @@ namespace Content.Server.Remotes
{
if (!boltsComp.BoltWireCut)
{
_bolts.SetBoltsWithAudio(args.Target.Value, boltsComp, !boltsComp.BoltsDown);
_doorSystem.SetBoltsDown((args.Target.Value, boltsComp), !boltsComp.BoltsDown, args.User);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} used {ToPrettyString(args.Used)} on {ToPrettyString(args.Target.Value)} to {(boltsComp.BoltsDown ? "" : "un")}bolt it");
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Server.RequiresGrid;
/// <summary>
/// Destroys an entity when they no longer are part of a Grid
/// </summary>
[RegisterComponent]
[Access(typeof(RequiresGridSystem))]
public sealed partial class RequiresGridComponent : Component
{
}

View File

@@ -0,0 +1,29 @@
using Content.Server.Destructible;
namespace Content.Server.RequiresGrid;
public sealed class RequiresGridSystem : EntitySystem
{
[Dependency] private readonly DestructibleSystem _destructible = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RequiresGridComponent, EntParentChangedMessage>(OnEntParentChanged);
}
private void OnEntParentChanged(EntityUid owner, RequiresGridComponent component, EntParentChangedMessage args)
{
if (args.OldParent == null)
return;
if (args.Transform.GridUid != null)
return;
if (TerminatingOrDeleted(owner))
return;
_destructible.DestroyEntity(owner);
}
}

View File

@@ -11,7 +11,7 @@ namespace Content.Server.Salvage.Expeditions;
/// <summary>
/// Designates this entity as holding a salvage expedition.
/// </summary>
[RegisterComponent]
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class SalvageExpeditionComponent : SharedSalvageExpeditionComponent
{
public SalvageMissionParams MissionParams = default!;
@@ -26,6 +26,7 @@ public sealed partial class SalvageExpeditionComponent : SharedSalvageExpedition
/// When the expeditions ends.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan EndTime;
/// <summary>

View File

@@ -31,10 +31,7 @@ public sealed partial class SalvageSystem
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, EntParentChangedMessage>(OnSalvageConsoleParent);
SubscribeLocalEvent<SalvageExpeditionConsoleComponent, ClaimSalvageMessage>(OnSalvageClaimMessage);
SubscribeLocalEvent<SalvageExpeditionDataComponent, EntityUnpausedEvent>(OnDataUnpaused);
SubscribeLocalEvent<SalvageExpeditionComponent, ComponentShutdown>(OnExpeditionShutdown);
SubscribeLocalEvent<SalvageExpeditionComponent, EntityUnpausedEvent>(OnExpeditionUnpaused);
SubscribeLocalEvent<SalvageExpeditionComponent, ComponentGetState>(OnExpeditionGetState);
SubscribeLocalEvent<SalvageStructureComponent, ExaminedEvent>(OnStructureExamine);
@@ -89,16 +86,6 @@ public sealed partial class SalvageSystem
}
}
private void OnDataUnpaused(EntityUid uid, SalvageExpeditionDataComponent component, ref EntityUnpausedEvent args)
{
component.NextOffer += args.PausedTime;
}
private void OnExpeditionUnpaused(EntityUid uid, SalvageExpeditionComponent component, ref EntityUnpausedEvent args)
{
component.EndTime += args.PausedTime;
}
private void UpdateExpeditions()
{
var currentTime = _timing.CurTime;

View File

@@ -3,13 +3,14 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Shuttles.Components;
[RegisterComponent, Access(typeof(ArrivalsSystem))]
[RegisterComponent, Access(typeof(ArrivalsSystem)), AutoGenerateComponentPause]
public sealed partial class ArrivalsShuttleComponent : Component
{
[DataField("station")]
public EntityUid Station;
[DataField("nextTransfer", customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan NextTransfer;
[DataField("nextArrivalsTime", customTypeSerializer: typeof(TimeOffsetSerializer))]

View File

@@ -6,9 +6,10 @@ namespace Content.Server.Shuttles.Components;
/// <summary>
/// If added to a grid gets launched when the emergency shuttle launches.
/// </summary>
[RegisterComponent, Access(typeof(EmergencyShuttleSystem))]
[RegisterComponent, Access(typeof(EmergencyShuttleSystem)), AutoGenerateComponentPause]
public sealed partial class EscapePodComponent : Component
{
[DataField("launchTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
[AutoPausedField]
public TimeSpan? LaunchTime;
}

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