Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor

This commit is contained in:
ElectroJr
2025-01-12 20:19:04 +13:00
378 changed files with 272396 additions and 119785 deletions

12
.github/CODEOWNERS vendored
View File

@@ -3,17 +3,17 @@
# Sorting by path instead of by who added it one day :(
# this isn't how codeowners rules work pls read the first comment instead of trying to force a sorting order
/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr
/Content.*/Administration/ @DrSmugleaf @nikthechampiongr
/Resources/ServerInfo/ @nikthechampiongr
/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr
/Resources/ConfigPresets/WizardsDen/ @nikthechampiongr @crazybrain23
/Content.*/Administration/ @DrSmugleaf @nikthechampiongr @crazybrain23
/Resources/ServerInfo/ @nikthechampiongr @crazybrain23
/Resources/ServerInfo/Guidebook/ServerRules/ @nikthechampiongr @crazybrain23
/Resources/Prototypes/Maps/** @Emisse
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr
/Resources/Prototypes/Guidebook/rules.yml @nikthechampiongr @crazybrain23
/Content.*/Body/ @DrSmugleaf
/Content.YAMLLinter @DrSmugleaf
/Content.Shared/Damage/ @DrSmugleaf
@@ -25,7 +25,7 @@
# SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr
/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @nikthechampiongr @crazybrain23
/Pow3r/ @PJB3005
/Content.Server/Power/Pow3r/ @PJB3005

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -22,7 +22,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Get Engine Tag
run: |

View File

@@ -51,7 +51,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build

View File

@@ -1,16 +1,21 @@
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Systems;
using Content.Shared.CCVar;
using Content.Shared.Mind;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client.Administration;
internal sealed class AdminNameOverlay : Overlay
{
[Dependency] private readonly IConfigurationManager _config = default!;
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
@@ -18,8 +23,16 @@ internal sealed class AdminNameOverlay : Overlay
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly Font _font;
//TODO make this adjustable via GUI
private readonly ProtoId<RoleTypePrototype>[] _filter =
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
private readonly Color _antagColorClassic = Color.OrangeRed;
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
{
IoCManager.InjectDependencies(this);
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
@@ -35,6 +48,9 @@ internal sealed class AdminNameOverlay : Overlay
{
var viewport = args.WorldAABB;
//TODO make this adjustable via GUI
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
foreach (var playerInfo in _system.PlayerList)
{
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
@@ -64,11 +80,19 @@ internal sealed class AdminNameOverlay : Overlay
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
if (playerInfo.Antag)
if (classic && playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), "ANTAG", uiScale, Color.OrangeRed);
;
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), _antagLabelClassic, uiScale, _antagColorClassic);
}
else if (!classic && _filter.Contains(playerInfo.RoleProto.ID))
{
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
var color = playerInfo.RoleProto.Color;
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), label, uiScale, color);
}
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
}

View File

@@ -197,6 +197,7 @@ public sealed partial class PlayerTab : Control
Header.Character => Compare(x.CharacterName, y.CharacterName),
Header.Job => Compare(x.StartingJob, y.StartingJob),
Header.Antagonist => x.Antag.CompareTo(y.Antag),
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};

View File

@@ -24,6 +24,11 @@
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="RoleTypeLabel"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="OverallPlaytimeLabel"
SizeFlagsStretchRatio="1"
HorizontalExpand="True"

View File

@@ -23,6 +23,8 @@ public sealed partial class PlayerTabEntry : PanelContainer
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
PlayerEntity = player.NetEntity;

View File

@@ -32,6 +32,13 @@
Text="{Loc player-tab-antagonist}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="RoleTypeLabel"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"
Text="{Loc player-tab-roletype}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="PlaytimeLabel"
SizeFlagsStretchRatio="1"
HorizontalExpand="True"

View File

@@ -19,6 +19,7 @@ public sealed partial class PlayerTabHeader : Control
CharacterLabel.OnKeyBindDown += CharacterClicked;
JobLabel.OnKeyBindDown += JobClicked;
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
}
@@ -30,6 +31,7 @@ public sealed partial class PlayerTabHeader : Control
Header.Character => CharacterLabel,
Header.Job => JobLabel,
Header.Antagonist => AntagonistLabel,
Header.RoleType => RoleTypeLabel,
Header.Playtime => PlaytimeLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
};
@@ -41,6 +43,7 @@ public sealed partial class PlayerTabHeader : Control
CharacterLabel.Text = Loc.GetString("player-tab-character");
JobLabel.Text = Loc.GetString("player-tab-job");
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
}
@@ -75,6 +78,11 @@ public sealed partial class PlayerTabHeader : Control
HeaderClicked(args, Header.Antagonist);
}
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.RoleType);
}
private void PlaytimeClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.Playtime);
@@ -90,6 +98,7 @@ public sealed partial class PlayerTabHeader : Control
CharacterLabel.OnKeyBindDown -= CharacterClicked;
JobLabel.OnKeyBindDown -= JobClicked;
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
}
}
@@ -100,6 +109,7 @@ public sealed partial class PlayerTabHeader : Control
Character,
Job,
Antagonist,
RoleType,
Playtime
}
}

View File

@@ -130,8 +130,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
if (!_pumps.TryGetValue(addr, out var pumpControl))
{
var control= new PumpControl(pump, addr);
control.PumpDataChanged += AtmosDeviceDataChanged!.Invoke;
control.PumpDataCopied += AtmosDeviceDataCopied!.Invoke;
control.PumpDataChanged += AtmosDeviceDataChanged;
control.PumpDataCopied += AtmosDeviceDataCopied;
_pumps.Add(addr, control);
CVentContainer.AddChild(control);
}
@@ -145,8 +145,8 @@ public sealed partial class AirAlarmWindow : FancyWindow
if (!_scrubbers.TryGetValue(addr, out var scrubberControl))
{
var control = new ScrubberControl(scrubber, addr);
control.ScrubberDataChanged += AtmosDeviceDataChanged!.Invoke;
control.ScrubberDataCopied += AtmosDeviceDataCopied!.Invoke;
control.ScrubberDataChanged += AtmosDeviceDataChanged;
control.ScrubberDataCopied += AtmosDeviceDataCopied;
_scrubbers.Add(addr, control);
CScrubberContainer.AddChild(control);
}
@@ -161,6 +161,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
{
var control = new SensorInfo(sensor, addr);
control.OnThresholdUpdate += AtmosAlarmThresholdChanged;
control.SensorDataCopied += AtmosDeviceDataCopied;
_sensors.Add(addr, control);
CSensorContainer.AddChild(control);
}

View File

@@ -3,6 +3,9 @@
<CollapsibleHeading Name="SensorAddress" />
<CollapsibleBody Margin="20 2 2 2">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" Margin ="0 0 0 2">
<Button Name="CCopySettings" Text="{Loc 'air-alarm-ui-thresholds-copy'}" ToolTip="{Loc 'air-alarm-ui-thresholds-copy-tooltip'}" />
</BoxContainer>
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
<RichTextLabel Name="AlarmStateLabel" />
<RichTextLabel Name="PressureLabel" />

View File

@@ -12,12 +12,14 @@ namespace Content.Client.Atmos.Monitor.UI.Widgets;
public sealed partial class SensorInfo : BoxContainer
{
public Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? OnThresholdUpdate;
public event Action<AtmosSensorData>? SensorDataCopied;
private string _address;
private ThresholdControl _pressureThreshold;
private ThresholdControl _temperatureThreshold;
private Dictionary<Gas, ThresholdControl> _gasThresholds = new();
private Dictionary<Gas, RichTextLabel> _gasLabels = new();
private Button _copySettings => CCopySettings;
public SensorInfo(AtmosSensorData data, string address)
{
@@ -56,7 +58,7 @@ public sealed partial class SensorInfo : BoxContainer
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
{
OnThresholdUpdate!(_address, type, alarmThreshold, arg3);
OnThresholdUpdate?.Invoke(_address, type, alarmThreshold, arg3);
};
_gasThresholds.Add(gas, gasThresholdControl);
@@ -72,12 +74,17 @@ public sealed partial class SensorInfo : BoxContainer
_pressureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
{
OnThresholdUpdate!(_address, type, threshold, arg3);
OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
};
_temperatureThreshold.ThresholdDataChanged += (type, threshold, arg3) =>
{
OnThresholdUpdate!(_address, type, threshold, arg3);
OnThresholdUpdate?.Invoke(_address, type, threshold, arg3);
};
_copySettings.OnPressed += _ =>
{
SensorDataCopied?.Invoke(data);
};
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Cargo.Components;
using Content.Shared.Timing;
using Content.Shared.Cargo.Systems;
@@ -10,9 +11,9 @@ public sealed class ClientPriceGunSystem : SharedPriceGunSystem
{
[Dependency] private readonly UseDelaySystem _useDelay = default!;
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
{
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
if (!TryComp(entity, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity, useDelay)))
return false;
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client.PDA;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Inventory;
@@ -51,6 +52,15 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
{
sprite.CopyFrom(otherSprite);
}
// Edgecase for PDAs to include visuals when UI is open
if (TryComp(uid, out PdaBorderColorComponent? borderColor)
&& proto.TryGetComponent(out PdaBorderColorComponent? otherBorderColor, _factory))
{
borderColor.BorderColor = otherBorderColor.BorderColor;
borderColor.AccentHColor = otherBorderColor.AccentHColor;
borderColor.AccentVColor = otherBorderColor.AccentVColor;
}
}
/// <summary>

View File

@@ -1,15 +1,21 @@
using Content.Client.Clothing.Systems;
using Content.Client.Clothing.Systems;
using Content.Shared.Clothing.Components;
using Content.Shared.Tag;
using Content.Shared.Prototypes;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Shared.Prototypes;
namespace Content.Client.Clothing.UI;
[UsedImplicitly]
public sealed class ChameleonBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
private readonly ChameleonClothingSystem _chameleon;
private readonly TagSystem _tag;
[ViewVariables]
private ChameleonMenu? _menu;
@@ -17,6 +23,7 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
public ChameleonBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
_chameleon = EntMan.System<ChameleonClothingSystem>();
_tag = EntMan.System<TagSystem>();
}
protected override void Open()
@@ -34,8 +41,25 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
return;
var targets = _chameleon.GetValidTargets(st.Slot);
if (st.RequiredTag != null)
{
var newTargets = new List<string>();
foreach (var target in targets)
{
if (string.IsNullOrEmpty(target) || !_proto.TryIndex(target, out EntityPrototype? proto))
continue;
if (!proto.TryGetComponent(out TagComponent? tag, _factory) || !_tag.HasTag(tag, st.RequiredTag))
continue;
newTargets.Add(target);
}
_menu?.UpdateState(newTargets, st.SelectedId);
} else
{
_menu?.UpdateState(targets, st.SelectedId);
}
}
private void OnIdSelected(string selectedId)
{

View File

@@ -27,7 +27,11 @@
<!-- Header text -->
<BoxContainer MinHeight="60" Orientation="Vertical" VerticalAlignment="Center">
<Label Name="CallStatusText" Margin="10 5 10 0" ReservesSpace="False"/>
<RichTextLabel Name="CallerIdText" HorizontalAlignment="Center" Margin="0 0 0 0" ReservesSpace="False"/>
<BoxContainer Name="CallerIdContainer" Orientation="Vertical" ReservesSpace="False">
<RichTextLabel Name="CallerIdText" HorizontalAlignment="Center" Margin="0 0 0 0"/>
<Label Text="{Loc 'holopad-window-relay-label'}" Margin="10 10 10 0" ReservesSpace="False"/>
<RichTextLabel Name="HolopadIdText" HorizontalAlignment="Center" Margin="0 0 0 10"/>
</BoxContainer>
</BoxContainer>
<!-- Controls (the answer call button is absent when the phone is not ringing) -->
@@ -70,6 +74,12 @@
</PanelContainer>
<PanelContainer Name="HolopadContactListPanel">
<BoxContainer Orientation="Vertical">
<!-- Contact filter -->
<LineEdit Name="SearchLineEdit" HorizontalExpand="True" Margin="4, 4, 4, 0"
PlaceHolder="{Loc holopad-window-filter-line-placeholder}" />
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="8, 8, 8, 8" MinHeight="256">
<!-- If there is no data yet, this will be displayed -->
@@ -80,6 +90,7 @@
<!-- Container for the contacts -->
<BoxContainer Name="ContactsList" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 0"/>
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>

View File

@@ -171,8 +171,10 @@ public sealed partial class HolopadWindow : FancyWindow
// Caller ID text
var callerId = _telephoneSystem.GetFormattedCallerIdForEntity(telephone.LastCallerId.Item1, telephone.LastCallerId.Item2, Color.LightGray, "Default", 11);
var holoapdId = _telephoneSystem.GetFormattedDeviceIdForEntity(telephone.LastCallerId.Item3, Color.LightGray, "Default", 11);
CallerIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
HolopadIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(holoapdId));
LockOutIdText.SetMessage(FormattedMessage.FromMarkupOrThrow(callerId));
// Sort holopads alphabetically
@@ -236,10 +238,13 @@ public sealed partial class HolopadWindow : FancyWindow
// Make / update required children
foreach (var child in ContactsList.Children)
{
if (child is not HolopadContactButton)
if (child is not HolopadContactButton contactButton)
continue;
var contactButton = (HolopadContactButton)child;
var passesFilter = string.IsNullOrEmpty(SearchLineEdit.Text) ||
contactButton.Text?.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase) == true;
contactButton.Visible = passesFilter;
contactButton.Disabled = (_currentState != TelephoneState.Idle || lockButtons);
}
@@ -290,7 +295,7 @@ public sealed partial class HolopadWindow : FancyWindow
FetchingAvailableHolopadsContainer.Visible = (ContactsList.ChildCount == 0);
ActiveCallControlsContainer.Visible = (_currentState != TelephoneState.Idle || _currentUiKey == HolopadUiKey.AiRequestWindow);
CallPlacementControlsContainer.Visible = !ActiveCallControlsContainer.Visible;
CallerIdText.Visible = (_currentState == TelephoneState.Ringing);
CallerIdContainer.Visible = (_currentState == TelephoneState.Ringing);
AnswerCallButton.Visible = (_currentState == TelephoneState.Ringing);
}
@@ -316,6 +321,7 @@ public sealed partial class HolopadWindow : FancyWindow
HorizontalExpand = true;
SetHeight = 32;
Margin = new Thickness(0f, 1f, 0f, 1f);
ReservesSpace = false;
}
public void UpdateValues(NetEntity netEntity, string label)

View File

@@ -5,19 +5,21 @@ using Content.Client.Lobby.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Systems.Chat;
using Content.Client.Voting;
using Content.Shared.CCVar;
using Robust.Client;
using Robust.Client.Console;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
namespace Content.Client.Lobby
{
public sealed class LobbyState : Robust.Client.State.State
{
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
@@ -49,7 +51,17 @@ namespace Content.Client.Lobby
_voteManager.SetPopupContainer(Lobby.VoteContainer);
LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
var lobbyNameCvar = _cfg.GetCVar(CCVars.ServerLobbyName);
var serverName = _baseClient.GameInfo?.ServerName ?? string.Empty;
Lobby.ServerName.Text = string.IsNullOrEmpty(lobbyNameCvar)
? Loc.GetString("ui-lobby-title", ("serverName", serverName))
: lobbyNameCvar;
var width = _cfg.GetCVar(CCVars.ServerLobbyRightPanelWidth);
Lobby.RightSide.SetWidth = width;
UpdateLobbyUi();
Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;

View File

@@ -36,19 +36,20 @@ public sealed partial class LoadoutContainer : BoxContainer
if (_protoManager.TryIndex(proto, out var loadProto))
{
var ent = _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
var ent = loadProto.DummyEntity ?? _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
if (ent == null)
return;
if (ent != null)
{
_entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
Sprite.SetEntity(_entity);
var spriteTooltip = new Tooltip();
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
TooltipSupplier = _ => spriteTooltip;
}
}
}
protected override void Dispose(bool disposing)
{

View File

@@ -62,14 +62,12 @@
<Control Access="Public" Visible="False" Name="CharacterSetupState" VerticalExpand="True" />
</BoxContainer>
<!-- Right Panel -->
<PanelContainer Name="RightSide" StyleClasses="AngleRect" HorizontalAlignment="Right" VerticalExpand="True"
<PanelContainer Name="RightSide" Access="Public" StyleClasses="AngleRect" HorizontalAlignment="Right" VerticalExpand="True"
VerticalAlignment="Stretch">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<!-- Top row -->
<BoxContainer Orientation="Horizontal" MinSize="0 40" Name="HeaderContainer" Access="Public"
SeparationOverride="4">
<Label Margin="8 0 0 0" StyleClasses="LabelHeadingBigger" VAlign="Center"
Text="{Loc 'ui-lobby-title'}" />
<Label Name="ServerName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
</BoxContainer>

View File

@@ -141,6 +141,11 @@ namespace Content.Client.PDA
_pdaOwner = state.PdaOwnerInfo.ActualOwnerName;
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
("actualOwnerName", _pdaOwner)));
PdaOwnerLabel.Visible = true;
}
else
{
PdaOwnerLabel.Visible = false;
}

View File

@@ -1,48 +1,8 @@
using Content.Shared.PDA;
using Content.Shared.Light;
using Robust.Client.GameObjects;
namespace Content.Client.PDA;
public sealed class PdaSystem : SharedPdaSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PdaComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(EntityUid uid, PdaComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (Appearance.TryGetData<bool>(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
args.Sprite.LayerSetVisible(PdaVisualLayers.Flashlight, isFlashlightOn);
if (Appearance.TryGetData<bool>(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
args.Sprite.LayerSetVisible(PdaVisualLayers.IdLight, isCardInserted);
}
protected override void OnComponentInit(EntityUid uid, PdaComponent component, ComponentInit args)
{
base.OnComponentInit(uid, component, args);
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (component.State != null)
sprite.LayerSetState(PdaVisualLayers.Base, component.State);
sprite.LayerSetVisible(PdaVisualLayers.Flashlight, component.FlashlightOn);
sprite.LayerSetVisible(PdaVisualLayers.IdLight, component.IdSlot.StartingItem != null);
}
public enum PdaVisualLayers : byte
{
Base,
Flashlight,
IdLight
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Light;
using Content.Shared.PDA;
using Robust.Client.GameObjects;
namespace Content.Client.PDA;
public sealed class PdaVisualizerSystem : VisualizerSystem<PdaVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, PdaVisualsComponent comp, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (AppearanceSystem.TryGetData<string>(uid, PdaVisuals.PdaType, out var pdaType, args.Component))
args.Sprite.LayerSetState(PdaVisualLayers.Base, pdaType);
if (AppearanceSystem.TryGetData<bool>(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
args.Sprite.LayerSetVisible(PdaVisualLayers.Flashlight, isFlashlightOn);
if (AppearanceSystem.TryGetData<bool>(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
args.Sprite.LayerSetVisible(PdaVisualLayers.IdLight, isCardInserted);
}
public enum PdaVisualLayers : byte
{
Base,
Flashlight,
IdLight
}
}

View File

@@ -0,0 +1,14 @@
namespace Content.Client.PDA;
/// <summary>
/// Used for visualizing PDA visuals.
/// </summary>
[RegisterComponent]
public sealed partial class PdaVisualsComponent : Component
{
public string? BorderColor;
public string? AccentHColor;
public string? AccentVColor;
}

View File

@@ -28,6 +28,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
public SensorMonitoringWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void UpdateState(ConsoleUIState state)

View File

@@ -104,7 +104,7 @@ public sealed class TippyUIController : UIController
? -WaddleRotation
: WaddleRotation;
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step) && step.FootstepSoundCollection != null)
{
var audioParams = step.FootstepSoundCollection.Params
.AddVolume(-7f)

View File

@@ -7,7 +7,9 @@ using Content.Client.UserInterface.Systems.Character.Controls;
using Content.Client.UserInterface.Systems.Character.Windows;
using Content.Client.UserInterface.Systems.Objectives.Controls;
using Content.Shared.Input;
using Content.Shared.Objectives.Systems;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Player;
@@ -15,6 +17,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using static Content.Client.CharacterInfo.CharacterInfoSystem;
using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -24,10 +27,25 @@ namespace Content.Client.UserInterface.Systems.Character;
[UsedImplicitly]
public sealed class CharacterUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CharacterInfoSystem>
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
[UISystemDependency] private readonly SpriteSystem _sprite = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
base.Initialize();
_sawmill = _logMan.GetSawmill("character");
SubscribeNetworkEvent<MindRoleTypeChangedEvent>(OnRoleTypeChanged);
}
private CharacterWindow? _window;
private MenuButton? CharacterButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.CharacterButton;
@@ -110,6 +128,9 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
var (entity, job, objectives, briefing, entityName) = data;
_window.SpriteView.SetEntity(entity);
UpdateRoleType();
_window.NameLabel.Text = entityName;
_window.SubText.Text = job;
_window.Objectives.RemoveAllChildren();
@@ -173,6 +194,37 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
_window.RolePlaceholder.Visible = briefing == null && !controls.Any() && !objectives.Any();
}
private void OnRoleTypeChanged(MindRoleTypeChangedEvent ev, EntitySessionEventArgs _)
{
UpdateRoleType();
}
private void UpdateRoleType()
{
if (_window == null || !_window.IsOpen)
return;
if (!_ent.TryGetComponent<MindContainerComponent>(_player.LocalEntity, out var container)
|| container.Mind is null)
return;
if (!_ent.TryGetComponent<MindComponent>(container.Mind.Value, out var mind))
return;
var roleText = Loc.GetString("role-type-crew-aligned-name");
var color = Color.White;
if (_prototypeManager.TryIndex(mind.RoleType, out var proto))
{
roleText = Loc.GetString(proto.Name);
color = proto.Color;
}
else
_sawmill.Error($"{_player.LocalEntity} has invalid Role Type '{mind.RoleType}'. Displaying '{roleText}' instead");
_window.RoleType.Text = roleText;
_window.RoleType.FontColorOverride = color;
}
private void CharacterDetached(EntityUid uid)
{
CloseWindow();

View File

@@ -7,6 +7,7 @@
MinHeight="545">
<ScrollContainer>
<BoxContainer Orientation="Vertical">
<Label Name="RoleType" VerticalAlignment="Top" Margin="0 6 0 10" HorizontalAlignment="Center" StyleClasses="LabelHeading" Access="Public"/>
<BoxContainer Orientation="Horizontal">
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64"/>
<BoxContainer Orientation="Vertical" VerticalAlignment="Top">

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared;
using Robust.Shared.Audio.Components;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
@@ -216,14 +217,17 @@ namespace Content.IntegrationTests.Tests
/// generally not spawn unrelated / detached entities. Any entities that do get spawned should be parented to
/// the spawned entity (e.g., in a container). If an entity needs to spawn an entity somewhere in null-space,
/// it should delete that entity when it is no longer required. This test mainly exists to prevent "entity leak"
/// bugs, where spawning some entity starts spawning unrelated entities in null space.
/// bugs, where spawning some entity starts spawning unrelated entities in null space that stick around after
/// the original entity is gone.
///
/// Note that this isn't really a strict requirement, and there are probably quite a few edge cases. Its a pretty
/// crude test to try catch issues like this, and possibly should just be disabled.
/// </remarks>
[Test]
public async Task SpawnAndDeleteEntityCountTest()
{
var settings = new PoolSettings { Connected = true, Dirty = true };
await using var pair = await PoolManager.GetServerClient(settings);
var mapManager = pair.Server.ResolveDependency<IMapManager>();
var mapSys = pair.Server.System<SharedMapSystem>();
var server = pair.Server;
var client = pair.Client;
@@ -261,6 +265,9 @@ namespace Content.IntegrationTests.Tests
await pair.RunTicksSync(3);
// We consider only non-audio entities, as some entities will just play sounds when they spawn.
int Count(IEntityManager ent) => ent.EntityCount - ent.Count<AudioComponent>();
foreach (var protoId in protoIds)
{
// TODO fix ninja
@@ -268,8 +275,8 @@ namespace Content.IntegrationTests.Tests
if (protoId == "MobHumanSpaceNinja")
continue;
var count = server.EntMan.EntityCount;
var clientCount = client.EntMan.EntityCount;
var count = Count(server.EntMan);
var clientCount = Count(client.EntMan);
EntityUid uid = default;
await server.WaitPost(() => uid = server.EntMan.SpawnEntity(protoId, coords));
await pair.RunTicksSync(3);
@@ -277,30 +284,30 @@ namespace Content.IntegrationTests.Tests
// If the entity deleted itself, check that it didn't spawn other entities
if (!server.EntMan.EntityExists(uid))
{
if (server.EntMan.EntityCount != count)
if (Count(server.EntMan) != count)
{
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
}
if (client.EntMan.EntityCount != clientCount)
if (Count(client.EntMan) != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Server was {count}.");
}
continue;
}
// Check that the number of entities has increased.
if (server.EntMan.EntityCount <= count)
if (Count(server.EntMan) <= count)
{
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
}
if (client.EntMan.EntityCount <= clientCount)
if (Count(client.EntMan) <= clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
$"Expected at least {clientCount} and found {Count(client.EntMan)}. " +
$"Server was {count}");
}
@@ -308,15 +315,15 @@ namespace Content.IntegrationTests.Tests
await pair.RunTicksSync(3);
// Check that the number of entities has gone back to the original value.
if (server.EntMan.EntityCount != count)
if (Count(server.EntMan) != count)
{
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
}
if (client.EntMan.EntityCount != clientCount)
if (Count(client.EntMan) != clientCount)
{
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
$"Expected {clientCount} and found {Count(client.EntMan)}.\n" +
$"Server was {count}.");
}
}

View File

@@ -35,7 +35,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
{
playerUid = serverPlayerManager.Sessions.Single().AttachedEntity.GetValueOrDefault();
#pragma warning disable NUnit2045 // Interdependent assertions.
Assert.That(playerUid, Is.Not.EqualTo(default));
Assert.That(playerUid, Is.Not.EqualTo(default(EntityUid)));
// Making sure it exists
Assert.That(entManager.HasComponent<AlertsComponent>(playerUid));
#pragma warning restore NUnit2045

View File

@@ -65,9 +65,8 @@ namespace Content.IntegrationTests.Tests
"Cog",
"Gate",
"Amber",
"Loop"
"Loop",
"Elkridge"
};
/// <summary>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>

View File

@@ -116,10 +116,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
targetLabelColor = Color.White;
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderComponent))
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderEnt))
return;
var currentAccessHashsets = accessReaderComponent.AccessLists;
var currentAccessHashsets = accessReaderEnt.Value.Comp.AccessLists;
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
}
@@ -210,10 +210,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
return;
}
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReader))
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReaderEnt))
return;
var oldTags = ConvertAccessHashSetsToList(accessReader.AccessLists);
var oldTags = ConvertAccessHashSetsToList(accessReaderEnt.Value.Comp.AccessLists);
var privilegedId = component.PrivilegedIdSlot.Item;
if (oldTags.SequenceEqual(newAccessList))
@@ -242,10 +242,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
var removedTags = oldTags.Except(newAccessList).Select(tag => "-" + tag).ToList();
_adminLogger.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(player):player} has modified {ToPrettyString(component.TargetAccessReaderId):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
accessReader.AccessLists = ConvertAccessListToHashSet(newAccessList);
Dirty(component.TargetAccessReaderId, accessReader);
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
Dirty(accessReaderEnt.Value);
}
/// <summary>

View File

@@ -33,16 +33,16 @@ public sealed class AdminWhoCommand : IConsoleCommand
var first = true;
foreach (var admin in adminMgr.ActiveAdmins)
{
if (!first)
sb.Append('\n');
first = false;
var adminData = adminMgr.GetAdminData(admin)!;
DebugTools.AssertNotNull(adminData);
if (adminData.Stealth && !seeStealth)
continue;
if (!first)
sb.Append('\n');
first = false;
sb.Append(admin.Name);
if (adminData.Title is { } title)
sb.Append($": [{title}]");

View File

@@ -408,6 +408,17 @@ namespace Content.Server.Administration.Managers
}
private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminData(ICommonSession session)
{
var result = await LoadAdminDataCore(session);
// Make sure admin didn't disconnect while data was loading.
if (session.Status != SessionStatus.InGame)
return null;
return result;
}
private async Task<(AdminData dat, int? rankId, bool specialLogin)?> LoadAdminDataCore(ICommonSession session)
{
var promoteHost = IsLocal(session) && _cfg.GetCVar(CCVars.ConsoleLoginLocal)
|| _promotedPlayers.Contains(session.UserId)

View File

@@ -17,7 +17,6 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Mind;
using Content.Shared.PDA;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Popups;
using Content.Shared.Roles;
@@ -32,6 +31,7 @@ using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Systems;
@@ -48,6 +48,7 @@ public sealed class AdminSystem : EntitySystem
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@@ -165,7 +166,8 @@ public sealed class AdminSystem : EntitySystem
private void OnRoleEvent(RoleEvent ev)
{
var session = _minds.GetSession(ev.Mind);
if (!ev.Antagonist || session == null)
if (!ev.RoleTypeUpdate || session == null)
return;
UpdatePlayerList(session);
@@ -239,9 +241,16 @@ public sealed class AdminSystem : EntitySystem
}
var antag = false;
RoleTypePrototype roleType = new();
var startingRole = string.Empty;
if (_minds.TryGetMind(session, out var mindId, out _))
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
{
if (_proto.TryIndex(mindComp.RoleType, out var role))
roleType = role;
else
Log.Error($"{ToPrettyString(mindId)} has invalid Role Type '{mindComp.RoleType}'. Displaying '{Loc.GetString(roleType.Name)}' instead");
antag = _role.MindIsAntagonist(mindId);
startingRole = _jobs.MindTryGetJobName(mindId);
}
@@ -255,7 +264,7 @@ public sealed class AdminSystem : EntitySystem
overallPlaytime = playTime;
}
return new PlayerInfo(name, entityName, identityName, startingRole, antag, GetNetEntity(session?.AttachedEntity), data.UserId,
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime);
}

View File

@@ -97,6 +97,6 @@ public sealed partial class ReagentProducerAnomalyComponent : Component
/// <summary>
/// Solution where the substance is generated
/// </summary>
[DataField("solutionRef")]
[ViewVariables]
public Entity<SolutionComponent>? Solution = null;
}

View File

@@ -153,7 +153,7 @@ public partial struct AntagSelectionDefinition()
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
public List<EntProtoId>? MindRoles;
/// <summary>
/// A set of starting gear that's equipped to the player.

View File

@@ -131,6 +131,19 @@ public sealed class AirAlarmSystem : EntitySystem
SyncDevice(uid, address);
}
private void SetAllThresholds(EntityUid uid, string address, AtmosSensorData data)
{
var payload = new NetworkPayload
{
[DeviceNetworkConstants.Command] = AtmosMonitorSystem.AtmosMonitorSetAllThresholdsCmd,
[AtmosMonitorSystem.AtmosMonitorAllThresholdData] = data
};
_deviceNet.QueuePacket(uid, address, payload);
SyncDevice(uid, address);
}
/// <summary>
/// Sync this air alarm's mode with the rest of the network.
/// </summary>
@@ -341,6 +354,13 @@ public sealed class AirAlarmSystem : EntitySystem
SetData(uid, addr, args.Data);
}
break;
case AtmosSensorData sensorData:
foreach (string addr in component.SensorData.Keys)
{
SetAllThresholds(uid, addr, sensorData);
}
break;
}
}

View File

@@ -33,10 +33,11 @@ public sealed class AtmosMonitorSystem : EntitySystem
// Commands
public const string AtmosMonitorSetThresholdCmd = "atmos_monitor_set_threshold";
public const string AtmosMonitorSetAllThresholdsCmd = "atmos_monitor_set_all_thresholds";
// Packet data
public const string AtmosMonitorThresholdData = "atmos_monitor_threshold_data";
public const string AtmosMonitorAllThresholdData = "atmos_monitor_all_threshold_data";
public const string AtmosMonitorThresholdDataType = "atmos_monitor_threshold_type";
public const string AtmosMonitorThresholdGasType = "atmos_monitor_threshold_gas";
@@ -138,7 +139,12 @@ public sealed class AtmosMonitorSystem : EntitySystem
args.Data.TryGetValue(AtmosMonitorThresholdGasType, out Gas? gas);
SetThreshold(uid, thresholdType.Value, thresholdData, gas);
}
break;
case AtmosMonitorSetAllThresholdsCmd:
if (args.Data.TryGetValue(AtmosMonitorAllThresholdData, out AtmosSensorData? allThresholdData))
{
SetAllThresholds(uid, allThresholdData);
}
break;
case AtmosDeviceNetworkSystem.SyncData:
var payload = new NetworkPayload();
@@ -403,4 +409,20 @@ public sealed class AtmosMonitorSystem : EntitySystem
}
}
/// <summary>
/// Sets all of a monitor's thresholds at once according to the incoming
/// AtmosSensorData object's thresholds.
/// </summary>
/// <param name="uid">The entity's uid</param>
/// <param name="allThresholdData">An AtmosSensorData object from which the thresholds will be loaded.</param>
public void SetAllThresholds(EntityUid uid, AtmosSensorData allThresholdData)
{
SetThreshold(uid, AtmosMonitorThresholdType.Temperature, allThresholdData.TemperatureThreshold);
SetThreshold(uid, AtmosMonitorThresholdType.Pressure, allThresholdData.PressureThreshold);
foreach (var gas in Enum.GetValues<Gas>())
{
SetThreshold(uid, AtmosMonitorThresholdType.Gas, allThresholdData.GasThresholds[gas], gas);
}
}
}

View File

@@ -25,7 +25,7 @@ public sealed partial class GasCondenserComponent : Component
/// <summary>
/// The solution that gases are condensed into.
/// </summary>
[DataField]
[ViewVariables]
public Entity<SolutionComponent>? Solution = null;
/// <summary>

View File

@@ -157,22 +157,22 @@ namespace Content.Server.Body.Components
/// <summary>
/// Internal solution for blood storage
/// </summary>
[DataField]
public Entity<SolutionComponent>? BloodSolution = null;
[ViewVariables]
public Entity<SolutionComponent>? BloodSolution;
/// <summary>
/// Internal solution for reagent storage
/// </summary>
[DataField]
public Entity<SolutionComponent>? ChemicalSolution = null;
[ViewVariables]
public Entity<SolutionComponent>? ChemicalSolution;
/// <summary>
/// Temporary blood solution.
/// When blood is lost, it goes to this solution, and when this
/// solution hits a certain cap, the blood is actually spilled as a puddle.
/// </summary>
[DataField]
public Entity<SolutionComponent>? TemporarySolution = null;
[ViewVariables]
public Entity<SolutionComponent>? TemporarySolution;
/// <summary>
/// Variable that stores the amount of status time added by having a low blood level.

View File

@@ -26,7 +26,7 @@ public sealed partial class LungComponent : Component
/// <summary>
/// The solution on this entity that these lungs act on.
/// </summary>
[DataField]
[ViewVariables]
public Entity<SolutionComponent>? Solution = null;
/// <summary>

View File

@@ -25,8 +25,8 @@ namespace Content.Server.Body.Components
/// <summary>
/// The solution inside of this stomach this transfers reagents to the body.
/// </summary>
[DataField]
public Entity<SolutionComponent>? Solution = null;
[ViewVariables]
public Entity<SolutionComponent>? Solution;
/// <summary>
/// What solution should this stomach push reagents into, on the body?

View File

@@ -96,6 +96,6 @@ public sealed partial class PlantHolderComponent : Component
[DataField]
public string SoilSolutionName = "soil";
[DataField]
[ViewVariables]
public Entity<SolutionComponent>? SoilSolution = null;
}

View File

@@ -1,7 +1,9 @@
using Content.Server.Popups;
using Content.Shared.Cargo.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Timing;
using Content.Shared.Cargo.Systems;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Cargo.Systems;
@@ -11,12 +13,12 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
[Dependency] private readonly PricingSystem _pricingSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly CargoSystem _bountySystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
protected override bool GetPriceOrBounty(Entity<PriceGunComponent> entity, EntityUid target, EntityUid user)
{
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
if (!TryComp(entity.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((entity.Owner, useDelay)))
return false;
// Check if we're scanning a bounty crate
if (_bountySystem.IsBountyComplete(target, out _))
{
@@ -25,10 +27,15 @@ public sealed class PriceGunSystem : SharedPriceGunSystem
else // Otherwise appraise the price
{
var price = _pricingSystem.GetPrice(target);
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result", ("object", Identity.Entity(target, EntityManager)), ("price", $"{price:F2}")), user, user);
_popupSystem.PopupEntity(Loc.GetString("price-gun-pricing-result",
("object", Identity.Entity(target, EntityManager)),
("price", $"{price:F2}")),
user,
user);
}
_useDelay.TryResetDelay((priceGunUid, useDelay));
_audio.PlayPvs(entity.Comp.AppraisalSound, entity.Owner);
_useDelay.TryResetDelay((entity.Owner, useDelay));
return true;
}
}

View File

@@ -1,11 +1,14 @@
using Content.Server.Administration.Logs;
using Content.Shared.CartridgeLoader;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.Database;
namespace Content.Server.CartridgeLoader.Cartridges;
public sealed class NotekeeperCartridgeSystem : EntitySystem
{
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
public override void Initialize()
{
@@ -36,16 +39,19 @@ public sealed class NotekeeperCartridgeSystem : EntitySystem
if (message.Action == NotekeeperUiAction.Add)
{
component.Notes.Add(message.Note);
_adminLogger.Add(LogType.PdaInteract, LogImpact.Low,
$"{ToPrettyString(args.Actor)} added a note to PDA: '{message.Note}' contained on: {ToPrettyString(uid)}");
}
else
{
component.Notes.Remove(message.Note);
_adminLogger.Add(LogType.PdaInteract, LogImpact.Low,
$"{ToPrettyString(args.Actor)} removed a note from PDA: '{message.Note}' was contained on: {ToPrettyString(uid)}");
}
UpdateUiState(uid, GetEntity(args.LoaderUid), component);
}
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, NotekeeperCartridgeComponent? component)
{
if (!Resolve(uid, ref component))

View File

@@ -20,7 +20,7 @@ public sealed partial class SolutionRegenerationComponent : Component
/// <summary>
/// The solution to add reagents to.
/// </summary>
[DataField]
[ViewVariables]
public Entity<SolutionComponent>? SolutionRef = null;
/// <summary>

View File

@@ -1,4 +1,4 @@
using Content.Server.IdentityManagement;
using Content.Server.IdentityManagement;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.IdentityManagement.Components;
@@ -63,7 +63,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
if (!Resolve(uid, ref component))
return;
var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default);
var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default, component.RequireTag);
_uiSystem.SetUiState(uid, ChameleonUiKey.Key, state);
}
@@ -84,7 +84,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
// make sure that it is valid change
if (string.IsNullOrEmpty(protoId) || !_proto.TryIndex(protoId, out EntityPrototype? proto))
return;
if (!IsValidTarget(proto, component.Slot))
if (!IsValidTarget(proto, component.Slot, component.RequireTag))
return;
component.Default = protoId;

View File

@@ -512,7 +512,8 @@ public sealed partial class ExplosionSystem
List<(Vector2i GridIndices, Tile Tile)> damagedTiles,
ExplosionPrototype type)
{
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef)
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef
|| tileDef.Indestructible)
return;
if (!CanCreateVacuum)

View File

@@ -356,6 +356,11 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
// + if the bomb is big enough, people outside of it too
// this is capped to 30 because otherwise really huge bombs
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
//
// TODO EXPLOSION redo this.
// Use the Filter.Pvs range-multiplier option instead of AddInRange.
// Also the default PVS range is 25*2 = 50. So capping it at 30 makes no sense here.
// So actually maybe don't use Filter.Pvs at all and only use AddInRange?
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
var filter = Filter.Pvs(pos).AddInRange(pos, audioRange);
var sound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold

View File

@@ -222,8 +222,6 @@ namespace Content.Server.GameTicking
_mind.SetUserId(newMind, data.UserId);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player);
@@ -233,6 +231,9 @@ namespace Content.Server.GameTicking
_mind.TransferTo(newMind, mob);
_roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
var jobName = _jobs.MindTryGetJobName(newMind);
if (lateJoin && !silent)
{
if (jobPrototype.JoinNotifyCrew)

View File

@@ -0,0 +1,37 @@
using Content.Shared.Dataset;
using Robust.Shared.Prototypes;
namespace Content.Server.Ghost.Components;
/// <summary>
/// Causes this entity to react to ghost player using the "Boo!" action by speaking
/// a randomly chosen message from a specified set.
/// </summary>
[RegisterComponent, AutoGenerateComponentPause]
public sealed partial class SpookySpeakerComponent : Component
{
/// <summary>
/// ProtoId of the LocalizedDataset to use for messages.
/// </summary>
[DataField(required: true)]
public ProtoId<LocalizedDatasetPrototype> MessageSet;
/// <summary>
/// Probability that this entity will speak if activated by a Boo action.
/// This is so whole banks of entities don't trigger at the same time.
/// </summary>
[DataField]
public float SpeakChance = 0.5f;
/// <summary>
/// Minimum time that must pass after speaking before this entity can speak again.
/// </summary>
[DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(30);
/// <summary>
/// Time when the cooldown will have elapsed and the entity can speak again.
/// </summary>
[DataField, AutoPausedField]
public TimeSpan NextSpeakTime;
}

View File

@@ -24,6 +24,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -33,6 +34,7 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Ghost
@@ -61,6 +63,8 @@ namespace Content.Server.Ghost
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private EntityQuery<GhostComponent> _ghostQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
@@ -125,7 +129,9 @@ namespace Content.Server.Ghost
if (args.Handled)
return;
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius);
var entities = _lookup.GetEntitiesInRange(args.Performer, component.BooRadius).ToList();
// Shuffle the possible targets so we don't favor any particular entities
_random.Shuffle(entities);
var booCounter = 0;
foreach (var ent in entities)
@@ -139,6 +145,9 @@ namespace Content.Server.Ghost
break;
}
if (booCounter == 0)
_popup.PopupEntity(Loc.GetString("ghost-component-boo-action-failed"), uid, uid);
args.Handled = true;
}

View File

@@ -1,10 +1,12 @@
namespace Content.Server.Ghost;
using Content.Shared.Roles;
namespace Content.Server.Ghost;
/// <summary>
/// This is used to mark Observers properly, as they get Minds
/// </summary>
[RegisterComponent]
public sealed partial class ObserverRoleComponent : Component
public sealed partial class ObserverRoleComponent : BaseMindRoleComponent
{
public string Name => Loc.GetString("observer-role-name");
}

View File

@@ -72,12 +72,16 @@ public sealed partial class GhostRoleComponent : Component
}
}
[DataField("allowSpeech")]
[ViewVariables(VVAccess.ReadWrite)]
/// <summary>
/// The mind roles that will be added to the mob's mind entity
/// </summary>
[DataField, Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // Don't make eye contact
public List<EntProtoId> MindRoles = new() { "MindRoleGhostRoleNeutral" };
[DataField]
public bool AllowSpeech { get; set; } = true;
[DataField("allowMovement")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool AllowMovement { get; set; }
[ViewVariables(VVAccess.ReadOnly)]
@@ -107,3 +111,4 @@ public sealed partial class GhostRoleComponent : Component
[Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // also FIXME Friends
public ProtoId<JobPrototype>? JobProto = null;
}

View File

@@ -9,39 +9,81 @@ namespace Content.Server.Ghost.Roles.Components;
[RegisterComponent, Access(typeof(ToggleableGhostRoleSystem))]
public sealed partial class ToggleableGhostRoleComponent : Component
{
[DataField("examineTextMindPresent")]
/// <summary>
/// The text shown on the entity's Examine when it is controlled by a player
/// </summary>
[DataField]
public string ExamineTextMindPresent = string.Empty;
[DataField("examineTextMindSearching")]
/// <summary>
/// The text shown on the entity's Examine when it is waiting for a controlling player
/// </summary>
[DataField]
public string ExamineTextMindSearching = string.Empty;
[DataField("examineTextNoMind")]
/// <summary>
/// The text shown on the entity's Examine when it has no controlling player
/// </summary>
[DataField]
public string ExamineTextNoMind = string.Empty;
[DataField("beginSearchingText")]
/// <summary>
/// The popup text when the entity (PAI/positronic brain) it is activated to seek a controlling player
/// </summary>
[DataField]
public string BeginSearchingText = string.Empty;
[DataField("roleName")]
/// <summary>
/// The name shown on the Ghost Role list
/// </summary>
[DataField]
public string RoleName = string.Empty;
[DataField("roleDescription")]
/// <summary>
/// The description shown on the Ghost Role list
/// </summary>
[DataField]
public string RoleDescription = string.Empty;
[DataField("roleRules")]
/// <summary>
/// The introductory message shown when trying to take the ghost role/join the raffle
/// </summary>
[DataField]
public string RoleRules = string.Empty;
[DataField("wipeVerbText")]
/// <summary>
/// A list of mind roles that will be added to the entity's mind
/// </summary>
[DataField]
public List<EntProtoId> MindRoles;
/// <summary>
/// The displayed name of the verb to wipe the controlling player
/// </summary>
[DataField]
public string WipeVerbText = string.Empty;
[DataField("wipeVerbPopup")]
/// /// <summary>
/// The popup message when wiping the controlling player
/// </summary>
[DataField]
public string WipeVerbPopup = string.Empty;
[DataField("stopSearchVerbText")]
/// <summary>
/// The displayed name of the verb to stop searching for a controlling player
/// </summary>
[DataField]
public string StopSearchVerbText = string.Empty;
[DataField("stopSearchVerbPopup")]
/// /// <summary>
/// The popup message when stopping to search for a controlling player
/// </summary>
[DataField]
public string StopSearchVerbPopup = string.Empty;
/// /// <summary>
/// The prototype ID of the job that will be given to the controlling mind
/// </summary>
[DataField("job")]
public ProtoId<JobPrototype>? JobProto = null;
public ProtoId<JobPrototype>? JobProto;
}

View File

@@ -3,11 +3,13 @@
namespace Content.Server.Ghost.Roles;
/// <summary>
/// This is used for round end display of ghost roles.
/// It may also be used to ensure some ghost roles count as antagonists in future.
/// Added to mind role entities to tag that they are a ghostrole.
/// It also holds the name for the round end display
/// </summary>
[RegisterComponent]
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
{
[DataField("name")] public string? Name;
//TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it
[DataField] public string? Name;
}

View File

@@ -33,7 +33,6 @@ using Content.Server.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Collections;
using Content.Shared.Ghost.Roles.Components;
using Content.Shared.Roles.Jobs;
namespace Content.Server.Ghost.Roles;
@@ -514,13 +513,13 @@ public sealed class GhostRoleSystem : EntitySystem
var newMind = _mindSystem.CreateMind(player.UserId,
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
_mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob);
_roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp);
if (_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
markerRole.Value.Comp2.Name = role.RoleName;
_mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob);
}
/// <summary>

View File

@@ -51,10 +51,13 @@ public sealed class ToggleableGhostRoleSystem : EntitySystem
var ghostRole = EnsureComp<GhostRoleComponent>(uid);
EnsureComp<GhostTakeoverAvailableComponent>(uid);
//GhostRoleComponent inherits custom settings from the ToggleableGhostRoleComponent
ghostRole.RoleName = Loc.GetString(component.RoleName);
ghostRole.RoleDescription = Loc.GetString(component.RoleDescription);
ghostRole.RoleRules = Loc.GetString(component.RoleRules);
ghostRole.JobProto = component.JobProto;
ghostRole.MindRoles = component.MindRoles;
}
private void OnExamined(EntityUid uid, ToggleableGhostRoleComponent component, ExaminedEvent args)

View File

@@ -0,0 +1,51 @@
using Content.Server.Chat.Systems;
using Content.Server.Ghost.Components;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Ghost;
public sealed class SpookySpeakerSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpookySpeakerComponent, GhostBooEvent>(OnGhostBoo);
}
private void OnGhostBoo(Entity<SpookySpeakerComponent> entity, ref GhostBooEvent args)
{
// Only activate sometimes, so groups don't all trigger together
if (!_random.Prob(entity.Comp.SpeakChance))
return;
var curTime = _timing.CurTime;
// Enforce a delay between messages to prevent spam
if (curTime < entity.Comp.NextSpeakTime)
return;
if (!_proto.TryIndex(entity.Comp.MessageSet, out var messages))
return;
// Grab a random localized message from the set
var message = _random.Pick(messages);
// Chatcode moment: messages starting with '.' are considered radio messages unless prefixed with '>'
// So this is a stupid trick to make the "...Oooo"-style messages work.
message = '>' + message;
// Say the message
_chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, hideChat: true);
// Set the delay for the next message
entity.Comp.NextSpeakTime = curTime + entity.Comp.Cooldown;
args.Handled = true;
}
}

View File

@@ -10,6 +10,7 @@ using Content.Shared.Holopad;
using Content.Shared.IdentityManagement;
using Content.Shared.Labels.Components;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Speech;
using Content.Shared.Telephone;
using Content.Shared.UserInterface;
using Content.Shared.Verbs;
@@ -179,11 +180,15 @@ public sealed class HolopadSystem : SharedHolopadSystem
// AI broadcasting
if (TryComp<StationAiHeldComponent>(args.Actor, out var stationAiHeld))
{
// Link the AI to the holopad they are broadcasting from
LinkHolopadToUser(source, args.Actor);
if (!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore) ||
stationAiCore.Value.Comp.RemoteEntity == null ||
!TryComp<HolopadComponent>(stationAiCore, out var stationAiCoreHolopad))
return;
// Execute the broadcast, but have it originate from the AI core
ExecuteBroadcast((stationAiCore.Value, stationAiCoreHolopad), args.Actor);
// Switch the AI's perspective from free roaming to the target holopad
@@ -381,7 +386,10 @@ public sealed class HolopadSystem : SharedHolopadSystem
if (TryComp<TelephoneComponent>(linkedHolopad, out var linkedHolopadTelephone) && linkedHolopadTelephone.Muted)
continue;
foreach (var receiver in GetLinkedHolopads(linkedHolopad))
var receivingHolopads = GetLinkedHolopads(linkedHolopad);
var range = receivingHolopads.Count > 1 ? ChatTransmitRange.HideChat : ChatTransmitRange.GhostRangeLimit;
foreach (var receiver in receivingHolopads)
{
if (receiver.Comp.Hologram == null)
continue;
@@ -391,7 +399,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
var name = Loc.GetString("holopad-hologram-name", ("name", ent));
// Force the emote, because if the user can do it, the hologram can too
_chatSystem.TryEmoteWithChat(receiver.Comp.Hologram.Value, args.Emote, ChatTransmitRange.Normal, false, name, true, true);
_chatSystem.TryEmoteWithChat(receiver.Comp.Hologram.Value, args.Emote, range, false, name, true, true);
}
}
}
@@ -521,16 +529,23 @@ public sealed class HolopadSystem : SharedHolopadSystem
entity.Comp.HologramProtoId == null)
return;
var uid = Spawn(entity.Comp.HologramProtoId, Transform(entity).Coordinates);
var hologramUid = Spawn(entity.Comp.HologramProtoId, Transform(entity).Coordinates);
// Safeguard - spawned holograms must have this component
if (!TryComp<HolopadHologramComponent>(uid, out var component))
if (!TryComp<HolopadHologramComponent>(hologramUid, out var holopadHologram))
{
Del(uid);
Del(hologramUid);
return;
}
entity.Comp.Hologram = new Entity<HolopadHologramComponent>(uid, component);
entity.Comp.Hologram = new Entity<HolopadHologramComponent>(hologramUid, holopadHologram);
// Relay speech preferentially through the hologram
if (TryComp<SpeechComponent>(hologramUid, out var hologramSpeech) &&
TryComp<TelephoneComponent>(entity, out var entityTelephone))
{
_telephoneSystem.SetSpeakerForTelephone((entity, entityTelephone), (hologramUid, hologramSpeech));
}
}
private void DeleteHologram(Entity<HolopadHologramComponent> hologram, Entity<HolopadComponent> attachedHolopad)
@@ -608,10 +623,25 @@ public sealed class HolopadSystem : SharedHolopadSystem
DeleteHologram(entity.Comp.Hologram.Value, entity);
if (entity.Comp.User != null)
UnlinkHolopadFromUser(entity, entity.Comp.User.Value);
{
// Check if the associated holopad user is an AI
if (TryComp<StationAiHeldComponent>(entity.Comp.User, out var stationAiHeld) &&
_stationAiSystem.TryGetStationAiCore((entity.Comp.User.Value, stationAiHeld), out var stationAiCore))
{
// Return the AI eye to free roaming
_stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, true);
if (TryComp<StationAiCoreComponent>(entity, out var stationAiCore))
_stationAiSystem.SwitchRemoteEntityMode((entity.Owner, stationAiCore), true);
// If the AI core is still broadcasting, end its calls
if (entity.Owner != stationAiCore.Value.Owner &&
TryComp<TelephoneComponent>(stationAiCore, out var stationAiCoreTelephone) &&
_telephoneSystem.IsTelephoneEngaged((stationAiCore.Value.Owner, stationAiCoreTelephone)))
{
_telephoneSystem.EndTelephoneCalls((stationAiCore.Value.Owner, stationAiCoreTelephone));
}
}
UnlinkHolopadFromUser(entity, entity.Comp.User.Value);
}
Dirty(entity);
}

View File

@@ -165,7 +165,7 @@ namespace Content.Server.Kitchen.EntitySystems
QueueDel(gib);
}
_audio.PlayEntity(component.SpikeSound, Filter.Pvs(uid), uid, true);
_audio.PlayPvs(component.SpikeSound, uid);
}
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,

View File

@@ -153,7 +153,7 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
else
{
_battery.SetCharge(entity.Owner, battery.CurrentCharge + entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency, battery);
if (battery.IsFullyCharged)
if (_battery.IsFull(entity, battery))
{
if (TryComp<ApcPowerReceiverComponent>(entity.Owner, out var receiver))
{

View File

@@ -275,7 +275,7 @@ namespace Content.Server.Light.EntitySystems
if (time > light.LastThunk + ThunkDelay)
{
light.LastThunk = time;
_audio.PlayEntity(light.TurnOnSound, Filter.Pvs(uid), uid, true, AudioParams.Default.WithVolume(-10f));
_audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f));
}
}
else

View File

@@ -106,6 +106,6 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
// repeat the doafter until battery is full
return !battery.IsFullyCharged;
return !_battery.IsFull(ent, battery);
}
}

View File

@@ -235,7 +235,7 @@ public sealed class NukeSystem : EntitySystem
private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args)
{
_audio.PlayEntity(component.KeypadPressSound, Filter.Pvs(uid), uid, true);
_audio.PlayPvs(component.KeypadPressSound, uid);
if (component.Status != NukeStatus.AWAIT_CODE)
return;
@@ -351,12 +351,12 @@ public sealed class NukeSystem : EntitySystem
{
component.Status = NukeStatus.AWAIT_ARM;
component.RemainingTime = component.Timer;
_audio.PlayEntity(component.AccessGrantedSound, Filter.Pvs(uid), uid, true);
_audio.PlayPvs(component.AccessGrantedSound, uid);
}
else
{
component.EnteredCode = "";
_audio.PlayEntity(component.AccessDeniedSound, Filter.Pvs(uid), uid, true);
_audio.PlayPvs(component.AccessDeniedSound, uid);
}
break;
@@ -425,7 +425,9 @@ public sealed class NukeSystem : EntitySystem
// Don't double-dip on the octave shifting
component.LastPlayedKeypadSemitones = number == 0 ? component.LastPlayedKeypadSemitones : semitoneShift;
_audio.PlayEntity(component.KeypadPressSound, Filter.Pvs(uid), uid, true, AudioHelpers.ShiftSemitone(semitoneShift).WithVolume(-5f));
var opts = component.KeypadPressSound.Params;
opts = AudioHelpers.ShiftSemitone(opts, semitoneShift).AddVolume(-5f);
_audio.PlayPvs(component.KeypadPressSound, uid, opts);
}
public string GenerateRandomNumberString(int length)

View File

@@ -165,6 +165,9 @@ public sealed class DrinkSystem : SharedDrinkSystem
if (!HasComp<BodyComponent>(target))
return false;
if (!_body.TryGetBodyOrganEntityComps<StomachComponent>(target, out var stomachs))
return false;
if (_openable.IsClosed(item, user))
return true;

View File

@@ -45,10 +45,7 @@ public sealed class HelpProgressConditionSystem : EntitySystem
return;
}
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind)
.Select(pair => pair.Item1)
.ToHashSet();
var removeList = new List<EntityUid>();
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
// cant help anyone who is tasked with helping:
// 1. thats boring
@@ -56,19 +53,26 @@ public sealed class HelpProgressConditionSystem : EntitySystem
foreach (var traitor in traitors)
{
// TODO: replace this with TryComp<ObjectivesComponent>(traitor) or something when objectives are moved out of mind
if (!TryComp<MindComponent>(traitor, out var mind))
if (!TryComp<MindComponent>(traitor.Id, out var mind))
continue;
foreach (var objective in mind.Objectives)
{
if (HasComp<HelpProgressConditionComponent>(objective))
removeList.Add(traitor);
traitors.RemoveWhere(x => x.Mind == mind);
}
}
foreach (var tot in removeList)
// Can't have multiple objectives to help/save the same person
foreach (var objective in args.Mind.Objectives)
{
traitors.Remove(tot);
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
{
if (TryComp<TargetObjectiveComponent>(objective, out var help))
{
traitors.RemoveWhere(x => x.Id == help.Target);
}
}
}
// no more helpable traitors
@@ -78,7 +82,7 @@ public sealed class HelpProgressConditionSystem : EntitySystem
return;
}
_target.SetTarget(uid, _random.Pick(traitors), target);
_target.SetTarget(uid, _random.Pick(traitors).Id, target);
}
private float GetProgress(EntityUid target)

View File

@@ -44,7 +44,19 @@ public sealed class KeepAliveConditionSystem : EntitySystem
return;
}
var traitors = Enumerable.ToList<(EntityUid Id, MindComponent Mind)>(_traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind));
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
// Can't have multiple objectives to help/save the same person
foreach (var objective in args.Mind.Objectives)
{
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
{
if (TryComp<TargetObjectiveComponent>(objective, out var help))
{
traitors.RemoveWhere(x => x.Id == help.Target);
}
}
}
// You are the first/only traitor.
if (traitors.Count == 0)

View File

@@ -6,6 +6,7 @@ using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Robust.Shared.Configuration;
using Robust.Shared.Random;
using System.Linq;
namespace Content.Server.Objectives.Systems;
@@ -52,8 +53,18 @@ public sealed class KillPersonConditionSystem : EntitySystem
if (target.Target != null)
return;
// no other humans to kill
var allHumans = _mind.GetAliveHumans(args.MindId);
// Can't have multiple objectives to kill the same person
foreach (var objective in args.Mind.Objectives)
{
if (HasComp<KillPersonConditionComponent>(objective) && TryComp<TargetObjectiveComponent>(objective, out var kill))
{
allHumans.RemoveWhere(x => x.Owner == kill.Target);
}
}
// no other humans to kill
if (allHumans.Count == 0)
{
args.Cancelled = true;

View File

@@ -29,6 +29,7 @@ public sealed class StealConditionSystem : EntitySystem
private EntityQuery<ContainerManagerComponent> _containerQuery;
private HashSet<Entity<TransformComponent>> _nearestEnts = new();
private HashSet<EntityUid> _countedItems = new();
public override void Initialize()
{
@@ -104,6 +105,8 @@ public sealed class StealConditionSystem : EntitySystem
var containerStack = new Stack<ContainerManagerComponent>();
var count = 0;
_countedItems.Clear();
//check stealAreas
if (condition.CheckStealAreas)
{
@@ -174,6 +177,9 @@ public sealed class StealConditionSystem : EntitySystem
private int CheckStealTarget(EntityUid entity, StealConditionComponent condition)
{
if (_countedItems.Contains(entity))
return 0;
// check if this is the target
if (!TryComp<StealTargetComponent>(entity, out var target))
return 0;
@@ -196,6 +202,8 @@ public sealed class StealConditionSystem : EntitySystem
}
}
_countedItems.Add(entity);
return TryComp<StackComponent>(entity, out var stack) ? stack.Count : 1;
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Access.Systems;
using Content.Server.AlertLevel;
using Content.Server.CartridgeLoader;
using Content.Server.Chat.Managers;
@@ -36,6 +37,7 @@ namespace Content.Server.PDA
[Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly UnpoweredFlashlightSystem _unpoweredFlashlight = default!;
[Dependency] private readonly ContainerSystem _containerSystem = default!;
[Dependency] private readonly IdCardSystem _idCard = default!;
public override void Initialize()
{
@@ -55,22 +57,28 @@ namespace Content.Server.PDA
SubscribeLocalEvent<PdaComponent, CartridgeLoaderNotificationSentEvent>(OnNotification);
SubscribeLocalEvent<StationRenamedEvent>(OnStationRenamed);
SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed);
SubscribeLocalEvent<EntityRenamedEvent>(OnEntityRenamed, after: new[] { typeof(IdCardSystem) });
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
}
private void OnEntityRenamed(ref EntityRenamedEvent ev)
{
if (HasComp<IdCardComponent>(ev.Uid))
return;
if (_idCard.TryFindIdCard(ev.Uid, out var idCard))
{
var query = EntityQueryEnumerator<PdaComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.PdaOwner == ev.Uid)
if (comp.ContainedId == idCard)
{
SetOwner(uid, comp, ev.Uid, ev.NewName);
}
}
}
}
protected override void OnComponentInit(EntityUid uid, PdaComponent pda, ComponentInit args)
{
@@ -86,6 +94,9 @@ namespace Content.Server.PDA
protected override void OnItemInserted(EntityUid uid, PdaComponent pda, EntInsertedIntoContainerMessage args)
{
base.OnItemInserted(uid, pda, args);
var id = CompOrNull<IdCardComponent>(pda.ContainedId);
if (id != null)
pda.OwnerName = id.FullName;
UpdatePdaUi(uid, pda);
}

View File

@@ -49,8 +49,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundEnd);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAdd);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemove);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleEvent);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleEvent);
SubscribeLocalEvent<AFKEvent>(OnAFK);
SubscribeLocalEvent<UnAFKEvent>(OnUnAFK);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
@@ -121,13 +121,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
return GetTimedRoles(contentData.Mind.Value);
}
private void OnRoleRemove(RoleRemovedEvent ev)
{
if (_minds.TryGetSession(ev.Mind, out var session))
_tracking.QueueRefreshTrackers(session);
}
private void OnRoleAdd(RoleAddedEvent ev)
private void OnRoleEvent(RoleEvent ev)
{
if (_minds.TryGetSession(ev.Mind, out var session))
_tracking.QueueRefreshTrackers(session);

View File

@@ -24,12 +24,6 @@ namespace Content.Server.Power.Components
[DataField("startingCharge")]
public float CurrentCharge;
/// <summary>
/// True if the battery is fully charged.
/// </summary>
[ViewVariables]
public bool IsFullyCharged => MathHelper.CloseToPercent(CurrentCharge, MaxCharge);
/// <summary>
/// The price per one joule. Default is 1 credit for 10kJ.
/// </summary>

View File

@@ -87,8 +87,8 @@ namespace Content.Server.Power.EntitySystems
var query = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
while (query.MoveNext(out var uid, out var comp, out var batt))
{
if (!comp.AutoRecharge) continue;
if (batt.IsFullyCharged) continue;
if (!comp.AutoRecharge || IsFull(uid, batt))
continue;
if (comp.AutoRechargePause)
{
@@ -212,14 +212,14 @@ namespace Content.Server.Power.EntitySystems
}
/// <summary>
/// Returns whether the battery is at least 99% charged, basically full.
/// Returns whether the battery is full.
/// </summary>
public bool IsFull(EntityUid uid, BatteryComponent? battery = null)
{
if (!Resolve(uid, ref battery))
return false;
return battery.CurrentCharge / battery.MaxCharge >= 0.99f;
return battery.CurrentCharge >= battery.MaxCharge;
}
}
}

View File

@@ -223,10 +223,10 @@ internal sealed class ChargerSystem : EntitySystem
if (container.ContainedEntities.Count == 0)
return CellChargerStatus.Empty;
if (!SearchForBattery(container.ContainedEntities[0], out _, out var heldBattery))
if (!SearchForBattery(container.ContainedEntities[0], out var heldEnt, out var heldBattery))
return CellChargerStatus.Off;
if (Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01)
if (_battery.IsFull(heldEnt.Value, heldBattery))
return CellChargerStatus.Charged;
return CellChargerStatus.Charging;
@@ -247,12 +247,6 @@ internal sealed class ChargerSystem : EntitySystem
return;
_battery.SetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * frameTime, heldBattery);
// Just so the sprite won't be set to 99.99999% visibility
if (heldBattery.MaxCharge - heldBattery.CurrentCharge < 0.01)
{
_battery.SetCharge(batteryUid.Value, heldBattery.MaxCharge, heldBattery);
}
UpdateStatus(uid, component);
}

View File

@@ -29,7 +29,7 @@ public sealed partial class ChemicalFuelGeneratorAdapterComponent : Component
/// <summary>
/// The solution on the <see cref="SolutionContainerManagerComponent"/> to use.
/// </summary>
[DataField("solutionRef")]
[ViewVariables]
public Entity<SolutionComponent>? Solution = null;
/// <summary>

View File

@@ -117,7 +117,7 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem
var clogged = _generator.GetIsClogged(uid);
var sound = empty ? component.StartSoundEmpty : component.StartSound;
_audio.PlayEntity(sound, Filter.Pvs(uid), uid, true);
_audio.PlayPvs(sound, uid);
if (!clogged && !empty && _random.Prob(component.StartChance))
{

View File

@@ -19,10 +19,25 @@ public sealed class JobSystem : SharedJobSystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindComponent, MindRoleAddedEvent>(MindOnDoGreeting);
SubscribeLocalEvent<RoleAddedEvent>(OnRoleAddedEvent);
SubscribeLocalEvent<RoleRemovedEvent>(OnRoleRemovedEvent);
}
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
private void OnRoleAddedEvent(RoleAddedEvent args)
{
MindOnDoGreeting(args.MindId, args.Mind, args);
if (args.RoleTypeUpdate)
_roles.RoleUpdateMessage(args.Mind);
}
private void OnRoleRemovedEvent(RoleRemovedEvent args)
{
if (args.RoleTypeUpdate)
_roles.RoleUpdateMessage(args.Mind);
}
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, RoleAddedEvent args)
{
if (args.Silent)
return;

View File

@@ -1,11 +1,13 @@
namespace Content.Server.Roles;
using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// Adds a briefing to the character info menu, does nothing else.
/// </summary>
[RegisterComponent]
public sealed partial class RoleBriefingComponent : Component
public sealed partial class RoleBriefingComponent : BaseMindRoleComponent
{
[DataField("briefing"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public string Briefing;
}

View File

@@ -1,10 +1,16 @@
using Content.Server.Chat.Managers;
using Content.Shared.Chat;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Server.Roles;
public sealed class RoleSystem : SharedRoleSystem
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public string? MindGetBriefing(EntityUid? mindId)
{
if (mindId == null)
@@ -37,6 +43,32 @@ public sealed class RoleSystem : SharedRoleSystem
return ev.Briefing;
}
public void RoleUpdateMessage(MindComponent mind)
{
if (mind.Session is null)
return;
if (!_proto.TryIndex(mind.RoleType, out var proto))
return;
var roleText = Loc.GetString(proto.Name);
var color = proto.Color;
var session = mind.Session;
//TODO add audio? Would need to be optional so it does not play on role changes that already come with their own audio
// _audio.PlayGlobal(Sound, session);
var message = Loc.GetString("role-type-update-message", ("color", color), ("role", roleText));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chat.ChatMessageToOne(ChatChannel.Server,
message,
wrappedMessage,
default,
false,
session.Channel);
}
}
/// <summary>

View File

@@ -1,5 +1,7 @@
using Content.Shared.Containers.ItemSlots;
using Content.Server.Roles;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Silicons.Borgs.Components;
using Robust.Shared.Containers;
@@ -8,6 +10,9 @@ namespace Content.Server.Silicons.Borgs;
/// <inheritdoc/>
public sealed partial class BorgSystem
{
[Dependency] private readonly SharedRoleSystem _roles = default!;
public void InitializeMMI()
{
SubscribeLocalEvent<MMIComponent, ComponentInit>(OnMMIInit);
@@ -41,8 +46,13 @@ public sealed partial class BorgSystem
Dirty(uid, component);
if (_mind.TryGetMind(ent, out var mindId, out var mind))
{
_mind.TransferTo(mindId, uid, true, mind: mind);
if (!_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSiliconBrain", silent: true);
}
_appearance.SetData(uid, MMIVisuals.BrainPresent, true);
}
@@ -75,7 +85,12 @@ public sealed partial class BorgSystem
RemComp(uid, component);
if (_mind.TryGetMind(linked, out var mindId, out var mind))
{
if (_roles.MindHasRole<SiliconBrainRoleComponent>(mindId))
_roles.MindRemoveRole<SiliconBrainRoleComponent>(mindId);
_mind.TransferTo(mindId, uid, true, mind: mind);
}
_appearance.SetData(linked, MMIVisuals.BrainPresent, false);
}

View File

@@ -5,7 +5,6 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared.Access.Systems;
using Content.Shared.Alert;
using Content.Shared.Database;
using Content.Shared.IdentityManagement;

View File

@@ -193,7 +193,7 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
private void EnsureSubvertedSiliconRole(EntityUid mindId)
{
if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
_roles.MindAddRole(mindId, "MindRoleSubvertedSilicon", silent: true);
}
private void RemoveSubvertedSiliconRole(EntityUid mindId)

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Speech.Components;
/// <summary>
/// Makes this entity speak like a sheep or a goat in all chat messages it sends.
/// </summary>
[RegisterComponent]
public sealed partial class BleatingAccentComponent : Component { }

View File

@@ -0,0 +1,28 @@
using System.Text.RegularExpressions;
using Content.Server.Speech.Components;
namespace Content.Server.Speech.EntitySystems;
public sealed partial class BleatingAccentSystem : EntitySystem
{
private static readonly Regex BleatRegex = new("([mbdlpwhrkcnytfo])([aiu])", RegexOptions.IgnoreCase);
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BleatingAccentComponent, AccentGetEvent>(OnAccentGet);
}
private void OnAccentGet(Entity<BleatingAccentComponent> entity, ref AccentGetEvent args)
{
args.Message = Accentuate(args.Message);
}
public static string Accentuate(string message)
{
// Repeats the vowel in certain consonant-vowel pairs
// So you taaaalk liiiike thiiiis
return BleatRegex.Replace(message, "$1$2$2$2$2");
}
}

View File

@@ -94,11 +94,11 @@ public sealed class GreytideVirusRule : StationEventSystem<GreytideVirusRuleComp
continue;
// use the access reader from the door electronics if they exist
if (!_access.GetMainAccessReader(airlockUid, out var accessComp))
if (!_access.GetMainAccessReader(airlockUid, out var accessEnt))
continue;
// check access
if (!_access.AreAccessTagsAllowed(accessIds, accessComp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessComp))
if (!_access.AreAccessTagsAllowed(accessIds, accessEnt.Value.Comp) || _access.AreAccessTagsAllowed(virusComp.Blacklist, accessEnt.Value.Comp))
continue;
// open and bolt airlocks

View File

@@ -16,6 +16,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Prototypes;
using Content.Server.Shuttles.Components;
namespace Content.Server.Storage.EntitySystems;
@@ -47,6 +48,8 @@ public sealed class BluespaceLockerSystem : EntitySystem
if (component.BehaviorProperties.BluespaceEffectOnInit)
BluespaceEffect(uid, component, component, true);
EnsureComp<ArrivalsBlacklistComponent>(uid); // To stop people getting to arrivals terminal
}
public void BluespaceEffect(EntityUid effectTargetUid, BluespaceLockerComponent effectSourceComponent, BluespaceLockerComponent? effectTargetComponent, bool bypassLimit = false)

View File

@@ -7,8 +7,11 @@ using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Labels.Components;
using Content.Shared.Mind.Components;
using Content.Shared.Power;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Speech;
using Content.Shared.Telephone;
using Robust.Server.GameObjects;
@@ -19,8 +22,6 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Replays;
using System.Linq;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Silicons.Borgs.Components;
namespace Content.Server.Telephone;
@@ -104,12 +105,17 @@ public sealed class TelephoneSystem : SharedTelephoneSystem
var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource));
RaiseLocalEvent(args.MessageSource, nameEv);
var name = Loc.GetString("speech-name-relay",
("speaker", Name(entity)),
("originalName", nameEv.VoiceName));
// Determine if speech should be relayed via the telephone itself or a designated speaker
var speaker = entity.Comp.Speaker != null ? entity.Comp.Speaker.Value.Owner : entity.Owner;
var name = Loc.GetString("chat-telephone-name-relay",
("originalName", nameEv.VoiceName),
("speaker", Name(speaker)));
var range = args.TelephoneSource.Comp.LinkedTelephones.Count > 1 ? ChatTransmitRange.HideChat : ChatTransmitRange.GhostRangeLimit;
var volume = entity.Comp.SpeakerVolume == TelephoneVolume.Speak ? InGameICChatType.Speak : InGameICChatType.Whisper;
_chat.TrySendInGameICMessage(entity, args.Message, volume, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false);
_chat.TrySendInGameICMessage(speaker, args.Message, volume, range, nameOverride: name, checkRadioPrefix: false);
}
#endregion
@@ -151,7 +157,7 @@ public sealed class TelephoneSystem : SharedTelephoneSystem
break;
// Try to hang up if their has been no recent in-call activity
// Try to hang up if there has been no recent in-call activity
case TelephoneState.InCall:
if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.IdlingTimeout))
EndTelephoneCalls(entity);
@@ -214,7 +220,15 @@ public sealed class TelephoneSystem : SharedTelephoneSystem
source.Comp.LinkedTelephones.Add(receiver);
source.Comp.Muted = options?.MuteSource == true;
receiver.Comp.LastCallerId = GetNameAndJobOfCallingEntity(user); // This will be networked when the state changes
var callerInfo = GetNameAndJobOfCallingEntity(user);
// Base the name of the device on its label
string? deviceName = null;
if (TryComp<LabelComponent>(source, out var label))
deviceName = label.CurrentLabel;
receiver.Comp.LastCallerId = (callerInfo.Item1, callerInfo.Item2, deviceName); // This will be networked when the state changes
receiver.Comp.LinkedTelephones.Add(source);
receiver.Comp.Muted = options?.MuteReceiver == true;
@@ -401,6 +415,11 @@ public sealed class TelephoneSystem : SharedTelephoneSystem
}
}
public void SetSpeakerForTelephone(Entity<TelephoneComponent> entity, Entity<SpeechComponent>? speaker)
{
entity.Comp.Speaker = speaker;
}
private (string?, string?) GetNameAndJobOfCallingEntity(EntityUid uid)
{
string? presumedName = null;

View File

@@ -439,4 +439,9 @@ public enum LogType
/// A ghost warped to an entity through the ghost warp menu.
/// </summary>
GhostWarp = 95,
/// <summary>
/// A player interacted with a PDA or its cartridge component
/// </summary>
PdaInteract = 96,
}

View File

@@ -113,25 +113,25 @@ public sealed class AccessReaderSystem : EntitySystem
return false;
}
public bool GetMainAccessReader(EntityUid uid, [NotNullWhen(true)] out AccessReaderComponent? component)
public bool GetMainAccessReader(EntityUid uid, [NotNullWhen(true)] out Entity<AccessReaderComponent>? ent)
{
component = null;
if (!TryComp(uid, out AccessReaderComponent? accessReader))
ent = null;
if (!TryComp<AccessReaderComponent>(uid, out var accessReader))
return false;
component = accessReader;
ent = (uid, accessReader);
if (component.ContainerAccessProvider == null)
if (ent.Value.Comp.ContainerAccessProvider == null)
return true;
if (!_containerSystem.TryGetContainer(uid, component.ContainerAccessProvider, out var container))
if (!_containerSystem.TryGetContainer(uid, ent.Value.Comp.ContainerAccessProvider, out var container))
return true;
foreach (var entity in container.ContainedEntities)
{
if (TryComp(entity, out AccessReaderComponent? containedReader))
if (TryComp<AccessReaderComponent>(entity, out var containedReader))
{
component = containedReader;
ent = (entity, containedReader);
return true;
}
}

View File

@@ -33,7 +33,8 @@ public abstract class SharedIdCardSystem : EntitySystem
// When a player gets renamed their id card is renamed as well to match.
// Unfortunately since TryFindIdCard will succeed if the entity is also a card this means that the card will
// keep renaming itself unless we return early.
if (HasComp<IdCardComponent>(ev.Uid))
// We also do not include the PDA itself being renamed, as that triggers the same event (e.g. for chameleon PDAs).
if (HasComp<IdCardComponent>(ev.Uid) || HasComp<PdaComponent>(ev.Uid))
return;
if (TryFindIdCard(ev.Uid, out var idCard))

View File

@@ -1,3 +1,4 @@
using Content.Shared.Mind;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
@@ -10,6 +11,7 @@ public sealed record PlayerInfo(
string IdentityName,
string StartingJob,
bool Antag,
RoleTypePrototype RoleProto,
NetEntity? NetEntity,
NetUserId SessionId,
bool Connected,

View File

@@ -28,7 +28,7 @@ public sealed partial class UdderComponent : Component
/// <summary>
/// The solution to add reagent to.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
[ViewVariables(VVAccess.ReadOnly)]
public Entity<SolutionComponent>? Solution = null;
/// <summary>

View File

@@ -28,7 +28,7 @@ public sealed partial class WoolyComponent : Component
/// <summary>
/// The solution to add reagent to.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
[ViewVariables(VVAccess.ReadOnly)]
public Entity<SolutionComponent>? Solution;
/// <summary>

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