Merge branch 'master' of https://github.com/space-wizards/space-station-14 into map-load-refactor
This commit is contained in:
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build-map-renderer.yml
vendored
2
.github/workflows/build-map-renderer.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/build-test-debug.yml
vendored
2
.github/workflows/build-test-debug.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/publish.yml
vendored
2
.github/workflows/publish.yml
vendored
@@ -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: |
|
||||
|
||||
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/yaml-linter.yml
vendored
2
.github/workflows/yaml-linter.yml
vendored
@@ -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
|
||||
|
||||
@@ -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,12 +80,20 @@ 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);
|
||||
}
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates+lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -83,10 +83,10 @@ public sealed partial class PumpControl : BoxContainer
|
||||
PumpDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
PumpDataCopied?.Invoke(_data);
|
||||
};
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
PumpDataCopied?.Invoke(_data);
|
||||
};
|
||||
}
|
||||
|
||||
public void ChangeData(GasVentPumpData data)
|
||||
|
||||
@@ -72,10 +72,10 @@ public sealed partial class ScrubberControl : BoxContainer
|
||||
ScrubberDataChanged?.Invoke(_address, _data);
|
||||
};
|
||||
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
ScrubberDataCopied?.Invoke(_data);
|
||||
};
|
||||
_copySettings.OnPressed += _ =>
|
||||
{
|
||||
ScrubberDataCopied?.Invoke(_data);
|
||||
};
|
||||
|
||||
foreach (var value in Enum.GetValues<Gas>())
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,7 +41,24 @@ public sealed class ChameleonBoundUserInterface : BoundUserInterface
|
||||
return;
|
||||
|
||||
var targets = _chameleon.GetValidTargets(st.Slot);
|
||||
_menu?.UpdateState(targets, st.SelectedId);
|
||||
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)
|
||||
|
||||
@@ -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,16 +74,23 @@
|
||||
</PanelContainer>
|
||||
|
||||
<PanelContainer Name="HolopadContactListPanel">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="8, 8, 8, 8" MinHeight="256">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
<!-- If there is no data yet, this will be displayed -->
|
||||
<BoxContainer Name="FetchingAvailableHolopadsContainer" HorizontalAlignment="Center" HorizontalExpand="True" VerticalExpand="True" ReservesSpace="False">
|
||||
<Label Text="{Loc 'holopad-window-fetching-contacts-list'}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<!-- Contact filter -->
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True" Margin="4, 4, 4, 0"
|
||||
PlaceHolder="{Loc holopad-window-filter-line-placeholder}" />
|
||||
|
||||
<!-- Container for the contacts -->
|
||||
<BoxContainer Name="ContactsList" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="8, 8, 8, 8" MinHeight="256">
|
||||
|
||||
<!-- If there is no data yet, this will be displayed -->
|
||||
<BoxContainer Name="FetchingAvailableHolopadsContainer" HorizontalAlignment="Center" HorizontalExpand="True" VerticalExpand="True" ReservesSpace="False">
|
||||
<Label Text="{Loc 'holopad-window-fetching-contacts-list'}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Container for the contacts -->
|
||||
<BoxContainer Name="ContactsList" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="10 0 10 0"/>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -36,17 +36,18 @@ 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)
|
||||
{
|
||||
_entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
|
||||
Sprite.SetEntity(_entity);
|
||||
if (ent == null)
|
||||
return;
|
||||
|
||||
var spriteTooltip = new Tooltip();
|
||||
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
|
||||
TooltipSupplier = _ => spriteTooltip;
|
||||
}
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
30
Content.Client/PDA/PdaVisualizerSystem.cs
Normal file
30
Content.Client/PDA/PdaVisualizerSystem.cs
Normal 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
|
||||
}
|
||||
}
|
||||
14
Content.Client/PDA/PdaVisualsComponent.cs
Normal file
14
Content.Client/PDA/PdaVisualsComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public sealed partial class SensorMonitoringWindow : FancyWindow, IComputerWindo
|
||||
public SensorMonitoringWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void UpdateState(ConsoleUIState state)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -65,9 +65,8 @@ namespace Content.IntegrationTests.Tests
|
||||
"Cog",
|
||||
"Gate",
|
||||
"Amber",
|
||||
"Loop"
|
||||
|
||||
|
||||
"Loop",
|
||||
"Elkridge"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}]");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -96,6 +96,6 @@ public sealed partial class PlantHolderComponent : Component
|
||||
[DataField]
|
||||
public string SoilSolutionName = "soil";
|
||||
|
||||
[DataField]
|
||||
[ViewVariables]
|
||||
public Entity<SolutionComponent>? SoilSolution = null;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal file
37
Content.Server/Ghost/Components/SpookySpeakerComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
|
||||
markerRole.Value.Comp2.Name = role.RoleName;
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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)
|
||||
|
||||
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal file
51
Content.Server/Ghost/SpookySpeakerSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,19 +57,25 @@ 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)
|
||||
{
|
||||
var query = EntityQueryEnumerator<PdaComponent>();
|
||||
if (HasComp<IdCardComponent>(ev.Uid))
|
||||
return;
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
if (_idCard.TryFindIdCard(ev.Uid, out var idCard))
|
||||
{
|
||||
if (comp.PdaOwner == ev.Uid)
|
||||
var query = EntityQueryEnumerator<PdaComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
SetOwner(uid, comp, ev.Uid, ev.NewName);
|
||||
if (comp.ContainedId == idCard)
|
||||
{
|
||||
SetOwner(uid, comp, ev.Uid, ev.NewName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 { }
|
||||
28
Content.Server/Speech/EntitySystems/BleatingAccentSystem.cs
Normal file
28
Content.Server/Speech/EntitySystems/BleatingAccentSystem.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user