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

# Conflicts:
#	Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs
#	Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml
#	Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs
#	Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs
#	Content.Server/VoiceMask/VoiceMaskSystem.cs
#	Content.Server/VoiceMask/VoiceMaskerComponent.cs
#	Content.Shared/VoiceMask/SharedVoiceMaskSystem.cs
#	Resources/Prototypes/Catalog/Fills/Lockers/security.yml
#	Resources/Textures/Interface/Alerts/essence_counter.rsi/essence0.png
#	Resources/Textures/Interface/Alerts/essence_counter.rsi/essence16.png
This commit is contained in:
Morb0
2024-03-29 11:43:30 +03:00
362 changed files with 3589 additions and 2787 deletions

View File

@@ -4,7 +4,6 @@ using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.Alerts;
@@ -13,7 +12,6 @@ public sealed class ClientAlertsSystem : AlertsSystem
{
public AlertOrderPrototype? AlertOrder { get; set; }
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;

View File

@@ -0,0 +1,21 @@
using Content.Shared.Alert;
using Robust.Client.GameObjects;
namespace Content.Client.Alerts;
/// <summary>
/// Event raised on an entity with alerts in order to allow it to update visuals for the alert sprite entity.
/// </summary>
[ByRefEvent]
public record struct UpdateAlertSpriteEvent
{
public Entity<SpriteComponent> SpriteViewEnt;
public AlertPrototype Alert;
public UpdateAlertSpriteEvent(Entity<SpriteComponent> spriteViewEnt, AlertPrototype alert)
{
SpriteViewEnt = spriteViewEnt;
Alert = alert;
}
}

View File

@@ -50,7 +50,6 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
private static AudioParams _params = AudioParams.Default
.WithVariation(0.01f)
.WithLoop(true)
.WithAttenuation(Attenuation.LinearDistance)
.WithMaxDistance(7f);
/// <summary>

View File

@@ -23,8 +23,8 @@ public sealed partial class ContentAudioSystem
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f);
private readonly AudioParams _roundEndSoundEffectParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f);
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
private readonly AudioParams _roundEndSoundEffectParams = new(-5f, 1, 0, 0, 0, false, 0f);
/// <summary>
/// EntityUid of lobby restart sound component.

View File

@@ -133,7 +133,7 @@ public sealed class ClientClothingSystem : ClothingSystem
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
if (rsi == null || rsi.Path == null)
if (rsi == null)
return false;
var correctedSlot = slot;

View File

@@ -4,14 +4,12 @@ using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Timing;
namespace Content.Client.Doors;
public sealed class DoorSystem : SharedDoorSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override void Initialize()

View File

@@ -108,7 +108,6 @@ namespace Content.Client.Entry
_prototypeManager.RegisterIgnore("gameMap");
_prototypeManager.RegisterIgnore("gameMapPool");
_prototypeManager.RegisterIgnore("lobbyBackground");
_prototypeManager.RegisterIgnore("advertisementsPack");
_prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("spaceBiome");

View File

@@ -16,8 +16,6 @@ namespace Content.Client.IconSmoothing
[UsedImplicitly]
public sealed partial class IconSmoothSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly Queue<EntityUid> _dirtyEntities = new();
private readonly Queue<EntityUid> _anchorChangedEntities = new();

View File

@@ -32,6 +32,7 @@ namespace Content.Client.Input
common.AddFunction(ContentKeyFunctions.ToggleFullscreen);
common.AddFunction(ContentKeyFunctions.MoveStoredItem);
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
common.AddFunction(ContentKeyFunctions.SaveItemLocation);
common.AddFunction(ContentKeyFunctions.Point);
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);

View File

@@ -93,7 +93,7 @@ public sealed class ItemSystem : SharedItemSystem
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
if (rsi == null || rsi.Path == null)
if (rsi == null)
return false;
var state = (item.HeldPrefix == null)

View File

@@ -3,10 +3,12 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc grinder-menu-title}" MinSize="768 256">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" VerticalAlignment="Top" Margin="8">
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 48"/>
<Control MinSize="0 16"/>
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 48"/>
<BoxContainer Orientation="Vertical" VerticalAlignment="Top" Margin="8" MinWidth="100">
<Label Text="{Loc grinder-menu-auto-label}" HorizontalAlignment="Center"/>
<Button Name="AutoModeButton" Text="{Loc grinder-menu-auto-button}" TextAlign="Center" MinSize="64 48" Margin="0 0 0 16" />
<Label Text="{Loc grinder-menu-manual-label}" HorizontalAlignment="Center"/>
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 48" Margin="0 0 0 16" />
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 48" />
</BoxContainer>
<ui:LabelledContentBox Name="ChamberContentBox" LabelText="{Loc grinder-menu-chamber-content-box-label}" ButtonText="{Loc grinder-menu-chamber-content-box-button}" VerticalExpand="True" HorizontalExpand="True" Margin="8" SizeFlagsStretchRatio="2"/>

View File

@@ -24,6 +24,7 @@ namespace Content.Client.Kitchen.UI
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_owner = owner;
AutoModeButton.OnPressed += owner.ToggleAutoMode;
GrindButton.OnPressed += owner.StartGrinding;
JuiceButton.OnPressed += owner.StartJuicing;
ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
@@ -56,6 +57,19 @@ namespace Content.Client.Kitchen.UI
GrindButton.Disabled = !state.CanGrind || !state.Powered;
JuiceButton.Disabled = !state.CanJuice || !state.Powered;
switch (state.AutoMode)
{
case GrinderAutoMode.Grind:
AutoModeButton.Text = Loc.GetString("grinder-menu-grind-button");
break;
case GrinderAutoMode.Juice:
AutoModeButton.Text = Loc.GetString("grinder-menu-juice-button");
break;
default:
AutoModeButton.Text = Loc.GetString("grinder-menu-auto-button-off");
break;
}
// TODO move this to a component state and ensure the net ids.
RefreshContentsDisplay(state.ReagentQuantities, _entityManager.GetEntityArray(state.ChamberContents), state.HasBeakerIn);
}

View File

@@ -52,6 +52,11 @@ namespace Content.Client.Kitchen.UI
_menu?.HandleMessage(message);
}
public void ToggleAutoMode(BaseButton.ButtonEventArgs args)
{
SendMessage(new ReagentGrinderToggleAutoModeMessage());
}
public void StartGrinding(BaseButton.ButtonEventArgs? _ = null)
{
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));

View File

@@ -33,7 +33,7 @@ namespace Content.Client.LateJoin
private readonly SpriteSystem _sprites;
private readonly CrewManifestSystem _crewManifest;
private readonly Dictionary<NetEntity, Dictionary<string, JobButton>> _jobButtons = new();
private readonly Dictionary<NetEntity, Dictionary<string, List<JobButton>>> _jobButtons = new();
private readonly Dictionary<NetEntity, Dictionary<string, BoxContainer>> _jobCategories = new();
private readonly List<ScrollContainer> _jobLists = new();
@@ -139,7 +139,7 @@ namespace Content.Client.LateJoin
var jobListScroll = new ScrollContainer()
{
VerticalExpand = true,
Children = {jobList},
Children = { jobList },
Visible = false,
};
@@ -163,11 +163,12 @@ namespace Content.Client.LateJoin
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
_jobButtons[id] = new Dictionary<string, List<JobButton>>();
foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary<string, BoxContainer>();
_jobButtons[id] = new Dictionary<string, JobButton>();
var stationAvailable = _gameTicker.JobsAvailable[id];
var jobsAvailable = new List<JobPrototype>();
@@ -223,7 +224,13 @@ namespace Content.Client.LateJoin
foreach (var prototype in jobsAvailable)
{
var value = stationAvailable[prototype.ID];
var jobButton = new JobButton(prototype.ID, value);
var jobLabel = new Label
{
Margin = new Thickness(5f, 0, 0, 0)
};
var jobButton = new JobButton(jobLabel, prototype.ID, prototype.LocalizedName, value);
var jobSelector = new BoxContainer
{
@@ -241,14 +248,6 @@ namespace Content.Client.LateJoin
icon.Texture = _sprites.Frame0(jobIcon.Icon);
jobSelector.AddChild(icon);
var jobLabel = new Label
{
Margin = new Thickness(5f, 0, 0, 0),
Text = value != null ?
Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.LocalizedName), ("amount", value)) :
Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.LocalizedName)),
};
jobSelector.AddChild(jobLabel);
jobButton.AddChild(jobSelector);
category.AddChild(jobButton);
@@ -280,15 +279,43 @@ namespace Content.Client.LateJoin
jobButton.Disabled = true;
}
_jobButtons[id][prototype.ID] = jobButton;
if (!_jobButtons[id].ContainsKey(prototype.ID))
{
_jobButtons[id][prototype.ID] = new List<JobButton>();
}
_jobButtons[id][prototype.ID].Add(jobButton);
}
}
}
}
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> _)
private void JobsAvailableUpdated(IReadOnlyDictionary<NetEntity, Dictionary<string, uint?>> updatedJobs)
{
RebuildUI();
foreach (var stationEntries in updatedJobs)
{
if (_jobButtons.ContainsKey(stationEntries.Key))
{
var jobsAvailable = stationEntries.Value;
var existingJobEntries = _jobButtons[stationEntries.Key];
foreach (var existingJobEntry in existingJobEntries)
{
if (jobsAvailable.ContainsKey(existingJobEntry.Key))
{
var updatedJobValue = jobsAvailable[existingJobEntry.Key];
foreach (var matchingJobButton in existingJobEntry.Value)
{
if (matchingJobButton.Amount != updatedJobValue)
{
matchingJobButton.RefreshLabel(updatedJobValue);
matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
}
}
}
}
}
}
}
protected override void Dispose(bool disposing)
@@ -307,14 +334,33 @@ namespace Content.Client.LateJoin
sealed class JobButton : ContainerButton
{
public Label JobLabel { get; }
public string JobId { get; }
public uint? Amount { get; }
public string JobLocalisedName { get; }
public uint? Amount { get; private set; }
private bool _initialised = false;
public JobButton(string jobId, uint? amount)
public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
{
JobLabel = jobLabel;
JobId = jobId;
Amount = amount;
JobLocalisedName = jobLocalisedName;
RefreshLabel(amount);
AddStyleClass(StyleClassButton);
_initialised = true;
}
public void RefreshLabel(uint? amount)
{
if (Amount == amount && _initialised)
{
return;
}
Amount = amount;
JobLabel.Text = Amount != null ?
Loc.GetString("late-join-gui-job-slot-capped", ("jobName", JobLocalisedName), ("amount", Amount)) :
Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", JobLocalisedName));
}
}
}

View File

@@ -289,7 +289,6 @@ namespace Content.Client.NPC
var invGridMatrix = gridXform.InvWorldMatrix;
DebugPathPoly? nearest = null;
var nearestDistance = float.MaxValue;
foreach (var poly in tile)
{

View File

@@ -183,6 +183,7 @@ namespace Content.Client.Options.UI.Tabs
AddButton(ContentKeyFunctions.SwapHands);
AddButton(ContentKeyFunctions.MoveStoredItem);
AddButton(ContentKeyFunctions.RotateStoredItem);
AddButton(ContentKeyFunctions.SaveItemLocation);
AddHeader("ui-options-header-interaction-adv");
AddButton(ContentKeyFunctions.SmartEquipBackpack);

View File

@@ -1,3 +1,5 @@
using Content.Client.Alerts;
using Content.Shared.Alert;
using Content.Shared.Revenant;
using Content.Shared.Revenant.Components;
using Robust.Client.GameObjects;
@@ -13,6 +15,7 @@ public sealed class RevenantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RevenantComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<RevenantComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
}
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
@@ -36,4 +39,16 @@ public sealed class RevenantSystem : EntitySystem
args.Sprite.LayerSetState(0, component.State);
}
}
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
{
if (args.Alert.AlertType != AlertType.Essence)
return;
var sprite = args.SpriteViewEnt.Comp;
var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
sprite.LayerSetState(RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
sprite.LayerSetState(RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
sprite.LayerSetState(RevenantVisualLayers.Digit3, $"{essence % 10}");
}
}

View File

@@ -88,7 +88,6 @@ public partial class BaseShuttleControl : MapGridControl
var cornerDistance = MathF.Sqrt(WorldRange * WorldRange + WorldRange * WorldRange);
var origin = ScalePosition(-new Vector2(Offset.X, -Offset.Y));
var distOffset = -24f;
for (var radius = minDistance; radius <= maxDistance; radius *= EquatorialMultiplier)
{

View File

@@ -3,6 +3,8 @@ using Content.Client.Gameplay;
using Content.Client.UserInterface.Systems.Alerts.Widgets;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Shared.Alert;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
@@ -10,6 +12,8 @@ namespace Content.Client.UserInterface.Systems.Alerts;
public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayState>, IOnSystemChanged<ClientAlertsSystem>
{
[Dependency] private readonly IPlayerManager _player = default!;
[UISystemDependency] private readonly ClientAlertsSystem? _alertsSystem = default;
private AlertsUI? UI => UIManager.GetActiveUIWidgetOrNull<AlertsUI>();
@@ -84,4 +88,16 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
SystemOnSyncAlerts(_alertsSystem, alerts);
}
}
public void UpdateAlertSpriteEntity(EntityUid spriteViewEnt, AlertPrototype alert)
{
if (_player.LocalEntity is not { } player)
return;
if (!EntityManager.TryGetComponent<SpriteComponent>(spriteViewEnt, out var sprite))
return;
var ev = new UpdateAlertSpriteEvent((spriteViewEnt, sprite), alert);
EntityManager.EventBus.RaiseLocalEvent(player, ref ev);
}
}

View File

@@ -2,6 +2,8 @@
using Content.Client.Actions.UI;
using Content.Client.Cooldown;
using Content.Shared.Alert;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -33,9 +35,12 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
private short? _severity;
private readonly IGameTiming _gameTiming;
private readonly AnimatedTextureRect _icon;
private readonly IEntityManager _entityManager;
private readonly SpriteView _icon;
private readonly CooldownGraphic _cooldownGraphic;
private EntityUid _spriteViewEntity;
/// <summary>
/// Creates an alert control reflecting the indicated alert + state
/// </summary>
@@ -44,19 +49,30 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
public AlertControl(AlertPrototype alert, short? severity)
{
_gameTiming = IoCManager.Resolve<IGameTiming>();
_entityManager = IoCManager.Resolve<IEntityManager>();
TooltipSupplier = SupplyTooltip;
Alert = alert;
_severity = severity;
var specifier = alert.GetIcon(_severity);
_icon = new AnimatedTextureRect
{
DisplayRect = {TextureScale = new Vector2(2, 2)}
};
_icon.SetFromSpriteSpecifier(specifier);
_spriteViewEntity = _entityManager.Spawn(Alert.AlertViewEntity);
if (_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
{
var icon = Alert.GetIcon(_severity);
if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
sprite.LayerSetSprite(layer, icon);
}
_icon = new SpriteView
{
Scale = new Vector2(2, 2)
};
_icon.SetEntity(_spriteViewEntity);
Children.Add(_icon);
_cooldownGraphic = new CooldownGraphic();
_cooldownGraphic = new CooldownGraphic
{
MaxSize = new Vector2(64, 64)
};
Children.Add(_cooldownGraphic);
}
@@ -72,16 +88,22 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
/// </summary>
public void SetSeverity(short? severity)
{
if (_severity != severity)
{
_severity = severity;
_icon.SetFromSpriteSpecifier(Alert.GetIcon(_severity));
}
if (_severity == severity)
return;
_severity = severity;
if (!_entityManager.TryGetComponent<SpriteComponent>(_spriteViewEntity, out var sprite))
return;
var icon = Alert.GetIcon(_severity);
if (sprite.LayerMapTryGet(AlertVisualLayers.Base, out var layer))
sprite.LayerSetSprite(layer, icon);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UserInterfaceManager.GetUIController<AlertsUIController>().UpdateAlertSpriteEntity(_spriteViewEntity, Alert);
if (!Cooldown.HasValue)
{
_cooldownGraphic.Visible = false;
@@ -91,5 +113,17 @@ namespace Content.Client.UserInterface.Systems.Alerts.Controls
_cooldownGraphic.FromTime(Cooldown.Value.Start, Cooldown.Value.End);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_entityManager.DeleteEntity(_spriteViewEntity);
}
}
public enum AlertVisualLayers : byte
{
Base
}
}

View File

@@ -12,6 +12,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Storage.Controls;
@@ -355,6 +356,40 @@ public sealed class StorageContainer : BaseWindow
origin,
currentLocation.Rotation);
foreach (var locations in storageComponent.SavedLocations)
{
if (!_entity.TryGetComponent<MetaDataComponent>(currentEnt, out var meta) || meta.EntityName != locations.Key)
continue;
float spot = 0;
var marked = new List<Control>();
foreach (var location in locations.Value)
{
var shape = itemSystem.GetAdjustedItemShape(currentEnt, location);
var bound = shape.GetBoundingBox();
var spotFree = storageSystem.ItemFitsInGridLocation(currentEnt, StorageEntity.Value, location);
if (spotFree)
spot++;
for (var y = bound.Bottom; y <= bound.Top; y++)
{
for (var x = bound.Left; x <= bound.Right; x++)
{
if (TryGetBackgroundCell(x, y, out var cell) && shape.Contains(x, y) && !marked.Contains(cell))
{
marked.Add(cell);
cell.ModulateSelfOverride = spotFree
? Color.FromHsv((0.18f, 1 / spot, 0.5f / spot + 0.5f, 1f))
: Color.FromHex("#2222CC");
}
}
}
}
}
var validColor = usingInHand ? Color.Goldenrod : Color.FromHex("#1E8000");
for (var y = itemBounding.Bottom; y <= itemBounding.Top; y++)

View File

@@ -240,6 +240,16 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
args.Handle();
}
else if (args.Function == ContentKeyFunctions.SaveItemLocation)
{
if (_container?.StorageEntity is not {} storage)
return;
_entity.RaisePredictiveEvent(new StorageSaveItemLocationEvent(
_entity.GetNetEntity(control.Entity),
_entity.GetNetEntity(storage)));
args.Handle();
}
else if (args.Function == ContentKeyFunctions.ExamineEntity)
{
_entity.System<ExamineSystem>().DoExamine(control.Entity);

View File

@@ -1,10 +1,13 @@
using Content.Shared.VoiceMask;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.VoiceMask;
public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[ViewVariables]
private VoiceMaskNameChangeWindow? _window;
@@ -16,11 +19,12 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
{
base.Open();
_window = new();
_window = new(_proto);
_window.OpenCentered();
_window.OnNameChange += OnNameSelected;
_window.OnVoiceChange += (value) => SendMessage(new VoiceMaskChangeVoiceMessage(value)); // Corvax-TTS
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
_window.OnVoiceChange += voice => SendMessage(new VoiceMaskChangeVoiceMessage(voice)); // Corvax-TTS
_window.OnClose += Close;
}
@@ -36,7 +40,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
return;
}
_window.UpdateState(cast.Name, cast.Voice); // Corvax-TTS
_window.UpdateState(cast.Name, cast.Voice, cast.Verb); // Corvax-TTS
}
protected override void Dispose(bool disposing)

View File

@@ -1,17 +1,22 @@
<DefaultWindow xmlns="https://spacestation14.io"
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'voice-mask-name-change-window'}"
MinSize="5 20">
<BoxContainer Orientation="Vertical">
MinSize="5 30">
<BoxContainer Orientation="Vertical" Margin="5">
<Label Text="{Loc 'voice-mask-name-change-info'}" />
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Horizontal" Margin="5">
<LineEdit Name="NameSelector" HorizontalExpand="True" />
<Button Name="NameSelectorSet" Text="{Loc 'voice-mask-name-change-set'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5">
<Label Text="{Loc 'voice-mask-name-change-speech-style'}" />
<OptionButton Name="SpeechVerbSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Corvax-TTS-Start -->
<Label Text="{Loc 'voice-mask-voice-change-info'}" />
<BoxContainer Orientation="Horizontal">
<OptionButton Name="VoiceSelector" HorizontalExpand="True" />
<BoxContainer Orientation="Horizontal" Margin="5">
<Label Text="{Loc 'voice-mask-voice-change-info'}" />
<OptionButton Name="VoiceSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Corvax-TTS-End -->
</BoxContainer>
</DefaultWindow>
</controls:FancyWindow>

View File

@@ -1,5 +1,7 @@
using System.Linq;
using Content.Shared.Corvax.TTS;
using Content.Client.UserInterface.Controls;
using Content.Shared.Speech;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -8,30 +10,80 @@ using Robust.Shared.Prototypes;
namespace Content.Client.VoiceMask;
[GenerateTypedNameReferences]
public sealed partial class VoiceMaskNameChangeWindow : DefaultWindow
public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
{
private readonly List<TTSVoicePrototype> _voices; // Corvax-TTS
private List<TTSVoicePrototype> _voices; // Corvax-TTS
public Action<string>? OnNameChange;
public Action<string?>? OnVerbChange;
public Action<string>? OnVoiceChange; // Corvax-TTS
public VoiceMaskNameChangeWindow()
private List<(string, string)> _verbs = new();
private string? _verb;
public VoiceMaskNameChangeWindow(IPrototypeManager proto)
{
RobustXamlLoader.Load(this);
NameSelectorSet.OnPressed += _ =>
{
OnNameChange!(NameSelector.Text);
OnNameChange?.Invoke(NameSelector.Text);
};
// Corvax-TTS-Start
SpeechVerbSelector.OnItemSelected += args =>
{
OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id));
SpeechVerbSelector.SelectId(args.Id);
};
ReloadVerbs(proto);
ReloadVoices(proto); // Corvax-TTS
AddVerbs();
}
private void ReloadVerbs(IPrototypeManager proto)
{
foreach (var verb in proto.EnumeratePrototypes<SpeechVerbPrototype>())
{
_verbs.Add((Loc.GetString(verb.Name), verb.ID));
}
_verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1));
}
private void AddVerbs()
{
SpeechVerbSelector.Clear();
AddVerb(Loc.GetString("chat-speech-verb-name-none"), null);
foreach (var (name, id) in _verbs)
{
AddVerb(name, id);
}
}
private void AddVerb(string name, string? verb)
{
var id = SpeechVerbSelector.ItemCount;
SpeechVerbSelector.AddItem(name);
if (verb is {} metadata)
SpeechVerbSelector.SetItemMetadata(id, metadata);
if (verb == _verb)
SpeechVerbSelector.SelectId(id);
}
// Corvax-TTS-Start
private void ReloadVoices(IPrototypeManager proto)
{
VoiceSelector.OnItemSelected += args =>
{
VoiceSelector.SelectId(args.Id);
if (VoiceSelector.SelectedMetadata != null)
OnVoiceChange!((string)VoiceSelector.SelectedMetadata);
};
_voices = IoCManager
.Resolve<IPrototypeManager>()
_voices = proto
.EnumeratePrototypes<TTSVoicePrototype>()
.Where(o => o.RoundStart)
.OrderBy(o => Loc.GetString(o.Name))
@@ -42,12 +94,22 @@ public sealed partial class VoiceMaskNameChangeWindow : DefaultWindow
VoiceSelector.AddItem(name);
VoiceSelector.SetItemMetadata(i, _voices[i].ID);
}
// Corvax-TTS-End
}
// Corvax-TTS-End
public void UpdateState(string name, string voice) // Corvax-TTS
public void UpdateState(string name, string voice, string? verb) // Corvax-TTS
{
NameSelector.Text = name;
_verb = verb;
for (int id = 0; id < SpeechVerbSelector.ItemCount; id++)
{
if (string.Equals(verb, SpeechVerbSelector.GetItemMetadata(id)))
{
SpeechVerbSelector.SelectId(id);
break;
}
}
// Corvax-TTS-Start
var voiceIdx = _voices.FindIndex(v => v.ID == voice);

View File

@@ -306,11 +306,6 @@ public static partial class PoolManager
Pairs[fallback!] = true;
}
if (fallback == null && _pairId > 8)
{
var x = 2;
}
return fallback;
}
}

View File

@@ -9,7 +9,6 @@ using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using System.Linq;
using System.Numerics;
@@ -61,12 +60,11 @@ namespace Content.IntegrationTests.Tests.Body
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapLoader = entityManager.System<MapLoaderSystem>();
RespiratorSystem respSys = default;
MetabolizerSystem metaSys = default;
MapId mapId;
EntityUid? grid = null;
BodyComponent body = default;
RespiratorComponent resp = default;
EntityUid human = default;
GridAtmosphereComponent relevantAtmos = default;
var startingMoles = 0.0f;
@@ -99,17 +97,15 @@ namespace Content.IntegrationTests.Tests.Body
await server.WaitAssertion(() =>
{
var coords = new Vector2(0.5f, -1f);
var coordinates = new EntityCoordinates(grid.Value, coords);
var center = new Vector2(0.5f, 0.5f);
var coordinates = new EntityCoordinates(grid.Value, center);
human = entityManager.SpawnEntity("HumanLungDummy", coordinates);
respSys = entityManager.System<RespiratorSystem>();
metaSys = entityManager.System<MetabolizerSystem>();
relevantAtmos = entityManager.GetComponent<GridAtmosphereComponent>(grid.Value);
startingMoles = GetMapMoles();
startingMoles = 100f; // Hardcoded because GetMapMoles returns 900 here for some reason.
#pragma warning disable NUnit2045
Assert.That(entityManager.TryGetComponent(human, out body), Is.True);
Assert.That(entityManager.HasComponent<RespiratorComponent>(human), Is.True);
Assert.That(entityManager.TryGetComponent(human, out resp), Is.True);
#pragma warning restore NUnit2045
});
@@ -118,18 +114,19 @@ namespace Content.IntegrationTests.Tests.Body
var inhaleCycles = 100;
for (var i = 0; i < inhaleCycles; i++)
{
await server.WaitAssertion(() =>
{
// inhale
respSys.Update(2.0f);
Assert.That(GetMapMoles(), Is.LessThan(startingMoles));
// Breathe in
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Exhaling);
Assert.That(
GetMapMoles(), Is.LessThan(startingMoles),
"Did not inhale in any gas"
);
// metabolize + exhale
metaSys.Update(1.0f);
metaSys.Update(1.0f);
respSys.Update(2.0f);
Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002));
});
// Breathe out
await PoolManager.WaitUntil(server, () => resp.Status == RespiratorStatus.Inhaling);
Assert.That(
GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002),
"Did not exhale as much gas as was inhaled"
);
}
await pair.CleanReturnAsync();

View File

@@ -181,9 +181,8 @@ namespace Content.IntegrationTests.Tests.Buckle
#pragma warning restore NUnit2045
// Move away from the chair
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
var oldWorldPosition = xformSystem.GetWorldPosition(chair, xformQuery);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(1000, 1000), xformQuery);
var oldWorldPosition = xformSystem.GetWorldPosition(chair);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(1000, 1000));
// Out of range
#pragma warning disable NUnit2045 // Interdependent asserts.
@@ -193,8 +192,8 @@ namespace Content.IntegrationTests.Tests.Buckle
#pragma warning restore NUnit2045
// Move near the chair
oldWorldPosition = xformSystem.GetWorldPosition(chair, xformQuery);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(0.5f, 0), xformQuery);
oldWorldPosition = xformSystem.GetWorldPosition(chair);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(0.5f, 0));
// In range
#pragma warning disable NUnit2045 // Interdependent asserts.
@@ -220,8 +219,8 @@ namespace Content.IntegrationTests.Tests.Buckle
Assert.That(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));
// Move away from the chair
oldWorldPosition = xformSystem.GetWorldPosition(chair, xformQuery);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(1, 0), xformQuery);
oldWorldPosition = xformSystem.GetWorldPosition(chair);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(1, 0));
});
await server.WaitRunTicks(1);
@@ -371,9 +370,8 @@ namespace Content.IntegrationTests.Tests.Buckle
});
// Move the buckled entity away
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
var oldWorldPosition = xformSystem.GetWorldPosition(chair, xformQuery);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(100, 0), xformQuery);
var oldWorldPosition = xformSystem.GetWorldPosition(chair);
xformSystem.SetWorldPosition(human, oldWorldPosition + new Vector2(100, 0));
});
await PoolManager.WaitUntil(server, () => !buckle.Buckled, 10);
@@ -383,9 +381,8 @@ namespace Content.IntegrationTests.Tests.Buckle
await server.WaitAssertion(() =>
{
// Move the now unbuckled entity back onto the chair
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
var oldWorldPosition = xformSystem.GetWorldPosition(chair, xformQuery);
xformSystem.SetWorldPosition(human, oldWorldPosition, xformQuery);
var oldWorldPosition = xformSystem.GetWorldPosition(chair);
xformSystem.SetWorldPosition(human, oldWorldPosition);
// Buckle
Assert.That(buckleSystem.TryBuckle(human, human, chair, buckleComp: buckle));

View File

@@ -67,7 +67,7 @@ namespace Content.IntegrationTests.Tests.Fluids
await server.WaitAssertion(() =>
{
var coordinates = grid.ToCoordinates();
var coordinates = grid.Owner.ToCoordinates();
var solution = new Solution("Water", FixedPoint2.New(20));
Assert.That(spillSystem.TrySpillAt(coordinates, solution, out _), Is.False);

View File

@@ -58,7 +58,6 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
var cuffableSys = entityManager.System<CuffableSystem>();
var xformSys = entityManager.System<SharedTransformSystem>();
var xformQuery = entityManager.GetEntityQuery<TransformComponent>();
// Spawn the entities
human = entityManager.SpawnEntity("HumanHandcuffDummy", coordinates);
@@ -66,8 +65,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
cuffs = entityManager.SpawnEntity("HandcuffsDummy", coordinates);
secondCuffs = entityManager.SpawnEntity("HandcuffsDummy", coordinates);
var coords = xformSys.GetWorldPosition(otherHuman, xformQuery);
xformSys.SetWorldPosition(human, coords, xformQuery);
var coords = xformSys.GetWorldPosition(otherHuman);
xformSys.SetWorldPosition(human, coords);
// Test for components existing
Assert.Multiple(() =>

View File

@@ -176,16 +176,18 @@ namespace Content.IntegrationTests.Tests.Power
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 1));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates());
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2));
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
@@ -237,16 +239,18 @@ namespace Content.IntegrationTests.Tests.Power
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 1));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates());
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2));
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
@@ -292,16 +296,17 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2));
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
@@ -383,16 +388,17 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", gridOwner.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 2));
netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(generatorEnt);
battery = entityManager.GetComponent<BatteryComponent>(generatorEnt);
@@ -486,17 +492,18 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 1));
var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 1));
var batteryEnt = entityManager.SpawnEntity("DischargingBatteryDummy", gridOwner.ToCoordinates(0, 2));
netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
battery = entityManager.GetComponent<BatteryComponent>(batteryEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
@@ -577,16 +584,17 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates());
var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates());
var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", gridOwner.ToCoordinates(0, 2));
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
var netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
@@ -635,20 +643,21 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 4; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 1));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 3));
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
@@ -712,20 +721,21 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 4; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 1));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 3));
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
@@ -787,6 +797,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Map layout here is
// C - consumer
@@ -800,18 +811,18 @@ namespace Content.IntegrationTests.Tests.Power
for (var i = 0; i < 5; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 2));
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 2));
entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 1));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 3));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 2));
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 0));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 4));
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 1));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 3));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 2));
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 4));
consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
@@ -888,6 +899,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Layout is two generators, two batteries, and one load. As to why two: because previously this test
// would fail ONLY if there were more than two batteries present, because each of them tries to supply
@@ -900,16 +912,16 @@ namespace Content.IntegrationTests.Tests.Power
for (var i = -2; i <= 2; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, -2));
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, -2));
var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 1));
var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, -1));
var supplyEnt1 = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 1));
var supplyEnt2 = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, -1));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier1 = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt1);
@@ -981,6 +993,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Map layout here is
// C - consumer
@@ -994,18 +1007,18 @@ namespace Content.IntegrationTests.Tests.Power
for (var i = 0; i < 5; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 2));
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 2));
entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 2));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 1));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 3));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 2));
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 0));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 4));
var batteryEnt1 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 1));
var batteryEnt2 = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 3));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 2));
var consumerEnt1 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 0));
var consumerEnt2 = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 4));
consumer1 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt1);
consumer2 = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt2);
@@ -1068,20 +1081,21 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 4; i++)
{
grid.SetTile(new Vector2i(0, i), new Tile(1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, i));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, i));
}
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 1));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.ToCoordinates(0, 3));
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", gridOwner.ToCoordinates(0, 3));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
@@ -1153,6 +1167,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 4; i++)
@@ -1160,15 +1175,15 @@ namespace Content.IntegrationTests.Tests.Power
grid.SetTile(new Vector2i(0, i), new Tile(1));
}
var leftEnt = entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 0));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 2));
var rightEnt = entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 3));
var leftEnt = entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 0));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 2));
var rightEnt = entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 3));
var terminal = entityManager.SpawnEntity("CableTerminal", grid.ToCoordinates(0, 1));
var terminal = entityManager.SpawnEntity("CableTerminal", gridOwner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var battery = entityManager.SpawnEntity("FullBatteryDummy", grid.ToCoordinates(0, 2));
var battery = entityManager.SpawnEntity("FullBatteryDummy", gridOwner.ToCoordinates(0, 2));
var batteryNodeContainer = entityManager.GetComponent<NodeContainerComponent>(battery);
if (nodeContainer.TryGetNode<CableNode>(entityManager.GetComponent<NodeContainerComponent>(leftEnt),
@@ -1216,6 +1231,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
// Power only works when anchored
for (var i = 0; i < 3; i++)
@@ -1223,14 +1239,14 @@ namespace Content.IntegrationTests.Tests.Power
grid.SetTile(new Vector2i(0, i), new Tile(1));
}
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 0));
entityManager.SpawnEntity("CableHV", grid.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableMV", grid.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableMV", grid.ToCoordinates(0, 2));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 0));
entityManager.SpawnEntity("CableHV", gridOwner.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableMV", gridOwner.ToCoordinates(0, 1));
entityManager.SpawnEntity("CableMV", gridOwner.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.ToCoordinates(0, 0));
var substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.ToCoordinates(0, 1));
var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", gridOwner.ToCoordinates(0, 0));
var substationEnt = entityManager.SpawnEntity("SubstationDummy", gridOwner.ToCoordinates(0, 1));
var apcEnt = entityManager.SpawnEntity("ApcDummy", gridOwner.ToCoordinates(0, 2));
var generatorSupplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
substationNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(substationEnt);
@@ -1273,6 +1289,7 @@ namespace Content.IntegrationTests.Tests.Power
{
var map = mapManager.CreateMap();
var grid = mapManager.CreateGrid(map);
var gridOwner = grid.Owner;
const int range = 5;
@@ -1282,15 +1299,15 @@ namespace Content.IntegrationTests.Tests.Power
grid.SetTile(new Vector2i(0, i), new Tile(1));
}
var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.ToCoordinates(0, 0));
var apcExtensionEnt = entityManager.SpawnEntity("CableApcExtension", grid.ToCoordinates(0, 0));
var apcEnt = entityManager.SpawnEntity("ApcDummy", gridOwner.ToCoordinates(0, 0));
var apcExtensionEnt = entityManager.SpawnEntity("CableApcExtension", gridOwner.ToCoordinates(0, 0));
// Create a powered receiver in range (range is 0 indexed)
var powerReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.ToCoordinates(0, range - 1));
var powerReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", gridOwner.ToCoordinates(0, range - 1));
receiver = entityManager.GetComponent<ApcPowerReceiverComponent>(powerReceiverEnt);
// Create an unpowered receiver outside range
var unpoweredReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", grid.ToCoordinates(0, range));
var unpoweredReceiverEnt = entityManager.SpawnEntity("ApcPowerReceiverDummy", gridOwner.ToCoordinates(0, range));
unpoweredReceiver = entityManager.GetComponent<ApcPowerReceiverComponent>(unpoweredReceiverEnt);
var battery = entityManager.GetComponent<BatteryComponent>(apcEnt);

View File

@@ -59,7 +59,7 @@ public sealed class PrototypeSaveTest
var tileDefinition = tileDefinitionManager["FloorSteel"]; // Wires n such disable ambiance while under the floor
var tile = new Tile(tileDefinition.TileId);
var coordinates = grid.ToCoordinates();
var coordinates = grid.Owner.ToCoordinates();
grid.SetTile(coordinates, tile);
});
@@ -94,7 +94,7 @@ public sealed class PrototypeSaveTest
await server.WaitAssertion(() =>
{
Assert.That(!mapManager.IsMapInitialized(mapId));
var testLocation = grid.ToCoordinates();
var testLocation = grid.Owner.ToCoordinates();
Assert.Multiple(() =>
{

View File

@@ -1,11 +1,6 @@
using Content.Server.GameTicking;
using Content.Server.Ghost.Components;
using Content.Server.Players;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Ghost;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Map;
@@ -17,7 +12,6 @@ namespace Content.Server.Administration.Commands;
public sealed class PersistenceSave : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IEntitySystemManager _system = default!;
[Dependency] private readonly IMapManager _map = default!;

View File

@@ -3,7 +3,6 @@ using Content.Shared.Maps;
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
namespace Content.Server.Administration.Commands;
@@ -11,7 +10,6 @@ namespace Content.Server.Administration.Commands;
public sealed class VariantizeCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
public string Command => "variantize";

View File

@@ -154,7 +154,7 @@ namespace Content.Server.Administration.Managers
plyData.ExplicitlyDeadminned = false;
reg.Data.Active = true;
if (reg.Data.Stealth)
if (!reg.Data.Stealth)
{
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)));
}

View File

@@ -1,42 +0,0 @@
using Content.Server.Advertisements;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Advertise
{
[RegisterComponent, Access(typeof(AdvertiseSystem))]
public sealed partial class AdvertiseComponent : Component
{
/// <summary>
/// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("minWait")]
public int MinimumWait { get; private set; } = 8 * 60;
/// <summary>
/// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
/// to <see cref="MinimumWait"/>
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxWait")]
public int MaximumWait { get; private set; } = 10 * 60;
/// <summary>
/// The identifier for the advertisements pack prototype.
/// </summary>
[DataField("pack", customTypeSerializer:typeof(PrototypeIdSerializer<AdvertisementsPackPrototype>), required: true)]
public string PackPrototypeId { get; private set; } = string.Empty;
/// <summary>
/// The next time an advertisement will be said.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
/// <summary>
/// Whether the entity will say advertisements or not.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled { get; set; } = true;
}
}

View File

@@ -1,154 +0,0 @@
using Content.Server.Advertisements;
using Content.Server.Chat.Systems;
using Content.Server.Power.Components;
using Content.Shared.VendingMachines;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Advertise
{
public sealed class AdvertiseSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ChatSystem _chat = default!;
/// <summary>
/// The maximum amount of time between checking if advertisements should be displayed
/// </summary>
private readonly TimeSpan _maximumNextCheckDuration = TimeSpan.FromSeconds(15);
/// <summary>
/// The next time the game will check if advertisements should be displayed
/// </summary>
private TimeSpan _nextCheckTime = TimeSpan.MaxValue;
public override void Initialize()
{
SubscribeLocalEvent<AdvertiseComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<AdvertiseComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ApcPowerReceiverComponent, AdvertiseEnableChangeAttemptEvent>(OnPowerReceiverEnableChangeAttempt);
SubscribeLocalEvent<VendingMachineComponent, AdvertiseEnableChangeAttemptEvent>(OnVendingEnableChangeAttempt);
// The component inits will lower this.
_nextCheckTime = TimeSpan.MaxValue;
}
private void OnComponentInit(EntityUid uid, AdvertiseComponent advertise, ComponentInit args)
{
RefreshTimer(uid, advertise);
}
private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, ref PowerChangedEvent args)
{
SetEnabled(uid, args.Powered, advertise);
}
public void RefreshTimer(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (!advertise.Enabled)
return;
var minDuration = Math.Max(1, advertise.MinimumWait);
var maxDuration = Math.Max(minDuration, advertise.MaximumWait);
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
var nextTime = _gameTiming.CurTime + waitDuration;
advertise.NextAdvertisementTime = nextTime;
_nextCheckTime = MathHelper.Min(nextTime, _nextCheckTime);
}
public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements))
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Advertisements)), InGameICChatType.Speak, true);
}
public void SayThankYou(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements))
{
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.ThankYous), ("name", Name(uid))), InGameICChatType.Speak, true);
}
}
public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (advertise.Enabled == enable)
return;
var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enable);
RaiseLocalEvent(uid, attemptEvent);
if (attemptEvent.Cancelled)
return;
advertise.Enabled = enable;
RefreshTimer(uid, advertise);
}
private static void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args)
{
if(args.Enabling && !component.Powered)
args.Cancel();
}
private static void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args)
{
if(args.Enabling && component.Broken)
args.Cancel();
}
public override void Update(float frameTime)
{
var curTime = _gameTiming.CurTime;
if (_nextCheckTime > curTime)
return;
_nextCheckTime = curTime + _maximumNextCheckDuration;
var query = EntityQueryEnumerator<AdvertiseComponent>();
while (query.MoveNext(out var uid, out var advert))
{
if (!advert.Enabled)
continue;
// If this isn't advertising yet
if (advert.NextAdvertisementTime > curTime)
{
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
continue;
}
SayAdvertisement(uid, advert);
RefreshTimer(uid, advert);
}
}
}
public sealed class AdvertiseEnableChangeAttemptEvent : CancellableEntityEventArgs
{
public bool Enabling { get; }
public AdvertiseEnableChangeAttemptEvent(bool enabling)
{
Enabling = enabling;
}
}
}

View File

@@ -0,0 +1,45 @@
using Content.Server.Advertise.EntitySystems;
using Content.Shared.Advertise;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Advertise.Components;
/// <summary>
/// Makes this entity periodically advertise by speaking a randomly selected
/// message from a specified MessagePack into local chat.
/// </summary>
[RegisterComponent, Access(typeof(AdvertiseSystem))]
public sealed partial class AdvertiseComponent : Component
{
/// <summary>
/// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
/// </summary>
[DataField]
public int MinimumWait { get; private set; } = 8 * 60;
/// <summary>
/// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
/// to <see cref="MinimumWait"/>
/// </summary>
[DataField]
public int MaximumWait { get; private set; } = 10 * 60;
/// <summary>
/// The identifier for the advertisements pack prototype.
/// </summary>
[DataField(required: true)]
public ProtoId<MessagePackPrototype> Pack { get; private set; }
/// <summary>
/// The next time an advertisement will be said.
/// </summary>
[DataField]
public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
/// <summary>
/// Whether the entity will say advertisements or not.
/// </summary>
[DataField]
public bool Enabled { get; set; } = true;
}

View File

@@ -0,0 +1,36 @@
using Content.Shared.Advertise;
using Robust.Shared.Prototypes;
namespace Content.Server.Advertise.Components;
/// <summary>
/// Causes the entity to speak using the Chat system when its ActivatableUI is closed, optionally
/// requiring that a Flag be set as well.
/// </summary>
[RegisterComponent, Access(typeof(SpeakOnUIClosedSystem))]
public sealed partial class SpeakOnUIClosedComponent : Component
{
/// <summary>
/// The identifier for the message pack prototype containing messages to be spoken by this entity.
/// </summary>
[DataField(required: true)]
public ProtoId<MessagePackPrototype> Pack { get; private set; }
/// <summary>
/// Is this component active? If false, no messages will be spoken.
/// </summary>
[DataField]
public bool Enabled = true;
/// <summary>
/// Should messages be spoken only if the <see cref="Flag"/> is set (true), or every time the UI is closed (false)?
/// </summary>
[DataField]
public bool RequireFlag = true;
/// <summary>
/// State variable only used if <see cref="RequireFlag"/> is true. Set with <see cref="SpeakOnUIClosedSystem.TrySetFlag"/>.
/// </summary>
[DataField]
public bool Flag;
}

View File

@@ -0,0 +1,137 @@
using Content.Server.Advertise.Components;
using Content.Server.Chat.Systems;
using Content.Server.Power.Components;
using Content.Shared.VendingMachines;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Advertise.EntitySystems;
public sealed class AdvertiseSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ChatSystem _chat = default!;
/// <summary>
/// The maximum amount of time between checking if advertisements should be displayed
/// </summary>
private readonly TimeSpan _maximumNextCheckDuration = TimeSpan.FromSeconds(15);
/// <summary>
/// The next time the game will check if advertisements should be displayed
/// </summary>
private TimeSpan _nextCheckTime = TimeSpan.MaxValue;
public override void Initialize()
{
SubscribeLocalEvent<AdvertiseComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AdvertiseComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<ApcPowerReceiverComponent, AdvertiseEnableChangeAttemptEvent>(OnPowerReceiverEnableChangeAttempt);
SubscribeLocalEvent<VendingMachineComponent, AdvertiseEnableChangeAttemptEvent>(OnVendingEnableChangeAttempt);
// The component inits will lower this.
_nextCheckTime = TimeSpan.MaxValue;
}
private void OnMapInit(EntityUid uid, AdvertiseComponent advertise, MapInitEvent args)
{
RefreshTimer(uid, advertise);
}
private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, ref PowerChangedEvent args)
{
SetEnabled(uid, args.Powered, advertise);
}
public void RefreshTimer(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (!advertise.Enabled)
return;
var minDuration = Math.Max(1, advertise.MinimumWait);
var maxDuration = Math.Max(minDuration, advertise.MaximumWait);
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
var nextTime = _gameTiming.CurTime + waitDuration;
advertise.NextAdvertisementTime = nextTime;
_nextCheckTime = MathHelper.Min(nextTime, _nextCheckTime);
}
public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (_prototypeManager.TryIndex(advertise.Pack, out var advertisements))
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true);
}
public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null)
{
if (!Resolve(uid, ref advertise))
return;
if (advertise.Enabled == enable)
return;
var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enable);
RaiseLocalEvent(uid, attemptEvent);
if (attemptEvent.Cancelled)
return;
advertise.Enabled = enable;
RefreshTimer(uid, advertise);
}
private static void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args)
{
if (args.Enabling && !component.Powered)
args.Cancel();
}
private static void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args)
{
if (args.Enabling && component.Broken)
args.Cancel();
}
public override void Update(float frameTime)
{
var curTime = _gameTiming.CurTime;
if (_nextCheckTime > curTime)
return;
_nextCheckTime = curTime + _maximumNextCheckDuration;
var query = EntityQueryEnumerator<AdvertiseComponent>();
while (query.MoveNext(out var uid, out var advert))
{
if (!advert.Enabled)
continue;
// If this isn't advertising yet
if (advert.NextAdvertisementTime > curTime)
{
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
continue;
}
SayAdvertisement(uid, advert);
RefreshTimer(uid, advert);
}
}
}
public sealed class AdvertiseEnableChangeAttemptEvent(bool enabling) : CancellableEntityEventArgs
{
public bool Enabling { get; } = enabling;
}

View File

@@ -0,0 +1,58 @@
using Content.Server.Advertise.Components;
using Content.Server.Chat.Systems;
using Content.Server.UserInterface;
using Content.Shared.Advertise;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Advertise;
public sealed partial class SpeakOnUIClosedSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpeakOnUIClosedComponent, BoundUIClosedEvent>(OnBoundUIClosed);
}
private void OnBoundUIClosed(Entity<SpeakOnUIClosedComponent> entity, ref BoundUIClosedEvent args)
{
if (!TryComp(entity, out ActivatableUIComponent? activatable) || !args.UiKey.Equals(activatable.Key))
return;
if (entity.Comp.RequireFlag && !entity.Comp.Flag)
return;
TrySpeak((entity, entity.Comp));
}
public bool TrySpeak(Entity<SpeakOnUIClosedComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return false;
if (!entity.Comp.Enabled)
return false;
if (!_prototypeManager.TryIndex(entity.Comp.Pack, out MessagePackPrototype? messagePack))
return false;
var message = Loc.GetString(_random.Pick(messagePack.Messages), ("name", Name(entity)));
_chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, true);
entity.Comp.Flag = false;
return true;
}
public bool TrySetFlag(Entity<SpeakOnUIClosedComponent?> entity, bool value = true)
{
if (!Resolve(entity, ref entity.Comp))
return false;
entity.Comp.Flag = value;
return true;
}
}

View File

@@ -1,18 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Server.Advertisements
{
[Serializable, Prototype("advertisementsPack")]
public sealed partial class AdvertisementsPackPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField("advertisements")]
public List<string> Advertisements { get; private set; } = new();
[DataField("thankyous")]
public List<string> ThankYous { get; private set; } = new();
}
}

View File

@@ -103,7 +103,7 @@ public sealed partial class AnomalySystem
var tile = new Vector2i(randomX, randomY);
// no air-blocked areas.
if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile, mapGridComp: gridComp) ||
if (_atmosphere.IsTileSpace(grid, xform.MapUid, tile) ||
_atmosphere.IsTileAirBlocked(grid, tile, mapGridComp: gridComp))
{
continue;

View File

@@ -8,6 +8,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Teleportation.Components;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Random;
namespace Content.Server.Anomaly.Effects;
@@ -35,20 +36,19 @@ public sealed class BluespaceAnomalySystem : EntitySystem
var range = component.MaxShuffleRadius * args.Severity;
var mobs = new HashSet<Entity<MobStateComponent>>();
_lookup.GetEntitiesInRange(xform.Coordinates, range, mobs);
var allEnts = new List<EntityUid>(mobs.Select(m => m.Owner)) { uid };
var coords = new List<Vector2>();
var allEnts = new ValueList<EntityUid>(mobs.Select(m => m.Owner)) { uid };
var coords = new ValueList<Vector2>();
foreach (var ent in allEnts)
{
if (xformQuery.TryGetComponent(ent, out var xf))
coords.Add(xf.MapPosition.Position);
if (xformQuery.TryGetComponent(ent, out var allXform))
coords.Add(_xform.GetWorldPosition(allXform));
}
_random.Shuffle(coords);
for (var i = 0; i < allEnts.Count; i++)
{
_adminLogger.Add(LogType.Teleport, $"{ToPrettyString(allEnts[i])} has been shuffled to {coords[i]} by the {ToPrettyString(uid)} at {xform.Coordinates}");
_xform.SetWorldPosition(allEnts[i], coords[i], xformQuery);
_xform.SetWorldPosition(allEnts[i], coords[i]);
}
}

View File

@@ -2,7 +2,6 @@ using Content.Shared.Anomaly;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects;
using Content.Shared.Anomaly.Effects.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
@@ -12,7 +11,6 @@ namespace Content.Server.Anomaly.Effects;
public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
{
[Dependency] private readonly SharedAnomalySystem _anomaly = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;

View File

@@ -62,7 +62,7 @@ public sealed class GasProducerAnomalySystem : EntitySystem
if (tilerefs.Length == 0)
return;
var mixture = _atmosphere.GetTileMixture((uid, xform), grid, true);
var mixture = _atmosphere.GetTileMixture((uid, xform), true);
if (mixture != null)
{
mixture.AdjustMoles(gas, mols);

View File

@@ -1,5 +1,7 @@
using Content.Server.Power.Components;
using Content.Shared.UserInterface;
using Content.Server.Advertise;
using Content.Server.Advertise.Components;
using Content.Shared.Arcade;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
@@ -9,6 +11,7 @@ namespace Content.Server.Arcade.BlockGame;
public sealed class BlockGameArcadeSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
public override void Initialize()
{
@@ -88,8 +91,6 @@ public sealed class BlockGameArcadeSystem : EntitySystem
component.Spectators.Remove(component.Player);
UpdatePlayerStatus(uid, component.Player, blockGame: component);
}
else
component.Player = null;
UpdatePlayerStatus(uid, temp, blockGame: component);
}
@@ -122,6 +123,9 @@ public sealed class BlockGameArcadeSystem : EntitySystem
return;
}
if (TryComp<SpeakOnUIClosedComponent>(uid, out var speakComponent))
_speakOnUIClosed.TrySetFlag((uid, speakComponent));
component.Game.ProcessInput(msg.PlayerAction);
}
}

View File

@@ -1,5 +1,7 @@
using Content.Server.Power.Components;
using Content.Shared.UserInterface;
using Content.Server.Advertise;
using Content.Server.Advertise.Components;
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
@@ -13,6 +15,7 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
public override void Initialize()
{
@@ -79,6 +82,9 @@ public sealed partial class SpaceVillainArcadeSystem : EntitySystem
case PlayerAction.Heal:
case PlayerAction.Recharge:
component.Game.ExecutePlayerAction(uid, msg.PlayerAction, component);
// Any sort of gameplay action counts
if (TryComp<SpeakOnUIClosedComponent>(uid, out var speakComponent))
_speakOnUIClosed.TrySetFlag((uid, speakComponent));
break;
case PlayerAction.NewGame:
_audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f));

View File

@@ -1,8 +1,6 @@
using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using System.Diagnostics.CodeAnalysis;
@@ -15,7 +13,6 @@ public sealed class AirFilterSystem : EntitySystem
{
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
public override void Initialize()
{

View File

@@ -4,7 +4,6 @@ using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Reactions;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
@@ -49,13 +48,7 @@ public partial class AtmosphereSystem
return GetTileMixture(gridUid, mapUid, position, excite);
}
public bool HasAtmosphere(EntityUid gridUid)
{
var ev = new HasAtmosphereMethodEvent(gridUid);
RaiseLocalEvent(gridUid, ref ev);
return ev.Result;
}
public bool HasAtmosphere(EntityUid gridUid) => _atmosQuery.HasComponent(gridUid);
public bool SetSimulatedGrid(EntityUid gridUid, bool simulated)
{
@@ -91,43 +84,60 @@ public partial class AtmosphereSystem
entity.Comp.InvalidatedCoords.Add(tile);
}
public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false)
public GasMixture?[]? GetTileMixtures(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, List<Vector2i> tiles, bool excite = false)
{
var ev = new GetTileMixturesMethodEvent(gridUid, mapUid, tiles, excite);
GasMixture?[]? mixtures = null;
var handled = false;
// If we've been passed a grid, try to let it handle it.
if (gridUid.HasValue)
if (grid is {} gridEnt && Resolve(gridEnt, ref gridEnt.Comp))
{
DebugTools.Assert(_mapManager.IsGrid(gridUid.Value));
RaiseLocalEvent(gridUid.Value, ref ev, false);
handled = true;
mixtures = new GasMixture?[tiles.Count];
for (var i = 0; i < tiles.Count; i++)
{
var tile = tiles[i];
if (!gridEnt.Comp.Tiles.TryGetValue(tile, out var atmosTile))
{
// need to get map atmosphere
handled = false;
continue;
}
mixtures[i] = atmosTile.Air;
if (excite)
gridEnt.Comp.InvalidatedCoords.Add(tile);
}
}
if (ev.Handled)
return ev.Mixtures;
if (handled)
return mixtures;
// We either don't have a grid, or the event wasn't handled.
// Let the map handle it instead, and also broadcast the event.
if (mapUid.HasValue)
if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp))
{
DebugTools.Assert(_mapManager.IsMap(mapUid.Value));
RaiseLocalEvent(mapUid.Value, ref ev, true);
}
else
RaiseLocalEvent(ref ev);
mixtures ??= new GasMixture?[tiles.Count];
for (var i = 0; i < tiles.Count; i++)
{
mixtures[i] ??= mapEnt.Comp.Mixture;
}
if (ev.Handled)
return ev.Mixtures;
return mixtures;
}
// Default to a space mixture... This is a space game, after all!
ev.Mixtures ??= new GasMixture?[tiles.Count];
mixtures ??= new GasMixture?[tiles.Count];
for (var i = 0; i < tiles.Count; i++)
{
ev.Mixtures[i] ??= GasMixture.SpaceGas;
mixtures[i] ??= GasMixture.SpaceGas;
}
return ev.Mixtures;
return mixtures;
}
public GasMixture? GetTileMixture (Entity<TransformComponent?> entity, MapGridComponent? grid = null, bool excite = false)
public GasMixture? GetTileMixture (Entity<TransformComponent?> entity, bool excite = false)
{
if (!Resolve(entity.Owner, ref entity.Comp))
return null;
@@ -136,32 +146,24 @@ public partial class AtmosphereSystem
return GetTileMixture(entity.Comp.GridUid, entity.Comp.MapUid, indices, excite);
}
public GasMixture? GetTileMixture(EntityUid? gridUid, EntityUid? mapUid, Vector2i gridTile, bool excite = false)
public GasMixture? GetTileMixture(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, Vector2i gridTile, bool excite = false)
{
var ev = new GetTileMixtureMethodEvent(gridUid, mapUid, gridTile, excite);
// If we've been passed a grid, try to let it handle it.
if(gridUid.HasValue)
if (grid is {} gridEnt
&& Resolve(gridEnt, ref gridEnt.Comp, false)
&& gridEnt.Comp.Tiles.TryGetValue(gridTile, out var tile))
{
DebugTools.Assert(_mapManager.IsGrid(gridUid.Value));
RaiseLocalEvent(gridUid.Value, ref ev, false);
if (excite)
gridEnt.Comp.InvalidatedCoords.Add(gridTile);
return tile.Air;
}
if (ev.Handled)
return ev.Mixture;
// We either don't have a grid, or the event wasn't handled.
// Let the map handle it instead, and also broadcast the event.
if(mapUid.HasValue)
{
DebugTools.Assert(_mapManager.IsMap(mapUid.Value));
RaiseLocalEvent(mapUid.Value, ref ev, true);
}
else
RaiseLocalEvent(ref ev);
if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp, false))
return mapEnt.Comp.Mixture;
// Default to a space mixture... This is a space game, after all!
return ev.Mixture ?? GasMixture.SpaceGas;
return GasMixture.SpaceGas;
}
public ReactionResult ReactTile(EntityUid gridId, Vector2i tile)
@@ -176,66 +178,67 @@ public partial class AtmosphereSystem
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
{
if (!Resolve(gridUid, ref mapGridComp))
if (!Resolve(gridUid, ref mapGridComp, false))
return false;
var data = GetAirtightData(gridUid, mapGridComp, tile);
return data.BlockedDirections.IsFlagSet(directions);
}
public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null)
public bool IsTileSpace(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, Vector2i tile)
{
var ev = new IsTileSpaceMethodEvent(gridUid, mapUid, tile, mapGridComp);
if (grid is {} gridEnt && _atmosQuery.Resolve(gridEnt, ref gridEnt.Comp, false)
&& gridEnt.Comp.Tiles.TryGetValue(tile, out var tileAtmos))
{
return tileAtmos.Space;
}
// Try to let the grid (if any) handle it...
if (gridUid.HasValue)
RaiseLocalEvent(gridUid.Value, ref ev, false);
// If we didn't have a grid or the event wasn't handled
// we let the map know, and also broadcast the event while at it!
if (mapUid.HasValue && !ev.Handled)
RaiseLocalEvent(mapUid.Value, ref ev, true);
// We didn't have a map, and the event isn't handled, therefore broadcast the event.
else if (!mapUid.HasValue && !ev.Handled)
RaiseLocalEvent(ref ev);
if (map is {} mapEnt && _mapAtmosQuery.Resolve(mapEnt, ref mapEnt.Comp, false))
return mapEnt.Comp.Space;
// If nothing handled the event, it'll default to true.
// Oh well, this is a space game after all, deal with it!
return ev.Result;
return true;
}
public bool IsTileMixtureProbablySafe(EntityUid? gridUid, EntityUid mapUid, Vector2i tile)
public bool IsTileMixtureProbablySafe(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?> map, Vector2i tile)
{
return IsMixtureProbablySafe(GetTileMixture(gridUid, mapUid, tile));
return IsMixtureProbablySafe(GetTileMixture(grid, map, tile));
}
public float GetTileHeatCapacity(EntityUid? gridUid, EntityUid mapUid, Vector2i tile)
public float GetTileHeatCapacity(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?> map, Vector2i tile)
{
return GetHeatCapacity(GetTileMixture(gridUid, mapUid, tile) ?? GasMixture.SpaceGas);
return GetHeatCapacity(GetTileMixture(grid, map, tile) ?? GasMixture.SpaceGas);
}
public IEnumerable<Vector2i> GetAdjacentTiles(EntityUid gridUid, Vector2i tile)
public TileMixtureEnumerator GetAdjacentTileMixtures(Entity<GridAtmosphereComponent?> grid, Vector2i tile, bool includeBlocked = false, bool excite = false)
{
var ev = new GetAdjacentTilesMethodEvent(gridUid, tile);
RaiseLocalEvent(gridUid, ref ev);
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
return TileMixtureEnumerator.Empty;
return ev.Result ?? Enumerable.Empty<Vector2i>();
return !grid.Comp.Tiles.TryGetValue(tile, out var atmosTile)
? TileMixtureEnumerator.Empty
: new(atmosTile.AdjacentTiles);
}
public IEnumerable<GasMixture> GetAdjacentTileMixtures(EntityUid gridUid, Vector2i tile, bool includeBlocked = false, bool excite = false)
{
var ev = new GetAdjacentTileMixturesMethodEvent(gridUid, tile, includeBlocked, excite);
RaiseLocalEvent(gridUid, ref ev);
return ev.Result ?? Enumerable.Empty<GasMixture>();
}
public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume,
public void HotspotExpose(Entity<GridAtmosphereComponent?> grid, Vector2i tile, float exposedTemperature, float exposedVolume,
EntityUid? sparkSourceUid = null, bool soh = false)
{
var ev = new HotspotExposeMethodEvent(gridUid, sparkSourceUid, tile, exposedTemperature, exposedVolume, soh);
RaiseLocalEvent(gridUid, ref ev);
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
return;
if (grid.Comp.Tiles.TryGetValue(tile, out var atmosTile))
HotspotExpose(grid.Comp, atmosTile, exposedTemperature, exposedVolume, soh, sparkSourceUid);
}
public void HotspotExpose(TileAtmosphere tile, float exposedTemperature, float exposedVolume,
EntityUid? sparkSourceUid = null, bool soh = false)
{
if (!_atmosQuery.TryGetComponent(tile.GridIndex, out var atmos))
return;
DebugTools.Assert(atmos.Tiles.TryGetValue(tile.GridIndices, out var tmp) && tmp == tile);
HotspotExpose(atmos, tile, exposedTemperature, exposedVolume, soh, sparkSourceUid);
}
public void HotspotExtinguish(EntityUid gridUid, Vector2i tile)
@@ -253,39 +256,45 @@ public partial class AtmosphereSystem
return ev.Result;
}
public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet)
public bool AddPipeNet(Entity<GridAtmosphereComponent?> grid, PipeNet pipeNet)
{
var ev = new AddPipeNetMethodEvent(gridUid, pipeNet);
RaiseLocalEvent(gridUid, ref ev);
return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Add(pipeNet);
}
public void RemovePipeNet(EntityUid gridUid, PipeNet pipeNet)
public bool RemovePipeNet(Entity<GridAtmosphereComponent?> grid, PipeNet pipeNet)
{
var ev = new RemovePipeNetMethodEvent(gridUid, pipeNet);
RaiseLocalEvent(gridUid, ref ev);
return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Remove(pipeNet);
}
public bool AddAtmosDevice(EntityUid gridUid, AtmosDeviceComponent device)
public bool AddAtmosDevice(Entity<GridAtmosphereComponent?> grid, Entity<AtmosDeviceComponent> device)
{
// TODO: check device is on grid
DebugTools.Assert(device.Comp.JoinedGrid == null);
DebugTools.Assert(Transform(device).GridUid == grid);
var ev = new AddAtmosDeviceMethodEvent(gridUid, device);
RaiseLocalEvent(gridUid, ref ev);
return ev.Result;
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
return false;
if (!grid.Comp.AtmosDevices.Add(device))
return false;
device.Comp.JoinedGrid = grid;
return true;
}
public bool RemoveAtmosDevice(EntityUid gridUid, AtmosDeviceComponent device)
public bool RemoveAtmosDevice(Entity<GridAtmosphereComponent?> grid, Entity<AtmosDeviceComponent> device)
{
// TODO: check device is on grid
DebugTools.Assert(device.Comp.JoinedGrid == grid);
var ev = new RemoveAtmosDeviceMethodEvent(gridUid, device);
RaiseLocalEvent(gridUid, ref ev);
return ev.Result;
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
return false;
if (!grid.Comp.AtmosDevices.Remove(device))
return false;
device.Comp.JoinedGrid = null;
return true;
}
[ByRefEvent] private record struct HasAtmosphereMethodEvent
(EntityUid Grid, bool Result = false, bool Handled = false);
[ByRefEvent] private record struct SetSimulatedGridMethodEvent
(EntityUid Grid, bool Simulated, bool Handled = false);
@@ -295,43 +304,12 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct GetAllMixturesMethodEvent
(EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
[ByRefEvent] private record struct GetTileMixturesMethodEvent
(EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false);
[ByRefEvent] private record struct GetTileMixtureMethodEvent
(EntityUid? GridUid, EntityUid? MapUid, Vector2i Tile, bool Excite = false, GasMixture? Mixture = null, bool Handled = false);
[ByRefEvent] private record struct ReactTileMethodEvent
(EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
[ByRefEvent] private record struct IsTileSpaceMethodEvent
(EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false);
[ByRefEvent] private record struct GetAdjacentTilesMethodEvent
(EntityUid Grid, Vector2i Tile, IEnumerable<Vector2i>? Result = null, bool Handled = false);
[ByRefEvent] private record struct GetAdjacentTileMixturesMethodEvent
(EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite,
IEnumerable<GasMixture>? Result = null, bool Handled = false);
[ByRefEvent] private record struct HotspotExposeMethodEvent
(EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false);
[ByRefEvent] private record struct HotspotExtinguishMethodEvent
(EntityUid Grid, Vector2i Tile, bool Handled = false);
[ByRefEvent] private record struct IsHotspotActiveMethodEvent
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
[ByRefEvent] private record struct AddPipeNetMethodEvent
(EntityUid Grid, PipeNet PipeNet, bool Handled = false);
[ByRefEvent] private record struct RemovePipeNetMethodEvent
(EntityUid Grid, PipeNet PipeNet, bool Handled = false);
[ByRefEvent] private record struct AddAtmosDeviceMethodEvent
(EntityUid Grid, AtmosDeviceComponent Device, bool Result = false, bool Handled = false);
[ByRefEvent] private record struct RemoveAtmosDeviceMethodEvent
(EntityUid Grid, AtmosDeviceComponent Device, bool Result = false, bool Handled = false);
}

View File

@@ -98,7 +98,7 @@ public sealed partial class AtmosphereSystem
continue;
}
if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices, gridComp))
if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices))
{
tile = new GasMixture(tile.Volume) { Temperature = tile.Temperature };
tileMain.Air = tile;

View File

@@ -20,22 +20,11 @@ public sealed partial class AtmosphereSystem
#region Atmos API Subscriptions
SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere);
SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice);
SubscribeLocalEvent<GridAtmosphereComponent, RemoveAtmosDeviceMethodEvent>(GridRemoveAtmosDevice);
#endregion
}
@@ -120,15 +109,6 @@ public sealed partial class AtmosphereSystem
}
}
private void GridHasAtmosphere(EntityUid uid, GridAtmosphereComponent component, ref HasAtmosphereMethodEvent args)
{
if (args.Handled)
return;
args.Result = true;
args.Handled = true;
}
private void GridIsSimulated(EntityUid uid, GridAtmosphereComponent component, ref IsSimulatedGridMethodEvent args)
{
if (args.Handled)
@@ -167,48 +147,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component,
ref GetTileMixtureMethodEvent args)
{
if (args.Handled)
return;
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return; // Do NOT handle the event if we don't have that tile, the map will handle it instead.
if (args.Excite)
component.InvalidatedCoords.Add(args.Tile);
args.Mixture = tile.Air;
args.Handled = true;
}
private void GridGetTileMixtures(EntityUid uid, GridAtmosphereComponent component,
ref GetTileMixturesMethodEvent args)
{
if (args.Handled)
return;
args.Handled = true;
args.Mixtures = new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++)
{
var tile = args.Tiles[i];
if (!component.Tiles.TryGetValue(tile, out var atmosTile))
{
// need to get map atmosphere
args.Handled = false;
continue;
}
if (args.Excite)
component.InvalidatedCoords.Add(tile);
args.Mixtures[i] = atmosTile.Air;
}
}
private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref ReactTileMethodEvent args)
{
if (args.Handled)
@@ -221,67 +159,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
{
if (args.Handled)
return;
// We don't have that tile, so let the map handle it.
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return;
args.Result = tile.Space;
args.Handled = true;
}
private void GridGetAdjacentTiles(EntityUid uid, GridAtmosphereComponent component,
ref GetAdjacentTilesMethodEvent args)
{
if (args.Handled)
return;
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return;
IEnumerable<Vector2i> EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmosphere t)
{
foreach (var adj in t.AdjacentTiles)
{
if (adj == null)
continue;
yield return adj.GridIndices;
}
}
args.Result = EnumerateAdjacent(component, tile);
args.Handled = true;
}
private void GridGetAdjacentTileMixtures(EntityUid uid, GridAtmosphereComponent component,
ref GetAdjacentTileMixturesMethodEvent args)
{
if (args.Handled)
return;
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return;
IEnumerable<GasMixture> EnumerateAdjacent(GridAtmosphereComponent grid, TileAtmosphere t)
{
foreach (var adj in t.AdjacentTiles)
{
if (adj?.Air == null)
continue;
yield return adj.Air;
}
}
args.Result = EnumerateAdjacent(component, tile);
args.Handled = true;
}
/// <summary>
/// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
/// </summary>
@@ -357,18 +234,6 @@ public sealed partial class AtmosphereSystem
return (air, map.Space);
}
private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args)
{
if (args.Handled)
return;
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return;
HotspotExpose(component, tile, args.ExposedTemperature, args.ExposedVolume, args.soh, args.SparkSourceUid);
args.Handled = true;
}
private void GridHotspotExtinguish(EntityUid uid, GridAtmosphereComponent component,
ref HotspotExtinguishMethodEvent args)
{
@@ -445,49 +310,6 @@ public sealed partial class AtmosphereSystem
tile.Air.Temperature = totalTemperature / count;
}
private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
{
if (args.Handled)
return;
args.Handled = component.PipeNets.Add(args.PipeNet);
}
private void GridRemovePipeNet(EntityUid uid, GridAtmosphereComponent component, ref RemovePipeNetMethodEvent args)
{
if (args.Handled)
return;
args.Handled = component.PipeNets.Remove(args.PipeNet);
}
private void GridAddAtmosDevice(Entity<GridAtmosphereComponent> grid, ref AddAtmosDeviceMethodEvent args)
{
if (args.Handled)
return;
if (!grid.Comp.AtmosDevices.Add((args.Device.Owner, args.Device)))
return;
args.Device.JoinedGrid = grid;
args.Handled = true;
args.Result = true;
}
private void GridRemoveAtmosDevice(EntityUid uid, GridAtmosphereComponent component,
ref RemoveAtmosDeviceMethodEvent args)
{
if (args.Handled)
return;
if (!component.AtmosDevices.Remove((args.Device.Owner, args.Device)))
return;
args.Device.JoinedGrid = null;
args.Handled = true;
args.Result = true;
}
/// <summary>
/// Repopulates all tiles on a grid atmosphere.
/// </summary>

View File

@@ -12,9 +12,6 @@ public partial class AtmosphereSystem
{
SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
}
@@ -31,37 +28,6 @@ public partial class AtmosphereSystem
RefreshAllGridMapAtmospheres(uid);
}
private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
{
if (args.Handled)
return;
args.Result = component.Space;
args.Handled = true;
}
private void MapGetTileMixture(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixtureMethodEvent args)
{
if (args.Handled)
return;
args.Mixture = component.Mixture;
args.Handled = true;
}
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
{
if (args.Handled)
return;
args.Handled = true;
args.Mixtures ??= new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++)
{
args.Mixtures[i] ??= component.Mixture;
}
}
private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
{
args.State = new MapAtmosphereComponentState(component.Overlay);

View File

@@ -42,6 +42,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
private float _exposedTimer = 0f;
private EntityQuery<GridAtmosphereComponent> _atmosQuery;
private EntityQuery<MapAtmosphereComponent> _mapAtmosQuery;
private EntityQuery<AirtightComponent> _airtightQuery;
private EntityQuery<FirelockComponent> _firelockQuery;
private HashSet<EntityUid> _entSet = new();
@@ -59,6 +60,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
InitializeGridAtmosphere();
InitializeMap();
_mapAtmosQuery = GetEntityQuery<MapAtmosphereComponent>();
_atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
_airtightQuery = GetEntityQuery<AirtightComponent>();
_firelockQuery = GetEntityQuery<FirelockComponent>();

View File

@@ -1,4 +1,3 @@
using System.Numerics;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
@@ -17,8 +16,6 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
namespace Content.Server.Atmos.EntitySystems
@@ -33,7 +30,6 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly SharedContainerSystem _containers = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;

View File

@@ -27,7 +27,6 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
public sealed class GasVolumePumpSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;

View File

@@ -28,7 +28,7 @@ public sealed partial class AtmosDeviceComponent : Component
/// <summary>
/// If non-null, the grid that this device is part of.
/// </summary>
[DataField]
[ViewVariables]
public EntityUid? JoinedGrid = null;
/// <summary>

View File

@@ -1,7 +1,9 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Piping.Components;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.Piping.EntitySystems
{
@@ -32,6 +34,14 @@ namespace Content.Server.Atmos.Piping.EntitySystems
public void JoinAtmosphere(Entity<AtmosDeviceComponent> ent)
{
if (ent.Comp.JoinedGrid != null)
{
DebugTools.Assert(HasComp<GridAtmosphereComponent>(ent.Comp.JoinedGrid));
DebugTools.Assert(Transform(ent).GridUid == ent.Comp.JoinedGrid);
DebugTools.Assert(ent.Comp.RequireAnchored == Transform(ent).Anchored);
return;
}
var component = ent.Comp;
var transform = Transform(ent);
@@ -39,7 +49,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
return;
// Attempt to add device to a grid atmosphere.
bool onGrid = (transform.GridUid != null) && _atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, component);
bool onGrid = (transform.GridUid != null) && _atmosphereSystem.AddAtmosDevice(transform.GridUid!.Value, ent);
if (!onGrid && component.JoinSystem)
{
@@ -55,7 +65,7 @@ namespace Content.Server.Atmos.Piping.EntitySystems
{
var component = ent.Comp;
// Try to remove the component from an atmosphere, and if not
if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component.JoinedGrid.Value, component))
if (component.JoinedGrid != null && !_atmosphereSystem.RemoveAtmosDevice(component.JoinedGrid.Value, ent))
{
// The grid might have been removed but not us... This usually shouldn't happen.
component.JoinedGrid = null;

View File

@@ -13,7 +13,6 @@ using Content.Shared.Atmos;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Lock;
using Robust.Server.GameObjects;
@@ -29,8 +28,6 @@ public sealed class GasCanisterSystem : EntitySystem
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;

View File

@@ -77,7 +77,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
return;
// Scrub adjacent tiles too.
foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true))
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true);
while (enumerator.MoveNext(out var adjacent))
{
Scrub(timeDelta, scrubber, adjacent, outlet);
}

View File

@@ -86,7 +86,8 @@ namespace Content.Server.Atmos.Portable
if (!running)
return;
// widenet
foreach (var adjacent in _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true))
var enumerator = _atmosphereSystem.GetAdjacentTileMixtures(xform.GridUid.Value, position, false, true);
while (enumerator.MoveNext(out var adjacent))
{
Scrub(timeDelta, component, adjacent);
}

View File

@@ -75,7 +75,7 @@ namespace Content.Server.Atmos.Reactions
var mixTemperature = mixture.Temperature;
if (mixTemperature > Atmospherics.FireMinimumTemperatureToExist)
{
atmosphereSystem.HotspotExpose(location.GridIndex, location.GridIndices, mixTemperature, mixture.Volume);
atmosphereSystem.HotspotExpose(location, mixTemperature, mixture.Volume);
}
}

View File

@@ -60,7 +60,7 @@ namespace Content.Server.Atmos.Reactions
temperature = mixture.Temperature;
if (temperature > Atmospherics.FireMinimumTemperatureToExist)
{
atmosphereSystem.HotspotExpose(location.GridIndex, location.GridIndices, temperature, mixture.Volume);
atmosphereSystem.HotspotExpose(location, temperature, mixture.Volume);
}
}

View File

@@ -0,0 +1,29 @@
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.Atmos;
public struct TileMixtureEnumerator
{
public readonly TileAtmosphere?[] Tiles;
public int Index = 0;
public static readonly TileMixtureEnumerator Empty = new(Array.Empty<TileAtmosphere>());
internal TileMixtureEnumerator(TileAtmosphere?[] tiles)
{
Tiles = tiles;
}
public bool MoveNext([NotNullWhen(true)] out GasMixture? mix)
{
while (Index < Tiles.Length)
{
mix = Tiles[Index++]?.Air;
if (mix != null)
return true;
}
mix = null;
return false;
}
}

View File

@@ -93,9 +93,8 @@ namespace Content.Server.Bed
if (!this.IsPowered(uid, EntityManager))
return;
var metabolicEvent = new ApplyMetabolicMultiplierEvent
{Uid = args.BuckledEntity, Multiplier = component.Multiplier, Apply = args.Buckling};
RaiseLocalEvent(args.BuckledEntity, metabolicEvent);
var metabolicEvent = new ApplyMetabolicMultiplierEvent(args.BuckledEntity, component.Multiplier, args.Buckling);
RaiseLocalEvent(args.BuckledEntity, ref metabolicEvent);
}
private void OnPowerChanged(EntityUid uid, StasisBedComponent component, ref PowerChangedEvent args)
@@ -121,9 +120,8 @@ namespace Content.Server.Bed
foreach (var buckledEntity in strap.BuckledEntities)
{
var metabolicEvent = new ApplyMetabolicMultiplierEvent
{Uid = buckledEntity, Multiplier = component.Multiplier, Apply = shouldApply};
RaiseLocalEvent(buckledEntity, metabolicEvent);
var metabolicEvent = new ApplyMetabolicMultiplierEvent(buckledEntity, component.Multiplier, shouldApply);
RaiseLocalEvent(buckledEntity, ref metabolicEvent);
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Server.Popups;
using Content.Server.Sound.Components;
using Content.Server.Sound;
using Content.Shared.Sound.Components;
using Content.Shared.Actions;
using Content.Shared.Audio;
using Content.Shared.Bed.Sleep;
@@ -30,6 +31,7 @@ namespace Content.Server.Bed.Sleep
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly EmitSoundSystem _emitSound = default!;
[ValidatePrototypeId<EntityPrototype>] public const string SleepActionId = "ActionSleep";
@@ -71,8 +73,8 @@ namespace Content.Server.Bed.Sleep
{
emitSound.Sound = sleepSound.Snore;
}
emitSound.PlayChance = sleepSound.Chance;
emitSound.RollInterval = sleepSound.Interval;
emitSound.MinInterval = sleepSound.Interval;
emitSound.MaxInterval = sleepSound.MaxInterval;
emitSound.PopUp = sleepSound.PopUp;
}
@@ -128,7 +130,7 @@ namespace Content.Server.Bed.Sleep
return;
}
if (TryComp<SpamEmitSoundComponent>(uid, out var spam))
spam.Enabled = args.NewMobState == MobState.Alive;
_emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive);
}
private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent<AlternativeVerb> args)

View File

@@ -34,7 +34,6 @@ namespace Content.Server.Body.Commands
switch (args.Length)
{
case 0:
{
if (player == null)
{
shell.WriteLine("Only a player can run this command without arguments.");
@@ -50,71 +49,68 @@ namespace Content.Server.Body.Commands
entity = player.AttachedEntity.Value;
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
break;
}
case 1:
{
if (NetEntity.TryParse(args[0], out var uidNet) && _entManager.TryGetEntity(uidNet, out var uid))
{
if (NetEntity.TryParse(args[0], out var uidNet) && _entManager.TryGetEntity(uidNet, out var uid))
{
if (!_entManager.EntityExists(uid))
{
shell.WriteLine($"No entity found with uid {uid}");
return;
}
entity = uid.Value;
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
}
else
{
if (player == null)
{
shell.WriteLine("You must specify an entity to add a hand to when using this command from the server terminal.");
return;
}
if (player.AttachedEntity == null)
{
shell.WriteLine("You don't have an entity to add a hand to.");
return;
}
entity = player.AttachedEntity.Value;
hand = _entManager.SpawnEntity(args[0], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
}
break;
}
case 2:
{
if (!NetEntity.TryParse(args[0], out var netEnt) || !_entManager.TryGetEntity(netEnt, out var uid))
{
shell.WriteLine($"{args[0]} is not a valid entity uid.");
return;
}
if (!_entManager.EntityExists(uid))
{
shell.WriteLine($"No entity found with uid {uid}");
shell.WriteLine($"No entity exists with uid {uid}.");
return;
}
entity = uid.Value;
hand = _entManager.SpawnEntity(DefaultHandPrototype, _entManager.GetComponent<TransformComponent>(entity).Coordinates);
}
else
{
if (player == null)
if (!_protoManager.HasIndex<EntityPrototype>(args[1]))
{
shell.WriteLine("You must specify an entity to add a hand to when using this command from the server terminal.");
shell.WriteLine($"No hand entity exists with id {args[1]}.");
return;
}
if (player.AttachedEntity == null)
{
shell.WriteLine("You don't have an entity to add a hand to.");
return;
}
hand = _entManager.SpawnEntity(args[1], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
entity = player.AttachedEntity.Value;
hand = _entManager.SpawnEntity(args[0], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
break;
}
break;
}
case 2:
{
if (!NetEntity.TryParse(args[0], out var netEnt) || !_entManager.TryGetEntity(netEnt, out var uid))
{
shell.WriteLine($"{args[0]} is not a valid entity uid.");
return;
}
if (!_entManager.EntityExists(uid))
{
shell.WriteLine($"No entity exists with uid {uid}.");
return;
}
entity = uid.Value;
if (!_protoManager.HasIndex<EntityPrototype>(args[1]))
{
shell.WriteLine($"No hand entity exists with id {args[1]}.");
return;
}
hand = _entManager.SpawnEntity(args[1], _entManager.GetComponent<TransformComponent>(entity).Coordinates);
break;
}
default:
{
shell.WriteLine(Help);
return;
}
}
if (!_entManager.TryGetComponent(entity, out BodyComponent? body) || body.RootContainer.ContainedEntity == null)
@@ -139,7 +135,7 @@ namespace Content.Server.Body.Commands
var slotId = part.GetHashCode().ToString();
if (!bodySystem.TryCreatePartSlotAndAttach(attachAt.Id, slotId, hand, BodyPartType.Hand,attachAt.Component, part))
if (!bodySystem.TryCreatePartSlotAndAttach(attachAt.Id, slotId, hand, BodyPartType.Hand, attachAt.Component, part))
{
shell.WriteError($"Couldn't create a slot with id {slotId} on entity {_entManager.ToPrettyString(entity)}");
return;

View File

@@ -103,11 +103,11 @@ namespace Content.Server.Body.Commands
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (body.RootContainer.ContainedEntity != null)
{
bodySystem.AttachPartToRoot(bodyId,partUid.Value, body ,part);
bodySystem.AttachPartToRoot(bodyId, partUid.Value, body, part);
}
else
{
var (rootPartId,rootPart) = bodySystem.GetRootPartOrNull(bodyId, body)!.Value;
var (rootPartId, rootPart) = bodySystem.GetRootPartOrNull(bodyId, body)!.Value;
if (!bodySystem.TryCreatePartSlotAndAttach(rootPartId, slotId, partUid.Value, part.PartType, rootPart, part))
{
shell.WriteError($"Could not create slot {slotId} on entity {_entManager.ToPrettyString(bodyId)}");

View File

@@ -1,11 +1,7 @@
namespace Content.Server.Body.Components;
public sealed class BeingGibbedEvent : EntityEventArgs
{
public readonly HashSet<EntityUid> GibbedParts;
public BeingGibbedEvent(HashSet<EntityUid> gibbedParts)
{
GibbedParts = gibbedParts;
}
}
/// <summary>
/// Raised when a body gets gibbed, before it is deleted.
/// </summary>
[ByRefEvent]
public readonly record struct BeingGibbedEvent(HashSet<EntityUid> GibbedParts);

View File

@@ -1,11 +1,13 @@
using Content.Server.Body.Systems;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Body.Components
{
@@ -16,7 +18,17 @@ namespace Content.Server.Body.Components
public static string DefaultBloodSolutionName = "bloodstream";
public static string DefaultBloodTemporarySolutionName = "bloodstreamTemporary";
public float AccumulatedFrametime = 0.0f;
/// <summary>
/// The next time that blood level will be updated and bloodloss damage dealt.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate;
/// <summary>
/// The interval at which this component updates.
/// </summary>
[DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3);
/// <summary>
/// How much is this entity currently bleeding?
@@ -32,7 +44,7 @@ namespace Content.Server.Body.Components
public float BleedAmount;
/// <summary>
/// How much should bleeding should be reduced every update interval?
/// How much should bleeding be reduced every update interval?
/// </summary>
[DataField]
public float BleedReductionAmount = 0.33f;
@@ -63,18 +75,12 @@ namespace Content.Server.Body.Components
[DataField(required: true)]
public DamageSpecifier BloodlossHealDamage = new();
/// <summary>
/// How frequently should this bloodstream update, in seconds?
/// </summary>
[DataField]
public float UpdateInterval = 3.0f;
// TODO shouldn't be hardcoded, should just use some organ simulation like bone marrow or smth.
/// <summary>
/// How much reagent of blood should be restored each update interval?
/// </summary>
[DataField]
public float BloodRefreshAmount = 1.0f;
public FixedPoint2 BloodRefreshAmount = 1.0f;
/// <summary>
/// How much blood needs to be in the temporary solution in order to create a puddle?
@@ -89,8 +95,8 @@ namespace Content.Server.Body.Components
/// <remarks>
/// For example, piercing damage is increased while poison damage is nullified entirely.
/// </remarks>
[DataField(customTypeSerializer:typeof(PrototypeIdSerializer<DamageModifierSetPrototype>))]
public string DamageBleedModifiers = "BloodlossHuman";
[DataField]
public ProtoId<DamageModifierSetPrototype> DamageBleedModifiers = "BloodlossHuman";
/// <summary>
/// The sound to be played when a weapon instantly deals blood loss damage.
@@ -126,7 +132,7 @@ namespace Content.Server.Body.Components
/// Slime-people might use slime as their blood or something like that.
/// </remarks>
[DataField]
public string BloodReagent = "Blood";
public ProtoId<ReagentPrototype> BloodReagent = "Blood";
/// <summary>Name/Key that <see cref="BloodSolution"/> is indexed by.</summary>
[DataField]
@@ -164,6 +170,6 @@ namespace Content.Server.Body.Components
/// Variable that stores the amount of status time added by having a low blood level.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float StatusTime;
public TimeSpan StatusTime;
}
}

View File

@@ -1,4 +1,3 @@
using System.Threading;
namespace Content.Server.Body.Components
{
/// <summary>
@@ -7,14 +6,17 @@ namespace Content.Server.Body.Components
[RegisterComponent]
public sealed partial class InternalsComponent : Component
{
[ViewVariables] public EntityUid? GasTankEntity { get; set; }
[ViewVariables] public EntityUid? BreathToolEntity { get; set; }
[ViewVariables]
public EntityUid? GasTankEntity;
[ViewVariables]
public EntityUid? BreathToolEntity;
/// <summary>
/// Toggle Internals delay (seconds) when the target is not you.
/// Toggle Internals delay when the target is not you.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("delay")]
public float Delay = 3;
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(3);
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Atmos;
using Content.Server.Atmos;
using Content.Server.Body.Systems;
using Content.Shared.Alert;
using Content.Shared.Atmos;
@@ -11,7 +11,7 @@ public sealed partial class LungComponent : Component
{
[DataField]
[Access(typeof(LungSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
public GasMixture Air { get; set; } = new()
public GasMixture Air = new()
{
Volume = 6,
Temperature = Atmospherics.NormalBodyTemperature

View File

@@ -1,8 +1,8 @@
using Content.Server.Body.Systems;
using Content.Server.Body.Systems;
using Content.Shared.Body.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Body.Components
{
@@ -12,20 +12,24 @@ namespace Content.Server.Body.Components
[RegisterComponent, Access(typeof(MetabolizerSystem))]
public sealed partial class MetabolizerComponent : Component
{
public float AccumulatedFrametime = 0.0f;
/// <summary>
/// The next time that reagents will be metabolized.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate;
/// <summary>
/// How often to metabolize reagents, in seconds.
/// How often to metabolize reagents.
/// </summary>
/// <returns></returns>
[DataField]
public float UpdateFrequency = 1.0f;
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// From which solution will this metabolizer attempt to metabolize chemicals
/// </summary>
[DataField("solution")]
public string SolutionName { get; set; } = BloodstreamComponent.DefaultChemicalsSolutionName;
public string SolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
/// <summary>
/// Does this component use a solution on it's parent entity (the body) or itself
@@ -39,9 +43,9 @@ namespace Content.Server.Body.Components
/// <summary>
/// List of metabolizer types that this organ is. ex. Human, Slime, Felinid, w/e.
/// </summary>
[DataField(customTypeSerializer:typeof(PrototypeIdHashSetSerializer<MetabolizerTypePrototype>))]
[DataField]
[Access(typeof(MetabolizerSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
public HashSet<string>? MetabolizerTypes = null;
public HashSet<ProtoId<MetabolizerTypePrototype>>? MetabolizerTypes = null;
/// <summary>
/// Should this metabolizer remove chemicals that have no metabolisms defined?
@@ -72,8 +76,8 @@ namespace Content.Server.Body.Components
[DataDefinition]
public sealed partial class MetabolismGroupEntry
{
[DataField(required: true, customTypeSerializer:typeof(PrototypeIdSerializer<MetabolismGroupPrototype>))]
public string Id = default!;
[DataField(required: true)]
public ProtoId<MetabolismGroupPrototype> Id = default!;
[DataField("rateModifier")]
public FixedPoint2 MetabolismRateModifier = 1.0;

View File

@@ -1,5 +1,6 @@
using Content.Server.Body.Systems;
using Content.Shared.Damage;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Body.Components
{
@@ -7,36 +8,49 @@ namespace Content.Server.Body.Components
public sealed partial class RespiratorComponent : Component
{
/// <summary>
/// Saturation level. Reduced by CycleDelay each tick.
/// The next time that this body will inhale or exhale.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate;
/// <summary>
/// The interval between updates. Each update is either inhale or exhale,
/// so a full cycle takes twice as long.
/// </summary>
[DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
/// <summary>
/// Saturation level. Reduced by UpdateInterval each tick.
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
/// </summary>
[DataField("saturation")]
[DataField]
public float Saturation = 5.0f;
/// <summary>
/// At what level of saturation will you begin to suffocate?
/// </summary>
[DataField("suffocationThreshold")]
[DataField]
public float SuffocationThreshold;
[DataField("maxSaturation")]
[DataField]
public float MaxSaturation = 5.0f;
[DataField("minSaturation")]
[DataField]
public float MinSaturation = -2.0f;
// TODO HYPEROXIA?
[DataField("damage", required: true)]
[DataField(required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = default!;
[DataField("damageRecovery", required: true)]
[DataField(required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier DamageRecovery = default!;
[DataField("gaspPopupCooldown")]
public TimeSpan GaspPopupCooldown { get; private set; } = TimeSpan.FromSeconds(8);
[DataField]
public TimeSpan GaspPopupCooldown = TimeSpan.FromSeconds(8);
[ViewVariables]
public TimeSpan LastGaspPopupTime;
@@ -55,11 +69,6 @@ namespace Content.Server.Body.Components
[ViewVariables]
public RespiratorStatus Status = RespiratorStatus.Inhaling;
[DataField("cycleDelay")]
public float CycleDelay = 2.0f;
public float AccumulatedFrametime;
}
}

View File

@@ -1,21 +1,26 @@
using Content.Server.Body.Systems;
using Content.Server.Body.Systems;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Whitelist;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Body.Components
{
[RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
public sealed partial class StomachComponent : Component
{
public float AccumulatedFrameTime;
/// <summary>
/// The next time that the stomach will try to digest its contents.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate;
/// <summary>
/// How fast should this component update, in seconds?
/// The interval at which this stomach digests its contents.
/// </summary>
[DataField]
public float UpdateInterval = 1.0f;
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// The solution inside of this stomach this transfers reagents to the body.
@@ -30,11 +35,11 @@ namespace Content.Server.Body.Components
public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
/// <summary>
/// Time in seconds between reagents being ingested and them being
/// Time between reagents being ingested and them being
/// transferred to <see cref="BloodstreamComponent"/>
/// </summary>
[DataField]
public float DigestionDelay = 20;
public TimeSpan DigestionDelay = TimeSpan.FromSeconds(20);
/// <summary>
/// A whitelist for what special-digestible-required foods this stomach is capable of eating.
@@ -54,15 +59,15 @@ namespace Content.Server.Body.Components
public sealed class ReagentDelta
{
public readonly ReagentQuantity ReagentQuantity;
public float Lifetime { get; private set; }
public TimeSpan Lifetime { get; private set; }
public ReagentDelta(ReagentQuantity reagentQuantity)
{
ReagentQuantity = reagentQuantity;
Lifetime = 0.0f;
Lifetime = TimeSpan.Zero;
}
public void Increment(float delta) => Lifetime += delta;
public void Increment(TimeSpan delta) => Lifetime += delta;
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Body.Systems;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Body.Components;
@@ -6,48 +7,58 @@ namespace Content.Server.Body.Components;
[Access(typeof(ThermalRegulatorSystem))]
public sealed partial class ThermalRegulatorComponent : Component
{
/// <summary>
/// The next time that the body will regulate its heat.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan NextUpdate;
/// <summary>
/// The interval at which thermal regulation is processed.
/// </summary>
[DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
/// <summary>
/// Heat generated due to metabolism. It's generated via metabolism
/// </summary>
[DataField("metabolismHeat")]
public float MetabolismHeat { get; private set; }
[DataField]
public float MetabolismHeat;
/// <summary>
/// Heat output via radiation.
/// </summary>
[DataField("radiatedHeat")]
public float RadiatedHeat { get; private set; }
[DataField]
public float RadiatedHeat;
/// <summary>
/// Maximum heat regulated via sweat
/// </summary>
[DataField("sweatHeatRegulation")]
public float SweatHeatRegulation { get; private set; }
[DataField]
public float SweatHeatRegulation;
/// <summary>
/// Maximum heat regulated via shivering
/// </summary>
[DataField("shiveringHeatRegulation")]
public float ShiveringHeatRegulation { get; private set; }
[DataField]
public float ShiveringHeatRegulation;
/// <summary>
/// Amount of heat regulation that represents thermal regulation processes not
/// explicitly coded.
/// </summary>
[DataField("implicitHeatRegulation")]
public float ImplicitHeatRegulation { get; private set; }
[DataField]
public float ImplicitHeatRegulation;
/// <summary>
/// Normal body temperature
/// </summary>
[DataField("normalBodyTemperature")]
public float NormalBodyTemperature { get; private set; }
[DataField]
public float NormalBodyTemperature;
/// <summary>
/// Deviation from normal temperature for body to start thermal regulation
/// </summary>
[DataField("thermalRegulationTemperatureThreshold")]
public float ThermalRegulationTemperatureThreshold { get; private set; }
public float AccumulatedFrametime;
[DataField]
public float ThermalRegulationTemperatureThreshold;
}

View File

@@ -13,7 +13,6 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Drunk;
using Content.Shared.FixedPoint;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Rejuvenate;
@@ -21,11 +20,13 @@ using Content.Shared.Speech.EntitySystems;
using Robust.Server.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Body.Systems;
public sealed class BloodstreamSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly AudioSystem _audio = default!;
@@ -44,6 +45,8 @@ public sealed class BloodstreamSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<BloodstreamComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<BloodstreamComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BloodstreamComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<BloodstreamComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<BloodstreamComponent, HealthBeingExaminedEvent>(OnHealthBeingExamined);
SubscribeLocalEvent<BloodstreamComponent, BeingGibbedEvent>(OnBeingGibbed);
@@ -53,6 +56,16 @@ public sealed class BloodstreamSystem : EntitySystem
SubscribeLocalEvent<BloodstreamComponent, RejuvenateEvent>(OnRejuvenate);
}
private void OnMapInit(Entity<BloodstreamComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
}
private void OnUnpaused(Entity<BloodstreamComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
private void OnReactionAttempt(Entity<BloodstreamComponent> entity, ref ReactionAttemptEvent args)
{
if (args.Cancelled)
@@ -83,7 +96,9 @@ public sealed class BloodstreamSystem : EntitySystem
if (args.Name != entity.Comp.BloodSolutionName
&& args.Name != entity.Comp.ChemicalSolutionName
&& args.Name != entity.Comp.BloodTemporarySolutionName)
{
return;
}
OnReactionAttempt(entity, ref args.Event);
}
@@ -95,12 +110,10 @@ public sealed class BloodstreamSystem : EntitySystem
var query = EntityQueryEnumerator<BloodstreamComponent>();
while (query.MoveNext(out var uid, out var bloodstream))
{
bloodstream.AccumulatedFrametime += frameTime;
if (bloodstream.AccumulatedFrametime < bloodstream.UpdateInterval)
if (_gameTiming.CurTime < bloodstream.NextUpdate)
continue;
bloodstream.AccumulatedFrametime -= bloodstream.UpdateInterval;
bloodstream.NextUpdate += bloodstream.UpdateInterval;
if (!_solutionContainerSystem.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
continue;
@@ -128,13 +141,17 @@ public sealed class BloodstreamSystem : EntitySystem
// bloodloss damage is based on the base value, and modified by how low your blood level is.
var amt = bloodstream.BloodlossDamage / (0.1f + bloodPercentage);
_damageableSystem.TryChangeDamage(uid, amt, false, false);
_damageableSystem.TryChangeDamage(uid, amt,
ignoreResistances: false, interruptsDoAfters: false);
// Apply dizziness as a symptom of bloodloss.
// The effect is applied in a way that it will never be cleared without being healthy.
// Multiplying by 2 is arbitrary but works for this case, it just prevents the time from running out
_drunkSystem.TryApplyDrunkenness(uid, bloodstream.UpdateInterval*2, false);
_stutteringSystem.DoStutter(uid, TimeSpan.FromSeconds(bloodstream.UpdateInterval*2), false);
_drunkSystem.TryApplyDrunkenness(
uid,
(float) bloodstream.UpdateInterval.TotalSeconds * 2,
applySlur: false);
_stutteringSystem.DoStutter(uid, bloodstream.UpdateInterval * 2, refresh: false);
// storing the drunk and stutter time so we can remove it independently from other effects additions
bloodstream.StatusTime += bloodstream.UpdateInterval * 2;
@@ -142,13 +159,16 @@ public sealed class BloodstreamSystem : EntitySystem
else if (!_mobStateSystem.IsDead(uid))
{
// If they're healthy, we'll try and heal some bloodloss instead.
_damageableSystem.TryChangeDamage(uid, bloodstream.BloodlossHealDamage * bloodPercentage, true, false);
_damageableSystem.TryChangeDamage(
uid,
bloodstream.BloodlossHealDamage * bloodPercentage,
ignoreResistances: true, interruptsDoAfters: false);
// Remove the drunk effect when healthy. Should only remove the amount of drunk and stutter added by low blood level
_drunkSystem.TryRemoveDrunkenessTime(uid, bloodstream.StatusTime);
_stutteringSystem.DoRemoveStutterTime(uid, bloodstream.StatusTime);
_drunkSystem.TryRemoveDrunkenessTime(uid, bloodstream.StatusTime.TotalSeconds);
_stutteringSystem.DoRemoveStutterTime(uid, bloodstream.StatusTime.TotalSeconds);
// Reset the drunk and stutter time to zero
bloodstream.StatusTime = 0;
bloodstream.StatusTime = TimeSpan.Zero;
}
}
}
@@ -167,17 +187,15 @@ public sealed class BloodstreamSystem : EntitySystem
bloodSolution.AddReagent(entity.Comp.BloodReagent, entity.Comp.BloodMaxVolume - bloodSolution.Volume);
}
private void OnDamageChanged(EntityUid uid, BloodstreamComponent component, DamageChangedEvent args)
private void OnDamageChanged(Entity<BloodstreamComponent> ent, ref DamageChangedEvent args)
{
if (args.DamageDelta is null)
return;
// definitely don't make them bleed if they got healed
if (!args.DamageIncreased)
if (args.DamageDelta is null || !args.DamageIncreased)
{
return;
}
// TODO probably cache this or something. humans get hurt a lot
if (!_prototypeManager.TryIndex<DamageModifierSetPrototype>(component.DamageBleedModifiers, out var modifiers))
if (!_prototypeManager.TryIndex<DamageModifierSetPrototype>(ent.Comp.DamageBleedModifiers, out var modifiers))
return;
var bloodloss = DamageSpecifier.ApplyModifierSet(args.DamageDelta, modifiers);
@@ -186,10 +204,10 @@ public sealed class BloodstreamSystem : EntitySystem
return;
// Does the calculation of how much bleed rate should be added/removed, then applies it
var oldBleedAmount = component.BleedAmount;
var oldBleedAmount = ent.Comp.BleedAmount;
var total = bloodloss.GetTotal();
var totalFloat = total.Float();
TryModifyBleedAmount(uid, totalFloat, component);
TryModifyBleedAmount(ent, totalFloat, ent);
/// <summary>
/// Critical hit. Causes target to lose blood, using the bleed rate modifier of the weapon, currently divided by 5
@@ -199,8 +217,8 @@ public sealed class BloodstreamSystem : EntitySystem
var prob = Math.Clamp(totalFloat / 25, 0, 1);
if (totalFloat > 0 && _robustRandom.Prob(prob))
{
TryModifyBloodLevel(uid, (-total) / 5, component);
_audio.PlayPvs(component.InstantBloodSound, uid);
TryModifyBloodLevel(ent, (-total) / 5, ent);
_audio.PlayPvs(ent.Comp.InstantBloodSound, ent);
}
// Heat damage will cauterize, causing the bleed rate to be reduced.
@@ -210,53 +228,52 @@ public sealed class BloodstreamSystem : EntitySystem
// because it's burn damage that cauterized their wounds.
// We'll play a special sound and popup for feedback.
_audio.PlayPvs(component.BloodHealedSound, uid);
_popupSystem.PopupEntity(Loc.GetString("bloodstream-component-wounds-cauterized"), uid,
uid, PopupType.Medium);
_audio.PlayPvs(ent.Comp.BloodHealedSound, ent);
_popupSystem.PopupEntity(Loc.GetString("bloodstream-component-wounds-cauterized"), ent,
ent, PopupType.Medium);
}
}
/// <summary>
/// Shows text on health examine, based on bleed rate and blood level.
/// </summary>
private void OnHealthBeingExamined(EntityUid uid, BloodstreamComponent component, HealthBeingExaminedEvent args)
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
{
// Shows profusely bleeding at half the max bleed rate.
if (component.BleedAmount > component.MaxBleedAmount / 2)
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount / 2)
{
args.Message.PushNewline();
args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", Identity.Entity(uid, EntityManager))));
args.Message.AddMarkup(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", ent.Owner)));
}
// Shows bleeding message when bleeding, but less than profusely.
else if (component.BleedAmount > 0)
else if (ent.Comp.BleedAmount > 0)
{
args.Message.PushNewline();
args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", Identity.Entity(uid, EntityManager))));
args.Message.AddMarkup(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
}
// If the mob's blood level is below the damage threshhold, the pale message is added.
if (GetBloodLevelPercentage(uid, component) < component.BloodlossThreshold)
if (GetBloodLevelPercentage(ent, ent) < ent.Comp.BloodlossThreshold)
{
args.Message.PushNewline();
args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", Identity.Entity(uid, EntityManager))));
args.Message.AddMarkup(Loc.GetString("bloodstream-component-looks-pale", ("target", ent.Owner)));
}
}
private void OnBeingGibbed(EntityUid uid, BloodstreamComponent component, BeingGibbedEvent args)
private void OnBeingGibbed(Entity<BloodstreamComponent> ent, ref BeingGibbedEvent args)
{
SpillAllSolutions(uid, component);
SpillAllSolutions(ent, ent);
}
private void OnApplyMetabolicMultiplier(EntityUid uid, BloodstreamComponent component, ApplyMetabolicMultiplierEvent args)
private void OnApplyMetabolicMultiplier(
Entity<BloodstreamComponent> ent,
ref ApplyMetabolicMultiplierEvent args)
{
if (args.Apply)
{
component.UpdateInterval *= args.Multiplier;
ent.Comp.UpdateInterval *= args.Multiplier;
return;
}
component.UpdateInterval /= args.Multiplier;
// Reset the accumulator properly
if (component.AccumulatedFrametime >= component.UpdateInterval)
component.AccumulatedFrametime = component.UpdateInterval;
ent.Comp.UpdateInterval /= args.Multiplier;
}
private void OnRejuvenate(Entity<BloodstreamComponent> entity, ref RejuvenateEvent args)
@@ -275,21 +292,15 @@ public sealed class BloodstreamSystem : EntitySystem
/// </summary>
public bool TryAddToChemicals(EntityUid uid, Solution solution, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
if (!_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution))
return false;
return _solutionContainerSystem.TryAddSolution(component.ChemicalSolution.Value, solution);
return Resolve(uid, ref component, logMissing: false)
&& _solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution)
&& _solutionContainerSystem.TryAddSolution(component.ChemicalSolution.Value, solution);
}
public bool FlushChemicals(EntityUid uid, string excludedReagentID, FixedPoint2 quantity, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
if (!_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution, out var chemSolution))
if (!Resolve(uid, ref component, logMissing: false)
|| !_solutionContainerSystem.ResolveSolution(uid, component.ChemicalSolutionName, ref component.ChemicalSolution, out var chemSolution))
return false;
for (var i = chemSolution.Contents.Count - 1; i >= 0; i--)
@@ -306,11 +317,11 @@ public sealed class BloodstreamSystem : EntitySystem
public float GetBloodLevelPercentage(EntityUid uid, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component))
return 0.0f;
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
if (!Resolve(uid, ref component)
|| !_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
{
return 0.0f;
}
return bloodSolution.FillFraction;
}
@@ -328,11 +339,11 @@ public sealed class BloodstreamSystem : EntitySystem
/// </summary>
public bool TryModifyBloodLevel(EntityUid uid, FixedPoint2 amount, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution))
if (!Resolve(uid, ref component, logMissing: false)
|| !_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution))
{
return false;
}
if (amount >= 0)
return _solutionContainerSystem.TryAddReagent(component.BloodSolution.Value, component.BloodReagent, amount, out _);
@@ -356,9 +367,9 @@ public sealed class BloodstreamSystem : EntitySystem
tempSolution.AddSolution(temp, _prototypeManager);
}
if (_puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, false))
if (_puddleSystem.TrySpillAt(uid, tempSolution, out var puddleUid, sound: false))
{
_forensicsSystem.TransferDna(puddleUid, uid, false);
_forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
}
tempSolution.RemoveAllSolution();
@@ -374,7 +385,7 @@ public sealed class BloodstreamSystem : EntitySystem
/// </summary>
public bool TryModifyBleedAmount(EntityUid uid, float amount, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component, false))
if (!Resolve(uid, ref component, logMissing: false))
return false;
component.BleedAmount += amount;
@@ -424,7 +435,7 @@ public sealed class BloodstreamSystem : EntitySystem
if (_puddleSystem.TrySpillAt(uid, tempSol, out var puddleUid))
{
_forensicsSystem.TransferDna(puddleUid, uid, false);
_forensicsSystem.TransferDna(puddleUid, uid, canDnaBeCleaned: false);
}
}
@@ -433,11 +444,11 @@ public sealed class BloodstreamSystem : EntitySystem
/// </summary>
public void ChangeBloodReagent(EntityUid uid, string reagent, BloodstreamComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return;
if (reagent == component.BloodReagent)
if (!Resolve(uid, ref component, logMissing: false)
|| reagent == component.BloodReagent)
{
return;
}
if (!_solutionContainerSystem.ResolveSolution(uid, component.BloodSolutionName, ref component.BloodSolution, out var bloodSolution))
{

View File

@@ -1,23 +1,17 @@
using Content.Server.Body.Components;
using Content.Server.GameTicking;
using Content.Server.Humanoid;
using Content.Server.Kitchen.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Body.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Kitchen.Components;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Numerics;
using Content.Shared.Gibbing.Components;
using Content.Shared.Movement.Systems;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Body.Systems;
@@ -27,10 +21,7 @@ public sealed class BodySystem : SharedBodySystem
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
@@ -40,7 +31,7 @@ public sealed class BodySystem : SharedBodySystem
SubscribeLocalEvent<BodyComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}
private void OnRelayMoveInput(EntityUid uid, BodyComponent component, ref MoveInputEvent args)
private void OnRelayMoveInput(Entity<BodyComponent> ent, ref MoveInputEvent args)
{
// If they haven't actually moved then ignore it.
if ((args.Component.HeldMoveButtons &
@@ -49,68 +40,67 @@ public sealed class BodySystem : SharedBodySystem
return;
}
if (_mobState.IsDead(uid) && _mindSystem.TryGetMind(uid, out var mindId, out var mind))
if (_mobState.IsDead(ent) && _mindSystem.TryGetMind(ent, out var mindId, out var mind))
{
mind.TimeOfDeath ??= _gameTiming.RealTime;
_ticker.OnGhostAttempt(mindId, true, mind: mind);
_ticker.OnGhostAttempt(mindId, canReturnGlobal: true, mind: mind);
}
}
private void OnApplyMetabolicMultiplier(EntityUid uid, BodyComponent component,
ApplyMetabolicMultiplierEvent args)
private void OnApplyMetabolicMultiplier(
Entity<BodyComponent> ent,
ref ApplyMetabolicMultiplierEvent args)
{
foreach (var organ in GetBodyOrgans(uid, component))
foreach (var organ in GetBodyOrgans(ent, ent))
{
RaiseLocalEvent(organ.Id, args);
RaiseLocalEvent(organ.Id, ref args);
}
}
protected override void AddPart(
EntityUid bodyUid,
EntityUid partUid,
string slotId,
BodyPartComponent component,
BodyComponent? bodyComp = null)
Entity<BodyComponent?> bodyEnt,
Entity<BodyPartComponent> partEnt,
string slotId)
{
// TODO: Predict this probably.
base.AddPart(bodyUid, partUid, slotId, component, bodyComp);
base.AddPart(bodyEnt, partEnt, slotId);
if (TryComp<HumanoidAppearanceComponent>(bodyUid, out var humanoid))
if (TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
{
var layer = component.ToHumanoidLayers();
var layer = partEnt.Comp.ToHumanoidLayers();
if (layer != null)
{
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
_humanoidSystem.SetLayersVisibility(bodyUid, layers, true, true, humanoid);
_humanoidSystem.SetLayersVisibility(
bodyEnt, layers, visible: true, permanent: true, humanoid);
}
}
}
protected override void RemovePart(
EntityUid bodyUid,
EntityUid partUid,
string slotId,
BodyPartComponent component,
BodyComponent? bodyComp = null)
Entity<BodyComponent?> bodyEnt,
Entity<BodyPartComponent> partEnt,
string slotId)
{
base.RemovePart(bodyUid, partUid, slotId, component, bodyComp);
base.RemovePart(bodyEnt, partEnt, slotId);
if (!TryComp<HumanoidAppearanceComponent>(bodyUid, out var humanoid))
if (!TryComp<HumanoidAppearanceComponent>(bodyEnt, out var humanoid))
return;
var layer = component.ToHumanoidLayers();
var layer = partEnt.Comp.ToHumanoidLayers();
if (layer == null)
if (layer is null)
return;
var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value);
_humanoidSystem.SetLayersVisibility(bodyUid, layers, false, true, humanoid);
_humanoidSystem.SetLayersVisibility(
bodyEnt, layers, visible: false, permanent: true, humanoid);
}
public override HashSet<EntityUid> GibBody(
EntityUid bodyId,
bool gibOrgans = false,
BodyComponent? body = null ,
BodyComponent? body = null,
bool launchGibs = true,
Vector2? splatDirection = null,
float splatModifier = 1,
@@ -118,19 +108,23 @@ public sealed class BodySystem : SharedBodySystem
SoundSpecifier? gibSoundOverride = null
)
{
if (!Resolve(bodyId, ref body, false))
return new HashSet<EntityUid>();
if (TerminatingOrDeleted(bodyId) || EntityManager.IsQueuedForDeletion(bodyId))
if (!Resolve(bodyId, ref body, logMissing: false)
|| TerminatingOrDeleted(bodyId)
|| EntityManager.IsQueuedForDeletion(bodyId))
{
return new HashSet<EntityUid>();
}
var xform = Transform(bodyId);
if (xform.MapUid == null)
if (xform.MapUid is null)
return new HashSet<EntityUid>();
var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs,
splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone);
RaiseLocalEvent(bodyId, new BeingGibbedEvent(gibs));
var ev = new BeingGibbedEvent(gibs);
RaiseLocalEvent(bodyId, ref ev);
QueueDel(bodyId);
return gibs;

View File

@@ -16,8 +16,8 @@ namespace Content.Server.Body.Systems
{
base.Initialize();
SubscribeLocalEvent<BrainComponent, AddedToPartInBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
SubscribeLocalEvent<BrainComponent, RemovedFromPartInBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
SubscribeLocalEvent<BrainComponent, OrganAddedToBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
SubscribeLocalEvent<BrainComponent, OrganRemovedFromBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
}
@@ -39,7 +39,7 @@ namespace Content.Server.Body.Systems
_mindSystem.TransferTo(mindId, newEntity, mind: mind);
}
private void OnPointAttempt(EntityUid uid, BrainComponent component, PointAttemptEvent args)
private void OnPointAttempt(Entity<BrainComponent> ent, ref PointAttemptEvent args)
{
args.Cancel();
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Hands.Systems;
using Content.Server.Popups;
using Content.Shared.Alert;
using Content.Shared.Atmos;
@@ -11,19 +10,16 @@ using Content.Shared.Internals;
using Content.Shared.Inventory;
using Content.Shared.Verbs;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Body.Systems;
public sealed class InternalsSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly GasTankSystem _gasTank = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -40,28 +36,36 @@ public sealed class InternalsSystem : EntitySystem
SubscribeLocalEvent<InternalsComponent, InternalsDoAfterEvent>(OnDoAfter);
}
private void OnGetInteractionVerbs(EntityUid uid, InternalsComponent component, GetVerbsEvent<InteractionVerb> args)
private void OnGetInteractionVerbs(
Entity<InternalsComponent> ent,
ref GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
if (!args.CanAccess || !args.CanInteract || args.Hands is null)
return;
var user = args.User;
InteractionVerb verb = new()
{
Act = () =>
{
ToggleInternals(uid, args.User, false, component);
ToggleInternals(ent, user, force: false, ent);
},
Message = Loc.GetString("action-description-internals-toggle"),
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
Text = Loc.GetString("action-name-internals-toggle"),
};
args.Verbs.Add(verb);
}
public void ToggleInternals(EntityUid uid, EntityUid user, bool force, InternalsComponent? internals = null)
public void ToggleInternals(
EntityUid uid,
EntityUid user,
bool force,
InternalsComponent? internals = null)
{
if (!Resolve(uid, ref internals, false))
if (!Resolve(uid, ref internals, logMissing: false))
return;
// Toggle off if they're on
@@ -73,12 +77,12 @@ public sealed class InternalsSystem : EntitySystem
return;
}
StartToggleInternalsDoAfter(user, uid, internals);
StartToggleInternalsDoAfter(user, (uid, internals));
return;
}
// If they're not on then check if we have a mask to use
if (internals.BreathToolEntity == null)
if (internals.BreathToolEntity is null)
{
_popupSystem.PopupEntity(Loc.GetString("internals-no-breath-tool"), uid, user);
return;
@@ -86,7 +90,7 @@ public sealed class InternalsSystem : EntitySystem
var tank = FindBestGasTank(uid);
if (tank == null)
if (tank is null)
{
_popupSystem.PopupEntity(Loc.GetString("internals-no-tank"), uid, user);
return;
@@ -94,20 +98,20 @@ public sealed class InternalsSystem : EntitySystem
if (!force)
{
StartToggleInternalsDoAfter(user, uid, internals);
StartToggleInternalsDoAfter(user, (uid, internals));
return;
}
_gasTank.ConnectToInternals(tank.Value);
}
private void StartToggleInternalsDoAfter(EntityUid user, EntityUid target, InternalsComponent internals)
private void StartToggleInternalsDoAfter(EntityUid user, Entity<InternalsComponent> targetEnt)
{
// Is the target not you? If yes, use a do-after to give them time to respond.
var isUser = user == target;
var delay = !isUser ? internals.Delay : 0f;
var isUser = user == targetEnt.Owner;
var delay = !isUser ? targetEnt.Comp.Delay : TimeSpan.Zero;
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new InternalsDoAfterEvent(), target, target: target)
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, delay, new InternalsDoAfterEvent(), targetEnt, target: targetEnt)
{
BreakOnDamage = true,
BreakOnMove = true,
@@ -115,66 +119,64 @@ public sealed class InternalsSystem : EntitySystem
});
}
private void OnDoAfter(EntityUid uid, InternalsComponent component, InternalsDoAfterEvent args)
private void OnDoAfter(Entity<InternalsComponent> ent, ref InternalsDoAfterEvent args)
{
if (args.Cancelled || args.Handled)
return;
ToggleInternals(uid, args.User, true, component);
ToggleInternals(ent, args.User, force: true, ent);
args.Handled = true;
}
private void OnInternalsStartup(EntityUid uid, InternalsComponent component, ComponentStartup args)
private void OnInternalsStartup(Entity<InternalsComponent> ent, ref ComponentStartup args)
{
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
}
private void OnInternalsShutdown(EntityUid uid, InternalsComponent component, ComponentShutdown args)
private void OnInternalsShutdown(Entity<InternalsComponent> ent, ref ComponentShutdown args)
{
_alerts.ClearAlert(uid, AlertType.Internals);
_alerts.ClearAlert(ent, AlertType.Internals);
}
private void OnInhaleLocation(EntityUid uid, InternalsComponent component, InhaleLocationEvent args)
private void OnInhaleLocation(Entity<InternalsComponent> ent, ref InhaleLocationEvent args)
{
if (AreInternalsWorking(component))
if (AreInternalsWorking(ent))
{
var gasTank = Comp<GasTankComponent>(component.GasTankEntity!.Value);
args.Gas = _gasTank.RemoveAirVolume((component.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume);
var gasTank = Comp<GasTankComponent>(ent.Comp.GasTankEntity!.Value);
args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume);
// TODO: Should listen to gas tank updates instead I guess?
_alerts.ShowAlert(uid, AlertType.Internals, GetSeverity(component));
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
}
}
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
{
var (owner, component) = ent;
var old = component.BreathToolEntity;
component.BreathToolEntity = null;
var old = ent.Comp.BreathToolEntity;
ent.Comp.BreathToolEntity = null;
if (TryComp(old, out BreathToolComponent? breathTool) )
if (TryComp(old, out BreathToolComponent? breathTool))
{
_atmos.DisconnectInternals(breathTool);
DisconnectTank(ent);
}
_alerts.ShowAlert(owner, AlertType.Internals, GetSeverity(component));
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
}
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
{
var (owner, component) = ent;
if (TryComp(component.BreathToolEntity, out BreathToolComponent? tool))
if (TryComp(ent.Comp.BreathToolEntity, out BreathToolComponent? tool))
{
_atmos.DisconnectInternals(tool);
}
component.BreathToolEntity = toolEntity;
_alerts.ShowAlert(owner, AlertType.Internals, GetSeverity(component));
ent.Comp.BreathToolEntity = toolEntity;
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
}
public void DisconnectTank(InternalsComponent? component)
{
if (component == null)
if (component is null)
return;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
@@ -186,46 +188,47 @@ public sealed class InternalsSystem : EntitySystem
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
{
var component = ent.Comp;
if (component.BreathToolEntity == null)
if (ent.Comp.BreathToolEntity is null)
return false;
if (TryComp(component.GasTankEntity, out GasTankComponent? tank))
_gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank));
if (TryComp(ent.Comp.GasTankEntity, out GasTankComponent? tank))
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
component.GasTankEntity = tankEntity;
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(component));
ent.Comp.GasTankEntity = tankEntity;
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
return true;
}
public bool AreInternalsWorking(EntityUid uid, InternalsComponent? component = null)
{
if (!Resolve(uid, ref component, false))
return false;
return AreInternalsWorking(component);
return Resolve(uid, ref component, logMissing: false)
&& AreInternalsWorking(component);
}
public bool AreInternalsWorking(InternalsComponent component)
{
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool) &&
breathTool.IsFunctional &&
TryComp(component.GasTankEntity, out GasTankComponent? _);
return TryComp(component.BreathToolEntity, out BreathToolComponent? breathTool)
&& breathTool.IsFunctional
&& HasComp<GasTankComponent>(component.GasTankEntity);
}
private short GetSeverity(InternalsComponent component)
{
if (component.BreathToolEntity == null || !AreInternalsWorking(component))
if (component.BreathToolEntity is null || !AreInternalsWorking(component))
return 2;
// If pressure in the tank is below low pressure threshhold, flash warning on internals UI
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure)
if (TryComp<GasTankComponent>(component.GasTankEntity, out var gasTank)
&& gasTank.IsLowPressure)
{
return 0;
}
return 1;
}
public Entity<GasTankComponent>? FindBestGasTank(Entity<HandsComponent?, InventoryComponent?, ContainerManagerComponent?> user)
public Entity<GasTankComponent>? FindBestGasTank(
Entity<HandsComponent?, InventoryComponent?, ContainerManagerComponent?> user)
{
// Prioritise
// 1. back equipped tanks
@@ -233,17 +236,17 @@ public sealed class InternalsSystem : EntitySystem
// 3. in-hand tanks
// 4. pocket/belt tanks
if (!Resolve(user.Owner, ref user.Comp1, ref user.Comp2, ref user.Comp3))
if (!Resolve(user, ref user.Comp1, ref user.Comp2, ref user.Comp3))
return null;
if (_inventory.TryGetSlotEntity(user.Owner, "back", out var backEntity, user.Comp2, user.Comp3) &&
if (_inventory.TryGetSlotEntity(user, "back", out var backEntity, user.Comp2, user.Comp3) &&
TryComp<GasTankComponent>(backEntity, out var backGasTank) &&
_gasTank.CanConnectToInternals(backGasTank))
{
return (backEntity.Value, backGasTank);
}
if (_inventory.TryGetSlotEntity(user.Owner, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
if (_inventory.TryGetSlotEntity(user, "suitstorage", out var entity, user.Comp2, user.Comp3) &&
TryComp<GasTankComponent>(entity, out var gasTank) &&
_gasTank.CanConnectToInternals(gasTank))
{

View File

@@ -1,4 +1,4 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
@@ -26,21 +26,24 @@ public sealed class LungSystem : EntitySystem
SubscribeLocalEvent<BreathToolComponent, ItemMaskToggledEvent>(OnMaskToggled);
}
private void OnGotUnequipped(EntityUid uid, BreathToolComponent component, GotUnequippedEvent args)
private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args)
{
_atmosphereSystem.DisconnectInternals(component);
_atmosphereSystem.DisconnectInternals(ent);
}
private void OnGotEquipped(EntityUid uid, BreathToolComponent component, GotEquippedEvent args)
private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent args)
{
if ((args.SlotFlags & ent.Comp.AllowedSlots) == 0)
{
return;
}
if ((args.SlotFlags & component.AllowedSlots) == 0) return;
component.IsFunctional = true;
ent.Comp.IsFunctional = true;
if (TryComp(args.Equipee, out InternalsComponent? internals))
{
component.ConnectedInternalsEntity = args.Equipee;
_internals.ConnectBreathTool((args.Equipee, internals), uid);
ent.Comp.ConnectedInternalsEntity = args.Equipee;
_internals.ConnectBreathTool((args.Equipee, internals), ent);
}
}
@@ -81,7 +84,7 @@ public sealed class LungSystem : EntitySystem
if (moles <= 0)
continue;
var reagent = _atmosphereSystem.GasReagents[i];
if (reagent == null) continue;
if (reagent is null) continue;
var amount = moles * Atmospherics.BreathMolesToReagentMultiplier;
solution.AddReagent(reagent, amount);

View File

@@ -12,11 +12,13 @@ using Content.Shared.Mobs.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Body.Systems
{
public sealed class MetabolizerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
@@ -34,9 +36,21 @@ namespace Content.Server.Body.Systems
_solutionQuery = GetEntityQuery<SolutionContainerManagerComponent>();
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
SubscribeLocalEvent<MetabolizerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MetabolizerComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<MetabolizerComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}
private void OnMapInit(Entity<MetabolizerComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
}
private void OnUnpaused(Entity<MetabolizerComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
private void OnMetabolizerInit(Entity<MetabolizerComponent> entity, ref ComponentInit args)
{
if (!entity.Comp.SolutionOnBody)
@@ -49,19 +63,17 @@ namespace Content.Server.Body.Systems
}
}
private void OnApplyMetabolicMultiplier(EntityUid uid, MetabolizerComponent component,
ApplyMetabolicMultiplierEvent args)
private void OnApplyMetabolicMultiplier(
Entity<MetabolizerComponent> ent,
ref ApplyMetabolicMultiplierEvent args)
{
if (args.Apply)
{
component.UpdateFrequency *= args.Multiplier;
ent.Comp.UpdateInterval *= args.Multiplier;
return;
}
component.UpdateFrequency /= args.Multiplier;
// Reset the accumulator properly
if (component.AccumulatedFrametime >= component.UpdateFrequency)
component.AccumulatedFrametime = component.UpdateFrequency;
ent.Comp.UpdateInterval /= args.Multiplier;
}
public override void Update(float frameTime)
@@ -78,50 +90,52 @@ namespace Content.Server.Body.Systems
foreach (var (uid, metab) in metabolizers)
{
metab.AccumulatedFrametime += frameTime;
// Only update as frequently as it should
if (metab.AccumulatedFrametime < metab.UpdateFrequency)
if (_gameTiming.CurTime < metab.NextUpdate)
continue;
metab.AccumulatedFrametime -= metab.UpdateFrequency;
TryMetabolize(uid, metab);
metab.NextUpdate += metab.UpdateInterval;
TryMetabolize((uid, metab));
}
}
private void TryMetabolize(EntityUid uid, MetabolizerComponent meta, OrganComponent? organ = null)
private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
{
_organQuery.Resolve(uid, ref organ, false);
_organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
// First step is get the solution we actually care about
var solutionName = ent.Comp1.SolutionName;
Solution? solution = null;
Entity<SolutionComponent>? soln = default!;
EntityUid? solutionEntityUid = null;
SolutionContainerManagerComponent? manager = null;
if (meta.SolutionOnBody)
if (ent.Comp1.SolutionOnBody)
{
if (organ?.Body is { } body)
if (ent.Comp2?.Body is { } body)
{
if (!_solutionQuery.Resolve(body, ref manager, false))
if (!_solutionQuery.Resolve(body, ref ent.Comp3, logMissing: false))
return;
_solutionContainerSystem.TryGetSolution((body, manager), meta.SolutionName, out soln, out solution);
_solutionContainerSystem.TryGetSolution((body, ent.Comp3), solutionName, out soln, out solution);
solutionEntityUid = body;
}
}
else
{
if (!_solutionQuery.Resolve(uid, ref manager, false))
if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
return;
_solutionContainerSystem.TryGetSolution((uid, manager), meta.SolutionName, out soln, out solution);
solutionEntityUid = uid;
_solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out soln, out solution);
solutionEntityUid = ent;
}
if (solutionEntityUid == null || soln is null || solution is null || solution.Contents.Count == 0)
if (solutionEntityUid is null
|| soln is null
|| solution is null
|| solution.Contents.Count == 0)
{
return;
}
// randomize the reagent list so we don't have any weird quirks
// like alphabetical order or insertion order mattering for processing
@@ -135,9 +149,9 @@ namespace Content.Server.Body.Systems
continue;
var mostToRemove = FixedPoint2.Zero;
if (proto.Metabolisms == null)
if (proto.Metabolisms is null)
{
if (meta.RemoveEmpty)
if (ent.Comp1.RemoveEmpty)
{
solution.RemoveReagent(reagent, FixedPoint2.New(1));
}
@@ -146,15 +160,15 @@ namespace Content.Server.Body.Systems
}
// we're done here entirely if this is true
if (reagents >= meta.MaxReagentsProcessable)
if (reagents >= ent.Comp1.MaxReagentsProcessable)
return;
// loop over all our groups and see which ones apply
if (meta.MetabolismGroups == null)
if (ent.Comp1.MetabolismGroups is null)
continue;
foreach (var group in meta.MetabolismGroups)
foreach (var group in ent.Comp1.MetabolismGroups)
{
if (!proto.Metabolisms.TryGetValue(group.Id, out var entry))
continue;
@@ -169,14 +183,14 @@ namespace Content.Server.Body.Systems
// if it's possible for them to be dead, and they are,
// then we shouldn't process any effects, but should probably
// still remove reagents
if (EntityManager.TryGetComponent<MobStateComponent>(solutionEntityUid.Value, out var state))
if (TryComp<MobStateComponent>(solutionEntityUid.Value, out var state))
{
if (!proto.WorksOnTheDead && _mobStateSystem.IsDead(solutionEntityUid.Value, state))
continue;
}
var actualEntity = organ?.Body ?? solutionEntityUid.Value;
var args = new ReagentEffectArgs(actualEntity, uid, solution, proto, mostToRemove,
var actualEntity = ent.Comp2?.Body ?? solutionEntityUid.Value;
var args = new ReagentEffectArgs(actualEntity, ent, solution, proto, mostToRemove,
EntityManager, null, scale);
// do all effects, if conditions apply
@@ -187,8 +201,14 @@ namespace Content.Server.Body.Systems
if (effect.ShouldLog)
{
_adminLogger.Add(LogType.ReagentEffect, effect.LogImpact,
$"Metabolism effect {effect.GetType().Name:effect} of reagent {proto.LocalizedName:reagent} applied on entity {actualEntity:entity} at {Transform(actualEntity).Coordinates:coordinates}");
_adminLogger.Add(
LogType.ReagentEffect,
effect.LogImpact,
$"Metabolism effect {effect.GetType().Name:effect}"
+ $" of reagent {proto.LocalizedName:reagent}"
+ $" applied on entity {actualEntity:entity}"
+ $" at {Transform(actualEntity).Coordinates:coordinates}"
);
}
effect.Effect(args);
@@ -209,15 +229,25 @@ namespace Content.Server.Body.Systems
}
}
public sealed class ApplyMetabolicMultiplierEvent : EntityEventArgs
[ByRefEvent]
public readonly record struct ApplyMetabolicMultiplierEvent(
EntityUid Uid,
float Multiplier,
bool Apply)
{
// The entity whose metabolism is being modified
public EntityUid Uid;
/// <summary>
/// The entity whose metabolism is being modified.
/// </summary>
public readonly EntityUid Uid = Uid;
// What the metabolism's update rate will be multiplied by
public float Multiplier;
/// <summary>
/// What the metabolism's update rate will be multiplied by.
/// </summary>
public readonly float Multiplier = Multiplier;
// Apply this multiplier or ignore / reset it?
public bool Apply;
/// <summary>
/// If true, apply the multiplier. If false, revert it.
/// </summary>
public readonly bool Apply = Apply;
}
}

View File

@@ -35,9 +35,21 @@ public sealed class RespiratorSystem : EntitySystem
// We want to process lung reagents before we inhale new reagents.
UpdatesAfter.Add(typeof(MetabolizerSystem));
SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RespiratorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
}
private void OnUnpaused(Entity<RespiratorComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -45,17 +57,15 @@ public sealed class RespiratorSystem : EntitySystem
var query = EntityQueryEnumerator<RespiratorComponent, BodyComponent>();
while (query.MoveNext(out var uid, out var respirator, out var body))
{
if (_gameTiming.CurTime < respirator.NextUpdate)
continue;
respirator.NextUpdate += respirator.UpdateInterval;
if (_mobState.IsDead(uid))
{
continue;
}
respirator.AccumulatedFrametime += frameTime;
if (respirator.AccumulatedFrametime < respirator.CycleDelay)
continue;
respirator.AccumulatedFrametime -= respirator.CycleDelay;
UpdateSaturation(uid, -respirator.CycleDelay, respirator);
UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);
if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
{
@@ -80,30 +90,30 @@ public sealed class RespiratorSystem : EntitySystem
_popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid);
}
TakeSuffocationDamage(uid, respirator);
TakeSuffocationDamage((uid, respirator));
respirator.SuffocationCycles += 1;
continue;
}
StopSuffocation(uid, respirator);
StopSuffocation((uid, respirator));
respirator.SuffocationCycles = 0;
}
}
public void Inhale(EntityUid uid, BodyComponent? body = null)
{
if (!Resolve(uid, ref body, false))
if (!Resolve(uid, ref body, logMissing: false))
return;
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
// Inhale gas
var ev = new InhaleLocationEvent();
RaiseLocalEvent(uid, ev);
RaiseLocalEvent(uid, ref ev, broadcast: false);
ev.Gas ??= _atmosSys.GetContainingMixture(uid, false, true);
ev.Gas ??= _atmosSys.GetContainingMixture(uid, excite: true);
if (ev.Gas == null)
if (ev.Gas is null)
{
return;
}
@@ -122,7 +132,7 @@ public sealed class RespiratorSystem : EntitySystem
public void Exhale(EntityUid uid, BodyComponent? body = null)
{
if (!Resolve(uid, ref body, false))
if (!Resolve(uid, ref body, logMissing: false))
return;
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid, body);
@@ -130,11 +140,11 @@ public sealed class RespiratorSystem : EntitySystem
// exhale gas
var ev = new ExhaleLocationEvent();
RaiseLocalEvent(uid, ev, false);
RaiseLocalEvent(uid, ref ev, broadcast: false);
if (ev.Gas == null)
if (ev.Gas is null)
{
ev.Gas = _atmosSys.GetContainingMixture(uid, false, true);
ev.Gas = _atmosSys.GetContainingMixture(uid, excite: true);
// Walls and grids without atmos comp return null. I guess it makes sense to not be able to exhale in walls,
// but this also means you cannot exhale on some grids.
@@ -154,37 +164,37 @@ public sealed class RespiratorSystem : EntitySystem
_atmosSys.Merge(ev.Gas, outGas);
}
private void TakeSuffocationDamage(EntityUid uid, RespiratorComponent respirator)
private void TakeSuffocationDamage(Entity<RespiratorComponent> ent)
{
if (respirator.SuffocationCycles == 2)
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} started suffocating");
if (ent.Comp.SuffocationCycles == 2)
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} started suffocating");
if (respirator.SuffocationCycles >= respirator.SuffocationCycleThreshold)
if (ent.Comp.SuffocationCycles >= ent.Comp.SuffocationCycleThreshold)
{
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid);
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(ent);
foreach (var (comp, _) in organs)
{
_alertsSystem.ShowAlert(uid, comp.Alert);
_alertsSystem.ShowAlert(ent, comp.Alert);
}
}
_damageableSys.TryChangeDamage(uid, respirator.Damage, false, false);
_damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false);
}
private void StopSuffocation(EntityUid uid, RespiratorComponent respirator)
private void StopSuffocation(Entity<RespiratorComponent> ent)
{
if (respirator.SuffocationCycles >= 2)
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(uid):entity} stopped suffocating");
if (ent.Comp.SuffocationCycles >= 2)
_adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} stopped suffocating");
// TODO: This is not going work with multiple different lungs, if that ever becomes a possibility
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(uid);
var organs = _bodySystem.GetBodyOrganComponents<LungComponent>(ent);
foreach (var (comp, _) in organs)
{
_alertsSystem.ClearAlert(uid, comp.Alert);
_alertsSystem.ClearAlert(ent, comp.Alert);
}
_damageableSys.TryChangeDamage(uid, respirator.DamageRecovery);
_damageableSys.TryChangeDamage(ent, ent.Comp.DamageRecovery);
}
public void UpdateSaturation(EntityUid uid, float amount,
@@ -198,35 +208,29 @@ public sealed class RespiratorSystem : EntitySystem
Math.Clamp(respirator.Saturation, respirator.MinSaturation, respirator.MaxSaturation);
}
private void OnApplyMetabolicMultiplier(EntityUid uid, RespiratorComponent component,
ApplyMetabolicMultiplierEvent args)
private void OnApplyMetabolicMultiplier(
Entity<RespiratorComponent> ent,
ref ApplyMetabolicMultiplierEvent args)
{
if (args.Apply)
{
component.CycleDelay *= args.Multiplier;
component.Saturation *= args.Multiplier;
component.MaxSaturation *= args.Multiplier;
component.MinSaturation *= args.Multiplier;
ent.Comp.UpdateInterval *= args.Multiplier;
ent.Comp.Saturation *= args.Multiplier;
ent.Comp.MaxSaturation *= args.Multiplier;
ent.Comp.MinSaturation *= args.Multiplier;
return;
}
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
component.CycleDelay /= args.Multiplier;
component.Saturation /= args.Multiplier;
component.MaxSaturation /= args.Multiplier;
component.MinSaturation /= args.Multiplier;
// Reset the accumulator properly
if (component.AccumulatedFrametime >= component.CycleDelay)
component.AccumulatedFrametime = component.CycleDelay;
ent.Comp.UpdateInterval /= args.Multiplier;
ent.Comp.Saturation /= args.Multiplier;
ent.Comp.MaxSaturation /= args.Multiplier;
ent.Comp.MinSaturation /= args.Multiplier;
}
}
public sealed class InhaleLocationEvent : EntityEventArgs
{
public GasMixture? Gas;
}
[ByRefEvent]
public record struct InhaleLocationEvent(GasMixture? Gas);
public sealed class ExhaleLocationEvent : EntityEventArgs
{
public GasMixture? Gas;
}
[ByRefEvent]
public record struct ExhaleLocationEvent(GasMixture? Gas);

View File

@@ -3,32 +3,44 @@ using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Body.Organ;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Body.Systems
{
public sealed class StomachSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
public const string DefaultSolutionName = "stomach";
public override void Initialize()
{
SubscribeLocalEvent<StomachComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<StomachComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}
private void OnMapInit(Entity<StomachComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
}
private void OnUnpaused(Entity<StomachComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<StomachComponent, OrganComponent, SolutionContainerManagerComponent>();
while (query.MoveNext(out var uid, out var stomach, out var organ, out var sol))
{
stomach.AccumulatedFrameTime += frameTime;
if (stomach.AccumulatedFrameTime < stomach.UpdateInterval)
if (_gameTiming.CurTime < stomach.NextUpdate)
continue;
stomach.AccumulatedFrameTime -= stomach.UpdateInterval;
stomach.NextUpdate += stomach.UpdateInterval;
// Get our solutions
if (!_solutionContainerSystem.ResolveSolution((uid, sol), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
@@ -70,49 +82,44 @@ namespace Content.Server.Body.Systems
}
}
private void OnApplyMetabolicMultiplier(EntityUid uid, StomachComponent component,
ApplyMetabolicMultiplierEvent args)
private void OnApplyMetabolicMultiplier(
Entity<StomachComponent> ent,
ref ApplyMetabolicMultiplierEvent args)
{
if (args.Apply)
{
component.UpdateInterval *= args.Multiplier;
ent.Comp.UpdateInterval *= args.Multiplier;
return;
}
// This way we don't have to worry about it breaking if the stasis bed component is destroyed
component.UpdateInterval /= args.Multiplier;
// Reset the accumulator properly
if (component.AccumulatedFrameTime >= component.UpdateInterval)
component.AccumulatedFrameTime = component.UpdateInterval;
ent.Comp.UpdateInterval /= args.Multiplier;
}
public bool CanTransferSolution(EntityUid uid, Solution solution,
public bool CanTransferSolution(
EntityUid uid,
Solution solution,
StomachComponent? stomach = null,
SolutionContainerManagerComponent? solutions = null)
{
if (!Resolve(uid, ref stomach, ref solutions, false))
return false;
if (!_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution))
return false;
// TODO: For now no partial transfers. Potentially change by design
if (!stomachSolution.CanAddSolution(solution))
return false;
return true;
return Resolve(uid, ref stomach, ref solutions, logMissing: false)
&& _solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution)
// TODO: For now no partial transfers. Potentially change by design
&& stomachSolution.CanAddSolution(solution);
}
public bool TryTransferSolution(EntityUid uid, Solution solution,
public bool TryTransferSolution(
EntityUid uid,
Solution solution,
StomachComponent? stomach = null,
SolutionContainerManagerComponent? solutions = null)
{
if (!Resolve(uid, ref stomach, ref solutions, false))
return false;
if (!_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution)
if (!Resolve(uid, ref stomach, ref solutions, logMissing: false)
|| !_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution)
|| !CanTransferSolution(uid, solution, stomach, solutions))
{
return false;
}
_solutionContainerSystem.TryAddSolution(stomach.Solution.Value, solution);
// Add each reagent to ReagentDeltas. Used to track how long each reagent has been in the stomach

View File

@@ -1,73 +1,95 @@
using Content.Server.Body.Components;
using Content.Server.Body.Components;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared.ActionBlocker;
using Robust.Shared.Timing;
namespace Content.Server.Body.Systems;
public sealed class ThermalRegulatorSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly TemperatureSystem _tempSys = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSys = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ThermalRegulatorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ThermalRegulatorComponent, EntityUnpausedEvent>(OnUnpaused);
}
private void OnMapInit(Entity<ThermalRegulatorComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
}
private void OnUnpaused(Entity<ThermalRegulatorComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextUpdate += args.PausedTime;
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<ThermalRegulatorComponent>();
while (query.MoveNext(out var uid, out var regulator))
{
regulator.AccumulatedFrametime += frameTime;
if (regulator.AccumulatedFrametime < 1)
if (_gameTiming.CurTime < regulator.NextUpdate)
continue;
regulator.AccumulatedFrametime -= 1;
ProcessThermalRegulation(uid, regulator);
regulator.NextUpdate += regulator.UpdateInterval;
ProcessThermalRegulation((uid, regulator));
}
}
/// <summary>
/// Processes thermal regulation for a mob
/// </summary>
private void ProcessThermalRegulation(EntityUid uid, ThermalRegulatorComponent comp)
private void ProcessThermalRegulation(Entity<ThermalRegulatorComponent, TemperatureComponent?> ent)
{
if (!EntityManager.TryGetComponent(uid, out TemperatureComponent? temperatureComponent)) return;
if (!Resolve(ent, ref ent.Comp2, logMissing: false))
return;
var totalMetabolismTempChange = comp.MetabolismHeat - comp.RadiatedHeat;
var totalMetabolismTempChange = ent.Comp1.MetabolismHeat - ent.Comp1.RadiatedHeat;
// implicit heat regulation
var tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
var heatCapacity = _tempSys.GetHeatCapacity(uid, temperatureComponent);
var tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
var heatCapacity = _tempSys.GetHeatCapacity(ent, ent);
var targetHeat = tempDiff * heatCapacity;
if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature)
if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
{
totalMetabolismTempChange -= Math.Min(targetHeat, comp.ImplicitHeatRegulation);
totalMetabolismTempChange -= Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
}
else
{
totalMetabolismTempChange += Math.Min(targetHeat, comp.ImplicitHeatRegulation);
totalMetabolismTempChange += Math.Min(targetHeat, ent.Comp1.ImplicitHeatRegulation);
}
_tempSys.ChangeHeat(uid, totalMetabolismTempChange, true, temperatureComponent);
_tempSys.ChangeHeat(ent, totalMetabolismTempChange, ignoreHeatResistance: true, ent);
// recalc difference and target heat
tempDiff = Math.Abs(temperatureComponent.CurrentTemperature - comp.NormalBodyTemperature);
tempDiff = Math.Abs(ent.Comp2.CurrentTemperature - ent.Comp1.NormalBodyTemperature);
targetHeat = tempDiff * heatCapacity;
// if body temperature is not within comfortable, thermal regulation
// processes starts
if (tempDiff > comp.ThermalRegulationTemperatureThreshold)
if (tempDiff > ent.Comp1.ThermalRegulationTemperatureThreshold)
return;
if (temperatureComponent.CurrentTemperature > comp.NormalBodyTemperature)
if (ent.Comp2.CurrentTemperature > ent.Comp1.NormalBodyTemperature)
{
if (!_actionBlockerSys.CanSweat(uid)) return;
_tempSys.ChangeHeat(uid, -Math.Min(targetHeat, comp.SweatHeatRegulation), true,
temperatureComponent);
if (!_actionBlockerSys.CanSweat(ent))
return;
_tempSys.ChangeHeat(ent, -Math.Min(targetHeat, ent.Comp1.SweatHeatRegulation), ignoreHeatResistance: true, ent);
}
else
{
if (!_actionBlockerSys.CanShiver(uid)) return;
_tempSys.ChangeHeat(uid, Math.Min(targetHeat, comp.ShiveringHeatRegulation), true,
temperatureComponent);
if (!_actionBlockerSys.CanShiver(ent))
return;
_tempSys.ChangeHeat(ent, Math.Min(targetHeat, ent.Comp1.ShiveringHeatRegulation), ignoreHeatResistance: true, ent);
}
}
}

View File

@@ -1,4 +1,6 @@
namespace Content.Server.Cargo.Components;
using Content.Server.Station.Systems;
namespace Content.Server.Cargo.Components;
/// <summary>
/// This is used for marking containers as
@@ -17,4 +19,10 @@ public sealed partial class CargoBountyLabelComponent : Component
/// Used to prevent recursion in calculating the price.
/// </summary>
public bool Calculating;
/// <summary>
/// The Station System to check and remove bounties from
/// </summary>
[DataField]
public EntityUid? AssociatedStationId;
}

View File

@@ -4,6 +4,7 @@ using Content.Server.Cargo.Components;
using Content.Server.Labels;
using Content.Server.NameIdentifier;
using Content.Server.Paper;
using Content.Server.Station.Systems;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Prototypes;
@@ -65,16 +66,17 @@ public sealed partial class CargoSystem
var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates);
component.NextPrintTime = _timing.CurTime + component.PrintDelay;
SetupBountyLabel(label, bounty.Value);
SetupBountyLabel(label, station, bounty.Value);
_audio.PlayPvs(component.PrintSound, uid);
}
public void SetupBountyLabel(EntityUid uid, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
{
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
return;
label.Id = bounty.Id;
label.AssociatedStationId = stationId;
var msg = new FormattedMessage();
msg.AddText(Loc.GetString("bounty-manifest-header", ("id", bounty.Id)));
msg.PushNewline();
@@ -103,7 +105,7 @@ public sealed partial class CargoSystem
if (!_container.TryGetContainingContainer(uid, out var container) || container.ID != LabelSystem.ContainerName)
return;
if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var database))
if (component.AssociatedStationId is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var database))
return;
if (database.CheckedBounties.Contains(component.Id))
@@ -131,14 +133,18 @@ public sealed partial class CargoSystem
if (!TryGetBountyLabel(sold, out _, out var component))
continue;
if (!TryGetBountyFromId(args.Station, component.Id, out var bounty))
if (component.AssociatedStationId is not { } station || !TryGetBountyFromId(station, component.Id, out var bounty))
{
continue;
}
if (!IsBountyComplete(sold, bounty.Value))
{
continue;
}
TryRemoveBounty(args.Station, bounty.Value);
FillBountyDatabase(args.Station);
TryRemoveBounty(station, bounty.Value);
FillBountyDatabase(station);
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
}
}
@@ -196,7 +202,7 @@ public sealed partial class CargoSystem
FillBountyDatabase(entity);
}
public bool IsBountyComplete(EntityUid container, EntityUid? station, out HashSet<EntityUid> bountyEntities)
public bool IsBountyComplete(EntityUid container, out HashSet<EntityUid> bountyEntities)
{
if (!TryGetBountyLabel(container, out _, out var component))
{
@@ -204,7 +210,7 @@ public sealed partial class CargoSystem
return false;
}
station ??= _station.GetOwningStation(container);
var station = component.AssociatedStationId;
if (station == null)
{
bountyEntities = new();
@@ -225,7 +231,7 @@ public sealed partial class CargoSystem
return IsBountyComplete(container, data, out _);
}
public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet<EntityUid> bountyEntities)
public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet<EntityUid> bountyEntities)
{
if (!_protoMan.TryIndex(data.Bounty, out var proto))
{
@@ -314,7 +320,7 @@ public sealed partial class CargoSystem
var children = GetBountyEntities(ent);
foreach (var child in children)
{
entities.Add(child);
entities.Add(child);
}
}
}

View File

@@ -230,9 +230,8 @@ public sealed partial class CargoSystem
#region Station
private bool SellPallets(EntityUid gridUid, EntityUid? station, out double amount)
private bool SellPallets(EntityUid gridUid, out double amount)
{
station ??= _station.GetOwningStation(gridUid);
GetPalletGoods(gridUid, out var toSell, out amount);
Log.Debug($"Cargo sold {toSell.Count} entities for {amount}");
@@ -240,11 +239,9 @@ public sealed partial class CargoSystem
if (toSell.Count == 0)
return false;
if (station != null)
{
var ev = new EntitySoldEvent(station.Value, toSell);
RaiseLocalEvent(ref ev);
}
var ev = new EntitySoldEvent(toSell);
RaiseLocalEvent(ref ev);
foreach (var ent in toSell)
{
@@ -299,7 +296,7 @@ public sealed partial class CargoSystem
return false;
}
var complete = IsBountyComplete(uid, (EntityUid?) null, out var bountyEntities);
var complete = IsBountyComplete(uid, out var bountyEntities);
// Recursively check for mobs at any point.
var children = xform.ChildEnumerator;
@@ -332,7 +329,7 @@ public sealed partial class CargoSystem
return;
}
if (!SellPallets(gridUid, null, out var price))
if (!SellPallets(gridUid, out var price))
return;
var stackPrototype = _protoMan.Index<StackPrototype>(component.CashType);
@@ -354,4 +351,4 @@ public sealed partial class CargoSystem
/// deleted but after the price has been calculated.
/// </summary>
[ByRefEvent]
public readonly record struct EntitySoldEvent(EntityUid Station, HashSet<EntityUid> Sold);
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold);

View File

@@ -37,7 +37,6 @@ public sealed partial class CargoSystem : SharedCargoSystem
[Dependency] private readonly PricingSystem _pricing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly StationSystem _station = default!;

View File

@@ -57,7 +57,7 @@ public sealed class PriceGunSystem : EntitySystem
return;
// Check if we're scanning a bounty crate
if (_bountySystem.IsBountyComplete(args.Target.Value, (EntityUid?) null, out _))
if (_bountySystem.IsBountyComplete(args.Target.Value, out _))
{
_popupSystem.PopupEntity(Loc.GetString("price-gun-bounty-complete"), args.User, args.User);
}

View File

@@ -12,7 +12,6 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Stacks;
using Robust.Shared.Console;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -27,7 +26,6 @@ public sealed class PricingSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IConsoleHost _consoleHost = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;

View File

@@ -4,7 +4,6 @@ using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Utility;
using System.Numerics;
@@ -12,8 +11,6 @@ namespace Content.Server.Chemistry.Containers.EntitySystems;
public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem
{
[Dependency] private readonly INetManager _netManager = default!;
public override void Initialize()
{
base.Initialize();

View File

@@ -1,4 +1,3 @@
using Content.Server.Administration.Logs;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Nutrition.EntitySystems;
@@ -30,7 +29,6 @@ namespace Content.Server.Chemistry.EntitySystems
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
public override void Initialize()

View File

@@ -170,7 +170,6 @@ namespace Content.Server.Communications
_uiSystem.SetUiState(ui, new CommunicationsConsoleInterfaceState(
CanAnnounce(comp),
CanBroadcast(comp),
CanCallOrRecall(comp),
levels,
currentLevel,
@@ -184,11 +183,6 @@ namespace Content.Server.Communications
return comp.AnnouncementCooldownRemaining <= 0f;
}
private static bool CanBroadcast(CommunicationsConsoleComponent comp)
{
return comp.AnnouncementCooldownRemaining <= 0f;
}
private bool CanUse(EntityUid user, EntityUid console)
{
// This shouldn't technically be possible because of BUI but don't trust client.

View File

@@ -2,7 +2,6 @@ using System.Numerics;
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Server.Decals;
@@ -11,7 +10,6 @@ namespace Content.Server.Decals;
public sealed class EditDecalCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public string Command => "editdecal";
public string Description => "Edits a decal.";

View File

@@ -1,9 +1,7 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using SQLitePCL;
namespace Content.Server.Decals.Commands
{
@@ -11,7 +9,6 @@ namespace Content.Server.Decals.Commands
public sealed class RemoveDecalCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public string Command => "rmdecal";
public string Description => "removes a decal";

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Disposal.Tube;
using Content.Server.Disposal.Tube.Components;
@@ -12,14 +11,12 @@ using Robust.Shared.Containers;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
namespace Content.Server.Disposal.Unit.EntitySystems
{
public sealed class DisposableSystem : EntitySystem
{
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly DisposalUnitSystem _disposalUnitSystem = default!;

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Serialization.Manager;
using System.Numerics;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Dragon;
@@ -69,8 +70,9 @@ public sealed class DragonRiftSystem : EntitySystem
comp.State = DragonRiftState.AlmostFinished;
Dirty(uid, comp);
var location = xform.LocalPosition;
_chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red);
var msg = Loc.GetString("carp-rift-warning",
("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, xform)))));
_chat.DispatchGlobalAnnouncement(msg, playSound: false, colorOverride: Color.Red);
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
_navMap.SetBeaconEnabled(uid, true);
}

View File

@@ -41,8 +41,32 @@ public sealed partial class ElectrifiedComponent : Component
[DataField("lowVoltageNode")]
public string? LowVoltageNode;
/// <summary>
/// Damage multiplier for HV electrocution
/// </summary>
[DataField]
public float HighVoltageDamageMultiplier = 3f;
/// <summary>
/// Shock time multiplier for HV electrocution
/// </summary>
[DataField]
public float HighVoltageTimeMultiplier = 1.5f;
/// <summary>
/// Damage multiplier for MV electrocution
/// </summary>
[DataField]
public float MediumVoltageDamageMultiplier = 2f;
/// <summary>
/// Shock time multiplier for MV electrocution
/// </summary>
[DataField]
public float MediumVoltageTimeMultiplier = 1.25f;
[DataField("shockDamage")]
public int ShockDamage = 20;
public float ShockDamage = 7.5f;
/// <summary>
/// Shock time, in seconds.

View File

@@ -15,10 +15,4 @@ public sealed partial class ElectrocutionComponent : Component
[DataField("timeLeft")]
public float TimeLeft;
[DataField("accumDamage")]
public float AccumulatedDamage;
[DataField("baseDamage")]
public float BaseDamage = 20f;
}

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