Compare commits

..
Author SHA1 Message Date
Janet Blackquill fc2d05f548 ser/de 2026-06-20 06:10:53 -05:00
Janet Blackquill 4c7f99296c and that's a wrap 2026-06-20 06:10:53 -05:00
pre-commit-ci[bot] df1e97986a [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-06-20 06:10:53 -05:00
Janet Blackquill 88203bb6b2 and that's a wrap 2026-06-20 06:10:53 -05:00
Janet Blackquill 158a114804 nuke me harder 2026-06-20 06:10:53 -05:00
Janet Blackquill d438aec056 she rise up until i don't have a round end condition yet 2026-06-20 06:10:53 -05:00
483 changed files with 5170 additions and 15836 deletions
-9
View File
@@ -12,21 +12,12 @@ jobs:
- name: Setup submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
- name: Update Engine Submodules
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
+1 -9
View File
@@ -24,11 +24,7 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -36,10 +32,6 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
-8
View File
@@ -24,10 +24,6 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -36,10 +32,6 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
-7
View File
@@ -20,7 +20,6 @@ jobs:
- uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
@@ -31,12 +30,6 @@ jobs:
cd RobustToolbox
git fetch --depth=1
- name: Get Engine Tag
run: |
cd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
- name: Install dependencies
run: dotnet restore
-8
View File
@@ -39,10 +39,6 @@ jobs:
- name: Setup Submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
@@ -51,10 +47,6 @@ jobs:
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
-8
View File
@@ -17,20 +17,12 @@ jobs:
- name: Setup submodule
run: |
git submodule update --init --recursive
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Pull engine updates
uses: space-wizards/submodule-dependency@v0.1.5
- name: Update Engine Submodules
run: |
cd RobustToolbox/
git submodule update --init --recursive
pushd Lidgren.Network/Lidgren.Network
git fetch
git checkout 726dd552a4b104fb2b701848705f1250a073f060
popd
- name: Setup .NET Core
uses: actions/setup-dotnet@v4.1.0
with:
+18 -78
View File
@@ -73,88 +73,28 @@ If you are adding a lot of C# code, then take advantage of partial classes. Put
Otherwise, **add comments on or around any changed lines.**
### Single-Line Changes
Format should look like this.
A comment on a new imported namespace:
```cs
/* Importing Namespaces - Include optional comment if its not obvious what its being used for. */
using Content.Server._DV.Psionics.Glimmer; // DeltaV
using Content.Shared.Damage.Systems; // DeltaV - Addition of HandHeldArmor
/* Changing an upstream line - Same line as the change */
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
/* Adding - Either same line or above the line. */
EnsureComp<PotentialPsionicComponent>(entity); // Deltav - Psionics
/* "Deleting" - Don't actually delete, just comment out and say why. This only applies to upstream code. */
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
using Content.Server.Psionics.Glimmer; // DeltaV
```
> * Its pretty obvious in the example above that importing `Content.Server._DV.Psionics.Glimmer` means we'll be interacting with glimmer so putting `// DeltaV - Add Glimmer` is needlessly redundant.
> * It's not as obvious what the `Content.Shared.Damage.Systems` namespace is used for, since its so broad, so adding a comment what feature is using it helps.
> * Actual code changes should almost always include the comment after ``// DeltaV`.
### Multi-Line Changes
Depending on how much you are editing, putting a comment on EACH line may be excessive, so if you have a larger block of code you are changing, denote it like so:
A pair of comments enclosing a block of added code:
```cs
// BEGIN DeltaV - Remove innate radio and radios from pockets
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
private EntityUid Slice(...)
{
if (_inventory.TryGetSlotEntity(target, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
_inventory.TryUnequip(target, $"pocket{i}", true, true);
...
_transform.SetLocalRotation(sliceUid, 0);
// DeltaV - start of deep frier stuff
var slicedEv = new FoodSlicedEvent(user, uid, sliceUid);
RaiseLocalEvent(uid, ref slicedEv);
// DeltaV - end of deep frier stuff
...
}
RemComp<ActiveRadioComponent>(target); // If the zombie has an innate radio, get rid of it.
// END DeltaV
```
> * Denoting these with a BEGIN and END clearly shows they are block of code without having to read the entire comment. This makes it easier to tell when you're dealing with single-line comments versus a block with merging in conflicts.
> * Case and order of the first two words is less of a concern. `// DeltaV Begin` or `// Begin DeltaV` will work fine too.
> * Try to make your blocks as small as possible, but use your discretion.
> * If you deleting multiple lines, use line comments (``//``) if its a few lines but if its a larger block (like commenting out an entire function), it is preferable to use block comments (`/* */`).
#### Soft Exceptions to the Multi-Line "Rules"
Some multi-line changes can use a single-line comment in certain scenarios. But if you are UNSURE, just use `// BEGIN DeltaV` and `// END DeltaV` comments like the previous section does and it'll be fine.
I'll give some examples.
```cs
/* This change comments out 3 lines but only needs a single line comment because commenting out the if statement implies that its logic will be commented out too. */
// if (obj.WasModified<TraitPrototype>()) // DeltaV - Refreshed in TraitsTab
// {
// _profileEditor.RefreshTraits();
// }
/* Same principle here. This adds two lines but the if statement implies the next line so commenting both lines isn't really needed. */
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
return true;
```
### New Methods or Component Variables
Sometimes, you'll need to implement a whole new method or component variable and instead of wrapping it in `// BEGIN DeltaV` and `// END DeltaV`, you can just denote that it's a DeltaV function in the summary block before the function. This denotes the WHOLE function as a DeltaV addition.
```cs
/* New Method Example */
/// <summary>
/// DeltaV - Handle revealing ninja if cloaked when attacked by a hitscan attack.
/// </summary>
private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref DamageChangedEvent args)
{
...
}
/* New Component Variable Example */
/// <summary>
/// DeltaV - If disabled the action will not disable when no charges remain. Use if you want to handle no charges differently.
/// </summary>
[DataField]
public bool DisableWhenEmpty = true;
```
In short:
* Use `// BEGIN DeltaV` and `// END Delta` to denote a *block* of changes.
* Keep blocks as small as possible.
* Use `// DeltaV` on or before the line if its not a block of changes.
* Use exceptions when they make sense.
### Changing Upstream Localization Fluent .ftl files
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
@@ -164,10 +104,10 @@ Example:
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
```
# DeltaV - moved to _DV file
# analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
# [true] {$info}
# *[false] Unlock nodes to gain info
# }[/color][/font]
#analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
# [true] {$info}
# *[false] Unlock nodes to gain info
#}[/color][/font]
```
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
-10
View File
@@ -7,7 +7,6 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Input;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Radio.Components; // DeltaV - Client-side Radio Colors
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
@@ -159,15 +158,6 @@ namespace Content.Client.Examine
// since there's probably one open already if it's coming in from the server.
var entity = GetEntity(ev.EntityUid);
// BEGIN DeltaV - Client-Side Radio Colors
// This *seems* a bit hacky, but we basically don't want to overwrite what's been pushed on the client-side,
// because the server will send us the server-side colors (default). I've yet to find a scenario where the
// server and client don't match, so until then, this is probably fine, if a little hacky.
if (HasComp<EncryptionKeyHolderComponent>(entity) || HasComp<EncryptionKeyComponent>(entity))
return;
// END DeltaV
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
}
@@ -1,16 +1,13 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:crewMonitoring="clr-namespace:Content.Client._DV.CrewMonitoring"
Title="{Loc crew-monitoring-ui-title}"
Resizable="False"
SetSize="1210 700"
MinSize="1210 700">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
<crewMonitoring:CrewMonitoringShuttleControl Name="ShuttleMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
<BoxContainer Orientation="Vertical" Margin="0 0 10 0">
<controls:StripeBack>
<PanelContainer>
@@ -5,7 +5,6 @@ using Content.Client.Pinpointer.UI;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Medical.SuitSensor;
using Content.Shared.Station; // DeltaV - crew monitoring radar
using Content.Shared.StatusIcon;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
@@ -28,7 +27,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SharedTransformSystem _transformSystem;
private readonly SpriteSystem _spriteSystem;
private readonly SharedStationSystem _station; // DeltaV - crew monitoring radar
private NetEntity? _trackedEntity;
private bool _tryToScrollToListFocus;
@@ -41,10 +39,8 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
_transformSystem = _entManager.System<SharedTransformSystem>();
_spriteSystem = _entManager.System<SpriteSystem>();
_station = _entManager.System<SharedStationSystem>(); // DeltaV - crew monitoring radar
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
ShuttleMap.NavMap = NavMap; // DeltaV - crew monitoring radar
}
public void Set(string stationName, EntityUid? mapUid)
@@ -54,10 +50,8 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (_entManager.TryGetComponent<TransformComponent>(mapUid, out var xform))
NavMap.MapUid = xform.GridUid;
// Begin DeltaV - handle navmap visibility based on current position
// else
// NavMap.Visible = false;
// End DeltaV - handle navmap visibility based on current position
else
NavMap.Visible = false;
StationName.AddStyleClass("LabelBig");
StationName.Text = stationName;
@@ -68,19 +62,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
{
base.FrameUpdate(args);
// Begin DeltaV - handle navmap visibility based on current position
if (_station.GetOwningStation(ShuttleMap.Owner) is not null)
{
NavMap.Visible = true;
ShuttleMap.Visible = false;
}
else
{
NavMap.Visible = false;
ShuttleMap.Visible = true;
}
// End DeltaV - handle navmap visibility based on current position
if (_tryToScrollToListFocus)
TryToScrollToFocus();
}
@@ -88,7 +69,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
{
ClearOutDatedData();
ShuttleMap.Owner = monitor; // DeltaV - crew monitoring radar
// No server label
if (sensors.Count == 0)
@@ -324,7 +304,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
jobContainer.AddChild(jobLabel);
// Add user coordinates to the navmap
if (coordinates != null && _blipTexture != null) // DeltaV - don't filter the navmap visibility
if (coordinates != null && NavMap.Visible && _blipTexture != null)
{
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
new NavMapBlip
@@ -10,7 +10,6 @@
<tabs:AudioTab Name="AudioTab" />
<tabs:AccessibilityTab Name="AccessibilityTab" />
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
<dtabs:RadioTab Name="RadioTab" /> <!-- DeltaV - Client-side Radio Options -->
<dtabs:DeltaTab Name="DeltaTab" /> <!-- DeltaV -->
</TabContainer>
</DefaultWindow>
@@ -21,8 +21,7 @@ namespace Content.Client.Options.UI
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-admin"));
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-radio")); // DeltaV - Radio Channel Colors
Tabs.SetTabTitle(7, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
UpdateTabs();
}
@@ -37,7 +36,6 @@ namespace Content.Client.Options.UI
AccessibilityTab.Control.ReloadValues();
AudioTab.Control.ReloadValues();
AdminOptionsTab.Control.ReloadValues();
RadioTab.Control.ReloadValues(); // DeltaV - Radio Channel Colors
DeltaTab.Control.ReloadValues(); // DeltaV
}
}
@@ -56,8 +56,8 @@ public partial class NavMapControl : MapGridControl
// Constants
protected float UpdateTime = 1.0f;
public float MaxSelectableDistance = 10f; // DeltaV - accessible to CrewMonitoringShuttleControl
public float MinDragDistance = 5f; // DeltaV - accessible to CrewMonitoringShuttleControl
protected float MaxSelectableDistance = 10f;
protected float MinDragDistance = 5f;
protected static float MinDisplayedRange = 8f;
protected static float MaxDisplayedRange = 128f;
protected static float DefaultDisplayedRange = 48f;
@@ -193,13 +193,6 @@ public partial class NavMapControl : MapGridControl
_recenter.Disabled = false;
}
// Begin DeltaV - we need access to that from outside
public void TrackEntity(NetEntity? entity)
{
TrackedEntitySelectedAction?.Invoke(entity);
}
// End DeltaV - we need access to that from outside
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
+111
View File
@@ -159,6 +159,35 @@ namespace Content.Client.Stylesheets
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
// DeltaV - AAC button variables
public static readonly string CommandButtonClass = "CommandButton";
public static readonly string EngineeringButtonClass = "EngineeringButton";
public static readonly string EpistemicsButtonClass = "EpistemicsButton";
public static readonly string JusticeButtonClass = "JusticeButton";
public static readonly string LogisticsButtonClass = "LogisticsButton";
public static readonly string MedicalButtonClass = "MedicalButton";
public static readonly string SecurityButtonClass = "SecurityButton";
public static readonly string ServiceButtonClass = "ServiceButton";
// DeltaV - AAC button colors
public static readonly Color CommandButtonColorDefault = Color.FromHex("#404A58");
public static readonly Color CommandColorHovered = Color.FromHex("#4F587B");
public static readonly Color EngineeringButtonColorDefault = Color.FromHex("#77684B");
public static readonly Color EngineeringColorHovered = Color.FromHex("#776D71");
public static readonly Color EpistemicsButtonColorDefault = Color.FromHex("#6F5973");
public static readonly Color EpistemicsColorHovered = Color.FromHex("#71638E");
public static readonly Color LogisticsButtonColorDefault = Color.FromHex("#61503A");
public static readonly Color LogisticsColorHovered = Color.FromHex("#675C64");
public static readonly Color JusticeButtonColorDefault = Color.FromHex("#4F3D4C");
public static readonly Color JusticeColorHovered = Color.FromHex("#5C4B5A");
public static readonly Color MedicalButtonColorDefault = Color.FromHex("#49687D");
public static readonly Color MedicalColorHovered = Color.FromHex("#556E95");
public static readonly Color SecurityButtonColorDefault = Color.FromHex("#724449");
public static readonly Color SecurityColorHovered = Color.FromHex("#745370");
public static readonly Color ServiceButtonColorDefault = Color.FromHex("#607952");
public static readonly Color ServiceColorHovered = Color.FromHex("#667A76");
// End DeltaV
public const string StyleClassCrewManifestGender = "CrewManifestGender"; //Delta-V - Manifest pronouns
// i'm not sure what the missing symbols were referencing, and this is getting obseleted anyway so:
@@ -1648,6 +1677,88 @@ namespace Content.Client.Stylesheets
BackgroundColor = FancyTreeSelectedRowColor,
}),
// DeltaV - AAC button styles
Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, CommandButtonColorDefault),
Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, CommandColorHovered),
Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EngineeringButtonColorDefault),
Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EngineeringColorHovered),
Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EpistemicsButtonColorDefault),
Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EpistemicsColorHovered),
Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, LogisticsButtonColorDefault),
Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, LogisticsColorHovered),
Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, MedicalButtonColorDefault),
Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, MedicalColorHovered),
Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, SecurityButtonColorDefault),
Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, SecurityColorHovered),
Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ServiceButtonColorDefault),
Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ServiceColorHovered),
Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, JusticeButtonColorDefault),
Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, JusticeColorHovered),
// End DeltaV
// Silicon law edit ui
Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
.Prop(Label.StylePropertyFontColor, NanoGold),
@@ -1,7 +1,6 @@
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Text.RegularExpressions; // DeltaV - Client-Side Radio Channel Colors
using Content.Client.Administration.Managers;
using Content.Client.Chat;
using Content.Client.Chat.Managers;
@@ -840,38 +839,8 @@ public sealed partial class ChatUIController : UIController
}
}
/// <summary>
/// DeltaV - Regex expression to match on the chat message text in order to replace it with another color.
/// </summary>
private static readonly Regex RadioChatColorRegex = new(@"color=#?([a-fA-F0-9]{8})", RegexOptions.Compiled);
/// <summary>
/// DeltaV - Replaces the channel color hex code in a message with the client-side hex color
/// overridden by <see cref="Content.Client._DV.Radio.RadioChannelColorSystem"/> .
/// </summary>
/// <param name="message">The message to find and replace the color hex code in.</param>
/// <param name="channelProtoId">The proto ID of the channel. Basically the replacement color.</param>
/// <returns>The message with the color hex code replaced, if it exists.</returns>
private string ReplaceRadioChannelColorFromServer(string message, string channelProtoId)
{
if (_prototypeManager.Resolve<RadioChannelPrototype>(channelProtoId, out var channel))
{
return RadioChatColorRegex.Replace(message, $"color={channel.Color.ToHex()}");
}
return message;
}
public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
{
// BEGIN DeltaV
// Needs to happen before ANY other color or pattern matching so that we don't screw up the other colors in the message.
if (msg.Channel == ChatChannel.Radio && msg.RadioChannelProtoId is { } channel)
{
msg.WrappedMessage = ReplaceRadioChannelColorFromServer(msg.WrappedMessage, channel);
}
// END DeltaV
// color the name unless it's something like "the old man"
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Whisper) && _chatNameColorsEnabled)
{
@@ -12,19 +12,6 @@ namespace Content.Client._DV.AACTablet.UI;
[CommonSheetlet]
public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, IButtonConfig
{
private static readonly (string StyleClass, Color Normal, Color Hover)[] SubjectButtonColors =
[
// Department colors
("CommandButton", Color.FromHex("#404A58"), Color.FromHex("#4F587B")),
("EngineeringButton", Color.FromHex("#77684B"), Color.FromHex("#776D71")),
("EpistemicsButton", Color.FromHex("#6F5973"), Color.FromHex("#71638E")),
("JusticeButton", Color.FromHex("#4F3D4C"), Color.FromHex("#5C4B5A")),
("LogisticsButton", Color.FromHex("#61503A"), Color.FromHex("#675C64")),
("MedicalButton", Color.FromHex("#49687D"), Color.FromHex("#556E95")),
("SecurityButton", Color.FromHex("#724449"), Color.FromHex("#745370")),
("ServiceButton", Color.FromHex("#607952"), Color.FromHex("#667A76")),
];
public override StyleRule[] GetRules(T sheet, object config)
{
// Create style boxes
@@ -94,21 +81,6 @@ public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, I
.Prop(Control.StylePropertyModulateSelf, Color.DarkRed),
]);
SubjectColorRules(rules);
return rules.ToArray();
}
// AAC - Makes sure colors are applied to subject buttons
private static void SubjectColorRules(List<StyleRule> rules)
{
foreach (var (styleClass, normal, hover) in SubjectButtonColors)
{
rules.AddRange([
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
.PseudoNormal().Prop(Control.StylePropertyModulateSelf, normal),
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
.PseudoHovered().Prop(Control.StylePropertyModulateSelf, hover),
]);
}
}
}
@@ -218,7 +218,7 @@ public sealed partial class AACWindow : FancyWindow
MaxSize = new Vector2(ButtonWidth, ButtonWidth),
ClipText = false,
HorizontalExpand = true,
StyleClasses = { ContainerButton.StyleClassButton, styleClass }
StyleClasses = { styleClass }
};
var buttonLabel = new RichTextLabel
@@ -1,180 +0,0 @@
using System.Numerics;
using Content.Client.Pinpointer.UI;
using Content.Client.Shuttles.UI;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Content.Client._DV.CrewMonitoring;
public sealed class CrewMonitoringShuttleControl : BaseShuttleControl
{
[Dependency] private readonly IMapManager _mapManager = default!;
private readonly SharedShuttleSystem _shuttles;
private readonly SharedTransformSystem _transform;
public EntityUid? Owner;
public NavMapControl? NavMap;
private List<Entity<MapGridComponent>> _grids = new();
public CrewMonitoringShuttleControl() : base(64f, 256f, 256f)
{
_shuttles = EntManager.System<SharedShuttleSystem>();
_transform = EntManager.System<SharedTransformSystem>();
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
DrawBacking(handle);
DrawCircles(handle);
if (Owner is not { } owner || NavMap is not { } navMap)
return;
var xformQuery = EntManager.GetEntityQuery<TransformComponent>();
var fixturesQuery = EntManager.GetEntityQuery<FixturesComponent>();
var bodyQuery = EntManager.GetEntityQuery<PhysicsComponent>();
var iffQuery = EntManager.GetEntityQuery<IFFComponent>();
if (!xformQuery.TryGetComponent(owner, out var xform)
|| xform.MapID == MapId.Nullspace)
{
return;
}
var coordinates = new EntityCoordinates(owner, Vector2.Zero);
var mapPos = _transform.ToMapCoordinates(coordinates);
var ourEntRot = Angle.Zero;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), ourEntRot, mapPos.Position);
var viewAABB = viewBounds.CalcBoundingBox();
_grids.Clear();
_mapManager.FindGridsIntersecting(xform.MapID, new Box2(mapPos.Position - MaxRadarRangeVector, mapPos.Position + MaxRadarRangeVector), ref _grids, approx: true, includeMap: false);
foreach (var grid in _grids)
{
if (!fixturesQuery.HasComponent(grid))
continue;
var body = bodyQuery.GetComponent(grid);
iffQuery.TryComp(grid, out var iff);
if (!_shuttles.CanDraw(grid, body, iff))
continue;
var curGridToWorld = _transform.GetWorldMatrix(grid);
var curGridToView = curGridToWorld * worldToShuttle * shuttleToView;
var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
if (!gridAABB.Intersects(viewAABB))
continue;
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
DrawGrid(handle, curGridToView, grid, labelColor);
}
var curTime = Timing.RealTime;
var blinkFrequency = 1f / 1f;
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
var box = SizeBox;
foreach (var (coord, value) in navMap.TrackedCoordinates)
{
if (!lit || !value.Visible)
continue;
var blipPos = _transform.ToMapCoordinates(coord);
if (mapPos.MapId == MapId.Nullspace)
continue;
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, value.Color);
}
foreach (var blip in navMap.TrackedEntities.Values)
{
if (blip.Blinks && !lit)
continue;
var blipPos = _transform.ToMapCoordinates(blip.Coordinates);
if (mapPos.MapId == MapId.Nullspace)
continue;
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, blip.Color);
}
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (Owner is not { } owner || NavMap is null)
return;
if (!EntManager.TryGetComponent<TransformComponent>(owner, out var xform) || xform.MapID == MapId.Nullspace)
{
return;
}
var ourEntRot = Angle.Zero;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
var worldToView = worldToShuttle * shuttleToView;
Matrix3x2.Invert(worldToView, out var viewToWorld);
if (args.Function == EngineKeyFunctions.UIClick)
{
if (NavMap.TrackedEntities.Count == 0)
return;
if ((StartDragPosition - args.PointerLocation.Position).Length() > NavMap.MinDragDistance)
return;
var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
var worldPosition = Vector2.Transform(localPosition, viewToWorld);
var closestEntity = NetEntity.Invalid;
var closestDistance = float.PositiveInfinity;
foreach (var (currentEntity, blip) in NavMap.TrackedEntities)
{
if (!blip.Selectable)
continue;
var currentDistance = (_transform.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > NavMap.MaxSelectableDistance)
continue;
closestEntity = currentEntity;
closestDistance = currentDistance;
}
if (closestDistance > NavMap.MaxSelectableDistance || !closestEntity.IsValid())
return;
NavMap.TrackEntity(closestEntity);
}
else if (args.Function == EngineKeyFunctions.UIRightClick)
{
NavMap.TrackEntity(null);
}
}
}
@@ -1,87 +0,0 @@
<tabs:RadioTab xmlns="https://spacestation14.io"
xmlns:tabs="clr-namespace:Content.Client._DV.Options.UI.Tabs"
xmlns:ui="clr-namespace:Content.Client.Options.UI">
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Margin="8">
<!-- Common -->
<Label Text="{Loc 'ui-options-radio-header-common'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCommonColorSlider"
Example="{Loc 'ui-options-radio-example-common'}"/>
<Button Name="ResetCommonColorButton" Text="{Loc 'ui-options-radio-reset-common'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Command -->
<Label Text="{Loc 'ui-options-radio-header-command'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCommandColorSlider"
Example="{Loc 'ui-options-radio-example-command'}"/>
<Button Name="ResetCommandColorButton" Text="{Loc 'ui-options-radio-reset-command'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Security -->
<Label Text="{Loc 'ui-options-radio-header-security'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioSecurityColorSlider"
Example="{Loc 'ui-options-radio-example-security'}"/>
<Button Name="ResetSecurityColorButton" Text="{Loc 'ui-options-radio-reset-security'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Medical -->
<Label Text="{Loc 'ui-options-radio-header-medical'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioMedicalColorSlider"
Example="{Loc 'ui-options-radio-example-medical'}"/>
<Button Name="ResetMedicalColorButton" Text="{Loc 'ui-options-radio-reset-medical'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Engineering -->
<Label Text="{Loc 'ui-options-radio-header-engineering'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioEngineeringColorSlider"
Example="{Loc 'ui-options-radio-example-engineering'}"/>
<Button Name="ResetEngineeringColorButton" Text="{Loc 'ui-options-radio-reset-engineering'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Science -->
<Label Text="{Loc 'ui-options-radio-header-science'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioScienceColorSlider"
Example="{Loc 'ui-options-radio-example-science'}"/>
<Button Name="ResetScienceColorButton" Text="{Loc 'ui-options-radio-reset-science'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Cargo -->
<Label Text="{Loc 'ui-options-radio-header-cargo'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioCargoColorSlider"
Example="{Loc 'ui-options-radio-example-cargo'}"/>
<Button Name="ResetCargoColorButton" Text="{Loc 'ui-options-radio-reset-cargo'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Service -->
<Label Text="{Loc 'ui-options-radio-header-service'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioServiceColorSlider"
Example="{Loc 'ui-options-radio-example-service'}"/>
<Button Name="ResetServiceColorButton" Text="{Loc 'ui-options-radio-reset-service'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- Binary -->
<Label Text="{Loc 'ui-options-radio-header-binary'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioBinaryColorSlider"
Example="{Loc 'ui-options-radio-example-binary'}"/>
<Button Name="ResetBinaryColorButton" Text="{Loc 'ui-options-radio-reset-binary'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- DeltaV - Justice -->
<Label Text="{Loc 'ui-options-radio-header-justice'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioJusticeColorSlider"
Example="{Loc 'ui-options-radio-example-justice'}"/>
<Button Name="ResetJusticeColorButton" Text="{Loc 'ui-options-radio-reset-justice'}" MaxWidth="300" Margin="0 4 0 0" />
<!-- DeltaV - Prison -->
<Label Text="{Loc 'ui-options-radio-header-prison'}"
StyleClasses="LabelKeyText"/>
<ui:OptionColorSlider Name="RadioPrisonColorSlider"
Example="{Loc 'ui-options-radio-example-prison'}"/>
<Button Name="ResetPrisonColorButton" Text="{Loc 'ui-options-radio-reset-prison'}" MaxWidth="300" Margin="0 4 0 0" />
</BoxContainer>
</ScrollContainer>
<ui:OptionsTabControlRow Name="Control" Access="Public" />
</BoxContainer>
</tabs:RadioTab>
@@ -1,55 +0,0 @@
using Content.Client.Options.UI;
using Content.Shared._DV.CCVars;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Options.UI.Tabs;
[GenerateTypedNameReferences]
public sealed partial class RadioTab : Control
{
[Dependency] private readonly IConfigurationManager _config = default!; // DeltaV
public RadioTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
// Having so many color sliders on one page is a lot. We should only really try
// to have common/departmental channels here, or it just becomes WAAAAY too much.
Control.AddOptionColorSlider(DCCVars.RadioColorCommon, RadioCommonColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorCommand, RadioCommandColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorSecurity, RadioSecurityColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorMedical, RadioMedicalColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorEngineering, RadioEngineeringColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorScience, RadioScienceColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorCargo, RadioCargoColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorService, RadioServiceColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorBinary, RadioBinaryColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorJustice, RadioJusticeColorSlider);
Control.AddOptionColorSlider(DCCVars.RadioColorPrison, RadioPrisonColorSlider);
ResetCommonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommon);
ResetCommandColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommand);
ResetSecurityColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorSecurity);
ResetMedicalColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorMedical);
ResetEngineeringColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorEngineering);
ResetScienceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorScience);
ResetCargoColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCargo);
ResetServiceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorService);
ResetBinaryColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorBinary);
ResetJusticeColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorJustice);
ResetPrisonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorPrison);
Control.Initialize();
}
private void OnResetColorButton(CVarDef<string> cvar)
{
_config.SetCVar(cvar, cvar.DefaultValue);
Control.ReloadValues();
}
}
@@ -1,49 +0,0 @@
using Content.Shared._DV.CCVars;
using Content.Shared.Radio;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client._DV.Radio;
public sealed partial class RadioChannelColorSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
// Feels like there should be a better way to do this but I'd rather not add a client-side cvar ID or reference
// to a prototype in Content.Shared, since I don't want people thinking they can ever use it on the server-side.
private static readonly ProtoId<RadioChannelPrototype> CommonChannelProto = "Common";
private static readonly ProtoId<RadioChannelPrototype> CommandChannelProto = "Command";
private static readonly ProtoId<RadioChannelPrototype> SecurityChannelProto = "Security";
private static readonly ProtoId<RadioChannelPrototype> MedicalChannelProto = "Medical";
private static readonly ProtoId<RadioChannelPrototype> EngineeringChannelProto = "Engineering";
private static readonly ProtoId<RadioChannelPrototype> ScienceChannelProto = "Science";
private static readonly ProtoId<RadioChannelPrototype> CargoChannelProto = "Supply";
private static readonly ProtoId<RadioChannelPrototype> ServiceChannelProto = "Service";
private static readonly ProtoId<RadioChannelPrototype> BinaryChannelProto = "Binary";
private static readonly ProtoId<RadioChannelPrototype> JusticeChannelProto = "Justice";
private static readonly ProtoId<RadioChannelPrototype> PrisonChannelProto = "Prison";
public override void Initialize()
{
base.Initialize();
_config.OnValueChanged(DCCVars.RadioColorCommon, (value) => { SetChannelColor(CommonChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorCommand, (value) => { SetChannelColor(CommandChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorSecurity, (value) => { SetChannelColor(SecurityChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorMedical, (value) => { SetChannelColor(MedicalChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorEngineering, (value) => { SetChannelColor(EngineeringChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorScience, (value) => { SetChannelColor(ScienceChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorCargo, (value) => { SetChannelColor(CargoChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorService, (value) => { SetChannelColor(ServiceChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorBinary, (value) => { SetChannelColor(BinaryChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorJustice, (value) => { SetChannelColor(JusticeChannelProto, value); }, true);
_config.OnValueChanged(DCCVars.RadioColorPrison, (value) => { SetChannelColor(PrisonChannelProto, value); }, true);
}
private void SetChannelColor(ProtoId<RadioChannelPrototype> channel, string newColor)
{
if (Color.TryParse(newColor, out var color))
_proto.Index(channel).Color = color;
}
}
@@ -1,17 +0,0 @@
using Content.Shared.Labels.Components;
namespace Content.Client._DV.Silicons;
public static class NameHelpers
{
extension(IEntityManager self)
{
public string PaperName(EntityUid paper)
{
if (self.TryGetComponent<LabelComponent>(paper, out var label) && label.CurrentLabel is { } labelText)
return labelText;
return self.GetComponent<MetaDataComponent>(paper).EntityName;
}
}
}
@@ -1,144 +0,0 @@
using System.IO;
using Content.Shared._DV.Silicons;
using Content.Shared.Fax;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
using Robust.Shared.Containers;
namespace Content.Client._DV.Silicons;
[UsedImplicitly]
public sealed class StationAiFaxBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[Dependency] private readonly IFileDialogManager _fileDialogManager = default!;
private StationAiFaxWindow? _window;
private List<StationAiFaxPageWindow> _windows = new();
private bool _dialogIsOpen;
protected override void Open()
{
base.Open();
_window = this.CreateWindow<StationAiFaxWindow>();
_window.FileButtonPressed += OnFileButtonPressed;
_window.BlankButtonPressed += OnBlankButtonPressed;
_window.OpenSendWindowPressed += OnOpenSendWindowPressed;
_window.ExaminePressed += OnExaminePressed;
_window.ShredPressed += OnShredPressed;
_window.DuplicatePressed += OnDuplicatePressed;
UpdateFaxes(EntMan.System<SharedContainerSystem>().EnsureContainer<Container>(Owner, StationAiFaxComponent.Container, out _));
}
private async void OnFileButtonPressed()
{
if (_dialogIsOpen)
return;
_dialogIsOpen = true;
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
await using var file = await _fileDialogManager.OpenFile(filters, FileAccess.Read);
_dialogIsOpen = false;
if (_window == null || _window.Disposed || file == null)
{
return;
}
using var reader = new StreamReader(file);
var firstLine = await reader.ReadLineAsync();
string? label = null;
var content = await reader.ReadToEndAsync();
if (firstLine is { })
{
if (firstLine.StartsWith('#'))
{
label = firstLine[1..].Trim();
}
else
{
content = firstLine + "\n" + content;
}
}
SendMessage(new FaxFileMessage(
label?[..Math.Min(label.Length, FaxFileMessageValidation.MaxLabelSize)],
content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)],
false));
}
private void OnBlankButtonPressed()
{
SendMessage(new FaxFileMessage(null, string.Empty, false));
}
private void OnOpenSendWindowPressed(EntityUid entity)
{
var window = new StationAiFaxPageWindow(entity, EntMan);
_windows.Add(window);
window.RefreshButtonPressed += OnRefreshButtonPressed;
window.PeerSelected += OnPeerSelected;
window.SendButtonPressed += OnSendDocument;
if (UiSystem.TryGetUiState<FaxUiState>(Owner, UiKey, out var state))
{
window.UpdateState(state);
}
window.OpenCentered();
}
private void OnSendDocument(EntityUid entity)
{
SendMessage(new StationAiFaxSendMessage(EntMan.GetNetEntity(entity)));
}
private void OnExaminePressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxExamineMessage(EntMan.GetNetEntity(entity)));
}
private void OnShredPressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxShredMessage(EntMan.GetNetEntity(entity)));
}
private void OnDuplicatePressed(EntityUid entity)
{
SendPredictedMessage(new StationAiFaxDuplicateMessage(EntMan.GetNetEntity(entity)));
}
private void OnRefreshButtonPressed()
{
SendMessage(new FaxRefreshMessage());
}
private void OnPeerSelected(string address)
{
SendMessage(new FaxDestinationMessage(address));
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not FaxUiState cast)
return;
_window?.UpdateState(cast);
foreach (var window in _windows)
{
window.UpdateState(cast);
}
}
public void UpdateFaxes(Container container)
{
_window?.UpdateFaxes(container);
}
}
@@ -1,14 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client._DV.UserInterfaces"
xmlns:xe="clr-namespace:Content.Client.UserInterface.XamlExtensions"
StyleClasses="PanelLight"
HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="8">
<Label Name="PaperName" HorizontalExpand="True" />
<ui:IconButton Name="Shred" StyleClasses="OpenRight" Icon="{xe:Tex '/Textures/Interface/shredder.svg.192dpi.png'}" />
<ui:IconButton Name="Duplicate" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/files.svg.192dpi.png'}" />
<ui:IconButton Name="Examine" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/eye.svg.192dpi.png'}" />
<ui:IconButton Name="Send" StyleClasses="OpenLeft" Icon="{xe:Tex '/Textures/Interface/send.svg.192dpi.png'}" />
</BoxContainer>
</PanelContainer>
@@ -1,27 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxItem : PanelContainer
{
public string? Text { get => PaperName.Text; set => PaperName.Text = value; }
public bool CanSend { get => !Duplicate.Disabled; set => Duplicate.Disabled = !value; }
public event Action? ShredPressed;
public event Action? DuplicatePressed;
public event Action? ExaminePressed;
public event Action? SendPressed;
public StationAiFaxItem()
{
RobustXamlLoader.Load(this);
Shred.OnPressed += _ => ShredPressed?.Invoke();
Duplicate.OnPressed += _ => DuplicatePressed?.Invoke();
Examine.OnPressed += _ => ExaminePressed?.Invoke();
Send.OnPressed += _ => SendPressed?.Invoke();
}
}
@@ -1,64 +0,0 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'fax-machine-ui-page-window'}"
MinSize="380 220">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Margin="8">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-sending'}"
VerticalAlignment="Bottom"
HorizontalExpand="True" />
<Label Name="SendingLabel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Align="Right"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-from'}"
VerticalAlignment="Bottom"
HorizontalExpand="True" />
<Label Name="FromLabel"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Align="Right"/>
</BoxContainer>
<BoxContainer
Orientation="Horizontal">
<Label Text="{Loc 'fax-machine-ui-to'}"
VerticalAlignment="Bottom"
HorizontalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<OptionButton Name="PeerSelector"
StyleClasses="OpenRight" />
<Button StyleClasses="OpenLeft"
Name="RefreshButton"
ToolTip="{Loc 'fax-machine-ui-refresh-button'}">
<TextureButton
MouseFilter="Ignore"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Scale="0.9 0.9"
StyleClasses="RefreshButton" />
</Button>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</PanelContainer>
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
<Button Name="SendButton"
Text="{Loc 'fax-machine-ui-send-button'}"
HorizontalExpand="True"
Disabled="True" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -1,70 +0,0 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared.Fax;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxPageWindow : FancyWindow
{
public event Action? RefreshButtonPressed;
public event Action<string>? PeerSelected;
public event Action<EntityUid>? SendButtonPressed;
public StationAiFaxPageWindow(EntityUid uid, IEntityManager entityManager)
{
RobustXamlLoader.Load(this);
SendingLabel.Text = entityManager.PaperName(uid);
RefreshButton.OnPressed += _ => RefreshButtonPressed?.Invoke();
SendButton.OnPressed += _ => SendButtonPressed?.Invoke(uid);
PeerSelector.OnItemSelected += args =>
PeerSelected?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
}
public void UpdateState(FaxUiState state)
{
SendButton.Disabled = !state.CanSendCooldown;
FromLabel.Text = state.DeviceName;
if (state.AvailablePeers.Count == 0)
{
PeerSelector.AddItem(Loc.GetString("fax-machine-ui-no-peers"));
PeerSelector.Disabled = true;
}
if (PeerSelector.Disabled && state.AvailablePeers.Count != 0)
{
PeerSelector.Clear();
PeerSelector.Disabled = false;
}
// always must be selected destination
if (string.IsNullOrEmpty(state.DestinationAddress) && state.AvailablePeers.Count != 0)
{
PeerSelected?.Invoke(state.AvailablePeers.First().Key);
return;
}
if (state.AvailablePeers.Count != 0)
{
PeerSelector.Clear();
foreach (var (address, name) in state.AvailablePeers)
{
var id = AddPeerSelect(name, address);
if (address == state.DestinationAddress)
PeerSelector.Select(id);
}
}
}
private int AddPeerSelect(string name, string address)
{
PeerSelector.AddItem(name);
PeerSelector.SetItemMetadata(PeerSelector.ItemCount - 1, address);
return PeerSelector.ItemCount - 1;
}
}
@@ -1,37 +0,0 @@
using Content.Shared._DV.Silicons;
using Content.Shared.Fax;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
namespace Content.Client._DV.Silicons;
public sealed class StationAiFaxSystem : SharedStationAiFaxSystem
{
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationAiFaxComponent, EntRemovedFromContainerMessage>(OnRemoved);
}
protected override void OnInserted(Entity<StationAiFaxComponent> ent, ref EntInsertedIntoContainerMessage args)
{
base.OnInserted(ent, ref args);
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
return;
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
}
private void OnRemoved(Entity<StationAiFaxComponent> ent, ref EntRemovedFromContainerMessage args)
{
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
return;
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
}
}
@@ -1,29 +0,0 @@
<controls:FancyWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'fax-machine-ui-window'}"
MinSize="500 600">
<BoxContainer Orientation="Vertical" Name="NoFaxes" HorizontalAlignment="Center" VerticalAlignment="Center" SeparationOverride="8">
<Label StyleClasses="PlaceholderText" Text="{Loc 'fax-machine-ui-no-faxes'}" Align="Center" />
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center">
<Button Name="NoFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalExpand="True" StyleClasses="OpenLeft" />
<Button Name="NoFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalExpand="True" StyleClasses="OpenRight" />
</BoxContainer>
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="YesFaxes" HorizontalExpand="True" VerticalExpand="True">
<PanelContainer StyleClasses="PanelDark" VerticalExpand="True" HorizontalExpand="True">
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
<BoxContainer Name="FaxList" Margin="8" VerticalAlignment="Top" HorizontalExpand="True" SeparationOverride="8" Orientation="Vertical" />
</ScrollContainer>
</PanelContainer>
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
<Button Name="YesFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalAlignment="Center" />
<Button Name="YesFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalAlignment="Center" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
@@ -1,92 +0,0 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Fax;
using Content.Shared.Labels.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.Silicons;
[GenerateTypedNameReferences]
public sealed partial class StationAiFaxWindow : FancyWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action? BlankButtonPressed;
public event Action? FileButtonPressed;
public event Action<EntityUid>? OpenSendWindowPressed;
public event Action<EntityUid>? ExaminePressed;
public event Action<EntityUid>? ShredPressed;
public event Action<EntityUid>? DuplicatePressed;
public bool CanSend
{
get;
set
{
if (field == value)
return;
field = value;
foreach (var item in FaxList.Children)
{
var castItem = (StationAiFaxItem)item;
castItem.CanSend = value;
}
NoFaxesBlank.Disabled = !value;
NoFaxesUpload.Disabled = !value;
YesFaxesBlank.Disabled = !value;
YesFaxesUpload.Disabled = !value;
}
}
public StationAiFaxWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
NoFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
NoFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
YesFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
YesFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
}
public void UpdateFaxes(Robust.Shared.Containers.Container container)
{
FaxList.RemoveAllChildren();
foreach (var item in container.ContainedEntities)
{
var page = new StationAiFaxItem
{
Text = _entityManager.TryGetComponent<LabelComponent>(item, out var label) &&
label.CurrentLabel is { } labelText
? labelText
: _entityManager.GetComponent<MetaDataComponent>(item).EntityName,
CanSend = CanSend,
};
FaxList.AddChild(page);
page.ExaminePressed += () => ExaminePressed?.Invoke(item);
page.ShredPressed += () => ShredPressed?.Invoke(item);
page.DuplicatePressed += () => DuplicatePressed?.Invoke(item);
page.SendPressed += () => OpenSendWindowPressed?.Invoke(item);
}
if (container.ContainedEntities.Count == 0)
{
YesFaxes.Visible = false;
NoFaxes.Visible = true;
}
else
{
YesFaxes.Visible = true;
NoFaxes.Visible = false;
}
}
public void UpdateState(FaxUiState state)
{
CanSend = state.CanSendCooldown;
}
}
@@ -108,70 +108,6 @@ public sealed partial class TraitCategory : BoxContainer
{
CategoryPointsLabel.Visible = false;
}
// Lock unselected entries whenever the category cap is reached so the
// player gets a clear visual cue instead of silently failing to equip.
UpdateCategoryFullState();
}
/// <summary>
/// Propagates category-cap lock state to every entry.
/// <para>
/// Selected entries are never locked - they are the traits filling the cap
/// and must stay interactive so the player can deselect them.
/// </para>
/// <para>
/// Unselected entries are locked when:
/// <list type="bullet">
/// <item><see cref="TraitCategoryPrototype.MaxTraits"/> is defined and already reached, OR</item>
/// <item><see cref="TraitCategoryPrototype.MaxPoints"/> is defined and this specific
/// trait's cost would push spending over the limit.</item>
/// </list>
/// </para>
/// </summary>
private void UpdateCategoryFullState()
{
var traitCapReached = _category.MaxTraits.HasValue && SelectedCount >= _category.MaxTraits.Value;
foreach (var (_, entry) in _traitEntries)
{
if (entry.IsSelected)
{
// Selected traits are never locked by category state.
entry.SetLockedByCategory(false);
continue;
}
var locked = traitCapReached;
// Even when the trait count cap hasn't been hit, lock this specific entry
// if adding its cost would exceed the category's point budget.
if (!locked && _category.MaxPoints.HasValue)
locked = PointsSpent + entry.TraitCost > _category.MaxPoints.Value;
entry.SetLockedByCategory(locked);
}
}
/// <summary>
/// Called by <see cref="TraitsTab"/> whenever the player's global point budget changes.
/// Locks any unselected entry whose cost the player can no longer afford.
/// <para>
/// Free (0-cost) and negative-cost traits are never locked - they cost nothing or
/// actually give points back. Selected traits are also never locked here; they
/// were affordable when picked and must stay interactive to be deselected.
/// </para>
/// </summary>
public void UpdateGlobalPointsLock(int remainingPoints)
{
foreach (var (_, entry) in _traitEntries)
{
// A selected trait is always unlocked here - same reasoning as category cap:
// it was paid for at selection time and must stay interactive.
// Traits that cost nothing or give points back are never locked by budget.
var shouldLock = !entry.IsSelected && entry.TraitCost > remainingPoints;
entry.SetLockedByPoints(shouldLock);
}
}
public void SetTraitSelected(ProtoId<TraitPrototype> traitId, bool selected)
@@ -235,11 +171,11 @@ public sealed partial class TraitCategory : BoxContainer
/// Updates condition states for all trait entries based on current job/species.
/// Traits that don't meet conditions are disabled but still visible.
/// </summary>
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
{
foreach (var (_, entry) in _traitEntries)
{
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences, selectedTraits);
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences);
}
// Update stats since some traits may have been deselected
+37 -158
View File
@@ -27,9 +27,6 @@ public sealed partial class TraitEntry : PanelContainer
private bool _isUpdating;
private readonly List<string> _failedConditionTooltips = new();
private bool _isLockedByCategory;
private bool _isLockedByPoints;
public TraitEntry(TraitPrototype trait)
{
RobustXamlLoader.Load(this);
@@ -62,17 +59,9 @@ public sealed partial class TraitEntry : PanelContainer
foreach (var condition in _trait.Conditions)
{
if (condition is TraitDependencyCondition depCond)
{
// Use GetTooltips() to get individual lines without duplication
tooltips.AddRange(depCond.GetTooltips(_prototype, _loc));
}
else
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
tooltips.Add(tooltip);
}
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
tooltips.Add(tooltip);
}
if (tooltips.Count > 0)
@@ -102,92 +91,39 @@ public sealed partial class TraitEntry : PanelContainer
/// <summary>
/// Updates whether conditions are met based on current job/species.
/// </summary>
public void UpdateConditionsMet(
ProtoId<JobPrototype>? jobId,
ProtoId<SpeciesPrototype>? speciesId,
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
{
_failedConditionTooltips.Clear();
MeetsConditions = true;
foreach (var condition in _trait.Conditions)
{
bool result;
if (condition is TraitDependencyCondition depCond)
var result = condition switch
{
result = CheckDependencyCondition(depCond, selectedTraits);
// CheckDependencyCondition adds its own tooltips directly
}
else
{
result = condition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences, selectedTraits),
_ => true,
};
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby but screws with the inversion logic
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences),
_ => true,
};
// Apply inversion for non-dependency conditions
result ^= condition.Invert;
if (!result)
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
_failedConditionTooltips.Add(tooltip);
}
}
// Apply inversion
result ^= condition.Invert;
if (!result)
{
MeetsConditions = false;
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
_failedConditionTooltips.Add(tooltip);
}
}
UpdateDisabledState();
}
/// <summary>
/// Checks a TraitDependencyCondition and adds failure tooltips directly.
/// Returns true if the condition passes.
/// </summary>
private bool CheckDependencyCondition(TraitDependencyCondition condition, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
{
var passed = true;
foreach (var conflict in condition.Conflicts)
{
if (selectedTraits == null || !selectedTraits.Contains(conflict))
continue;
if (_prototype.TryIndex(conflict, out var conflictProto))
{
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-conflict",
("trait", _loc.GetString(conflictProto.Name))));
}
passed = false;
}
foreach (var required in condition.Requires)
{
if (selectedTraits != null && selectedTraits.Contains(required))
continue;
if (_prototype.TryIndex(required, out var requiredProto))
{
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-required",
("trait", _loc.GetString(requiredProto.Name))));
}
passed = false;
}
return passed;
}
private bool CheckSpeciesCondition(IsSpeciesCondition condition, ProtoId<SpeciesPrototype>? speciesId)
{
if (!_prototype.TryIndex(speciesId, out var species))
@@ -223,12 +159,7 @@ public sealed partial class TraitEntry : PanelContainer
return antagPreferences.Contains(condition.Antag);
}
private bool CheckAnyOfCondition(
AnyOfCondition condition,
ProtoId<JobPrototype>? jobId,
ProtoId<SpeciesPrototype>? speciesId,
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
{
if (condition.Conditions.Count == 0)
return false;
@@ -236,27 +167,19 @@ public sealed partial class TraitEntry : PanelContainer
// Return true if ANY child condition evaluates to true
foreach (var childCondition in condition.Conditions)
{
bool result;
if (childCondition is TraitDependencyCondition depCond)
var result = childCondition switch
{
result = CheckDependencyCondition(depCond, selectedTraits);
}
else
{
result = childCondition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences, selectedTraits),
_ => true,
};
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences), // Recursive!
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
_ => true,
};
result ^= childCondition.Invert;
}
// Apply child's inversion
result ^= childCondition.Invert;
// If any child passes, the AnyOf passes
if (result)
@@ -267,50 +190,16 @@ public sealed partial class TraitEntry : PanelContainer
return false;
}
/// <summary>
/// Called by <see cref="TraitCategory"/> whenever the category's trait or
/// points-cap state changes. Locking only applies to <em>unselected</em>
/// entries - the selected traits are the ones filling the cap and must
/// remain interactive so the player can deselect them.
/// </summary>
public void SetLockedByCategory(bool locked)
{
if (_isLockedByCategory == locked)
return;
_isLockedByCategory = locked;
UpdateDisabledState();
}
/// <summary>
/// Called by <see cref="TraitCategory"/> whenever the player's global point budget
/// changes. Locks unselected entries whose cost the player can no longer afford.
/// Free (0-cost) and negative-cost traits are never locked by this.
/// </summary>
public void SetLockedByPoints(bool locked)
{
if (_isLockedByPoints == locked)
return;
_isLockedByPoints = locked;
UpdateDisabledState();
}
private void UpdateDisabledState()
{
var isSelected = TraitCheckbox.Pressed;
var conditionLocked = !MeetsConditions;
var categoryLocked = _isLockedByCategory && !isSelected;
var pointsLocked = _isLockedByPoints && !isSelected;
var isDisabled = conditionLocked || categoryLocked || pointsLocked;
if (isDisabled)
if (!MeetsConditions)
{
// Hide checkbox, show lock icon
TraitCheckbox.Visible = false;
LockIcon.Visible = true;
if (isSelected && conditionLocked)
// Deselect if conditions no longer met
if (TraitCheckbox.Pressed)
{
_isUpdating = true;
TraitCheckbox.Pressed = false;
@@ -322,24 +211,14 @@ public sealed partial class TraitEntry : PanelContainer
// Add disabled styling
AddStyleClass("TraitsEntryDisabled");
// Tooltip priority for conditions: condition failures > category full > insufficient points.
if (conditionLocked && _failedConditionTooltips.Count > 0)
// Update tooltip to show failed conditions
if (_failedConditionTooltips.Count > 0)
{
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
("requirements", string.Join("\n", _failedConditionTooltips)));
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
else if (categoryLocked)
{
var tooltipText = Loc.GetString("trait-category-full-tooltip");
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
else if (pointsLocked)
{
var tooltipText = Loc.GetString("trait-insufficient-points-tooltip");
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
}
else
{
+8 -18
View File
@@ -154,18 +154,14 @@ public sealed partial class TraitsTab : BoxContainer
}
}
// Check conflicts via TraitDependencyCondition
var depCondition = trait.Conditions.OfType<Content.Shared._DV.Traits.Conditions.TraitDependencyCondition>().FirstOrDefault();
if (depCondition != null)
// Check conflicts
foreach (var conflict in trait.Conflicts)
{
foreach (var conflict in depCondition.Conflicts)
{
if (!_selectedTraits.Contains(conflict))
continue;
if (!_selectedTraits.Contains(conflict))
continue;
RevertTraitToggle(traitId);
return;
}
RevertTraitToggle(traitId);
return;
}
_selectedTraits.Add(traitId);
@@ -180,7 +176,6 @@ public sealed partial class TraitsTab : BoxContainer
}
UpdateGlobalStats();
UpdateAllConditions();
UpdateCategoryStats(trait.Category);
OnTraitsChanged?.Invoke(_selectedTraits);
}
@@ -216,7 +211,7 @@ public sealed partial class TraitsTab : BoxContainer
// If parent width is 0 (not laid out yet), defer until layout happens
if (parentWidth > 0)
{
GlobalPointsBar.SetWidth = (int)((parentWidth - 2) * percentage);
GlobalPointsBar.SetWidth = (int)((parentWidth - 2 ) * percentage);
_awaitingLayoutUpdate = false;
}
else if (!_awaitingLayoutUpdate)
@@ -240,11 +235,6 @@ public sealed partial class TraitsTab : BoxContainer
> 0f => "TraitsProgressBarLow",
_ => "TraitsProgressBarEmpty"
});
foreach (var (_, categoryUi) in _categoryUis)
{
categoryUi.UpdateGlobalPointsLock(remainingPoints);
}
}
private void OnProgressBarParentResized()
@@ -291,7 +281,7 @@ public sealed partial class TraitsTab : BoxContainer
foreach (var (_, categoryUi) in _categoryUis)
{
// If some fork wants to use the top selected job as well, just add that to the UpdateConditions method in the editor
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences, _selectedTraits);
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences);
}
RecalculateStats();
@@ -1,7 +0,0 @@
<Button xmlns="https://spacestation14.io">
<TextureRect Name="TextureRect"
TextureScale="0.5 0.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="KeepCentered"/>
</Button>
@@ -1,17 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._DV.UserInterfaces;
[GenerateTypedNameReferences]
public sealed partial class IconButton : Button
{
public Texture? Icon { get => TextureRect.Texture; set => TextureRect.Texture = value; }
public IconButton()
{
RobustXamlLoader.Load(this);
}
}
@@ -24,7 +24,7 @@ public sealed class WizdenContentFreeze
var protoMan = server.ProtoMan;
var recipesCount = protoMan.Count<FoodRecipePrototype>();
var recipesLimit = 266; // DeltaV - was 218
var recipesLimit = 269; // DeltaV - was 218
if (recipesCount > recipesLimit)
{
@@ -262,6 +262,32 @@ public sealed partial class AdminVerbSystem
Message = string.Join(": ", ntAgent, Loc.GetString("admin-verb-text-make-NTAgent")),
};
args.Verbs.Add(agent);
args.Verbs.Add(new ()
{
Text = Loc.GetString("admin-verb-make-HeadInsurgent.text"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
Act = () =>
{
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadInsurgent");
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-HeadInsurgent.message"),
});
args.Verbs.Add(new ()
{
Text = Loc.GetString("admin-verb-make-HeadLoyalist.text"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "Nanotrasen"),
Act = () =>
{
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadLoyalist");
},
Impact = LogImpact.High,
Message = Loc.GetString("admin-verb-make-HeadLoyalist.message"),
});
// End DeltaV Additions
// start DeltaV Additions - add hitman
var hitmanName = Loc.GetString("admin-verb-make-hitman");
@@ -6,7 +6,6 @@ using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Player; // DeltaV
namespace Content.Server.AlertLevel;
@@ -192,8 +191,7 @@ public sealed class AlertLevelSystem : EntitySystem
{
if (detail.Sound != null)
{
//var filter = _stationSystem.GetInOwningStation(station); // DeltaV - Global Annoucements
var filter = Filter.Empty().AddAllPlayers(); // DeltaV - Global Annoucements
var filter = _stationSystem.GetInOwningStation(station);
_audio.PlayGlobal(detail.Sound, filter, true, detail.Sound.Params);
}
else
@@ -204,7 +202,7 @@ public sealed class AlertLevelSystem : EntitySystem
if (announce)
{
_chatSystem.DispatchGlobalAnnouncement(announcementFull, playSound: playDefault, // DeltaV - Global Annoucements
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playDefaultSound: playDefault,
colorOverride: detail.Color, sender: stationName);
}
@@ -23,7 +23,8 @@ public sealed partial class AntagSelectionSystem
/// </summary>
public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
[NotNullWhen(true)] out AntagSelectionDefinition? definition,
int? players = null)
int? players = null,
string? preferredRole = null) // DeltaV - preferrec roles
{
definition = null;
@@ -39,6 +40,11 @@ public sealed partial class AntagSelectionSystem
// It needs to track selected minds for each definition independently.
foreach (var def in ent.Comp.Definitions)
{
// Begin DeltaV - preferred roles
if (preferredRole is not null && !def.PrefRoles.Contains(preferredRole))
continue;
// End DeltaV - preferred roles
var target = GetTargetAntagCount(ent, null, def);
if (mindCount < target)
@@ -336,11 +342,11 @@ public sealed partial class AntagSelectionSystem
/// This technically is a gamerule-ent-less way to make an entity an antag.
/// You should almost never be using this.
/// </summary>
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule) where T : Component
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule, string? preferredRole = null) where T : Component // DeltaV - preferred antag roles
{
var rule = ForceGetGameRuleEnt<T>(defaultRule);
if (!TryGetNextAvailableDefinition(rule, out var def))
if (!TryGetNextAvailableDefinition(rule, out var def, preferredRole: preferredRole)) // DeltaV - preferred antag roles
def = rule.Comp.Definitions.Last();
MakeAntag(rule, player, def.Value);
}
@@ -7,7 +7,7 @@ using Robust.Shared.Player;
namespace Content.Server.Audio;
public sealed partial class ServerGlobalSoundSystem : SharedGlobalSoundSystem // DeltaV - Made Partial
public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
{
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
@@ -184,11 +184,6 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
var rate = entry.MetabolismRate * group.MetabolismRateModifier;
// BEGIN DeltaV - fix FixedPoint2 truncation flooring low rateModifiers to zero
if (rate == FixedPoint2.Zero && entry.MetabolismRate > FixedPoint2.Zero && group.MetabolismRateModifier > 0f)
rate = FixedPoint2.Epsilon;
// END DeltaV
// Remove $rate, as long as there's enough reagent there to actually remove that much
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
+8 -13
View File
@@ -5,7 +5,6 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Tools;
using Content.Shared._DV.Fax; // DeltaV - fax
using Content.Shared._DV.Pager; // DeltaV - pagers
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
@@ -91,10 +90,10 @@ public sealed class FaxSystem : EntitySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<FaxMachineComponent>();
while (query.MoveNext(out var uid, out var fax)) // DeltaV - faxes aren't necessarily powered
var query = EntityQueryEnumerator<FaxMachineComponent, ApcPowerReceiverComponent>();
while (query.MoveNext(out var uid, out var fax, out var receiver))
{
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered) // DeltaV - faxes aren't necessarily powered
if (!receiver.Powered)
continue;
ProcessPrintingAnimation(uid, frameTime, fax);
@@ -393,7 +392,7 @@ public sealed class FaxSystem : EntitySystem
var canCopy = isPaperInserted &&
component.SendTimeoutRemaining <= 0 &&
component.InsertingTimeRemaining <= 0;
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress, component.SendTimeoutRemaining <= 0); // DeltaV - AI fax
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress);
_userInterface.SetUiState(uid, FaxUiKey.Key, state);
}
@@ -462,7 +461,7 @@ public sealed class FaxSystem : EntitySystem
/// Copies the paper in the fax. A timeout is set after copying,
/// which is shared by the send button.
/// </summary>
public void Copy(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage args)
{
if (!Resolve(uid, ref component))
return;
@@ -470,7 +469,7 @@ public sealed class FaxSystem : EntitySystem
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
var sendEntity = component.PaperSlot.Item;
if (sendEntity == null)
return;
@@ -512,7 +511,7 @@ public sealed class FaxSystem : EntitySystem
/// Sends message to addressee if paper is set and a known fax is selected
/// A timeout is set after sending, which is shared by the copy button.
/// </summary>
public void Send(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
{
if (!Resolve(uid, ref component))
return;
@@ -520,7 +519,7 @@ public sealed class FaxSystem : EntitySystem
if (component.SendTimeoutRemaining > 0)
return;
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
var sendEntity = component.PaperSlot.Item;
if (sendEntity == null)
return;
@@ -636,10 +635,6 @@ public sealed class FaxSystem : EntitySystem
}
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
// Begin DeltaV - notify on fax printing
var evt = new FaxPrintedEvent(printed);
RaiseLocalEvent(uid, ref evt);
// End DeltaV - notify on fax printing
}
private void NotifyAdmins(string faxName)
+11 -11
View File
@@ -327,15 +327,15 @@ public sealed class NukeSystem : EntitySystem
// should play
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
{
_sound.DispatchGlobalEventMusic(_selectedNukeSong, StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
_sound.DispatchStationEventMusic(uid, _selectedNukeSong, StationEventMusicType.Nuke);
nuke.PlayedNukeSong = true;
}
// play alert sound if time is running out
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
{
_sound.PlayGlobal(_audio.ResolveSound(nuke.AlertSound), new AudioParams { Volume = -5f }); // DeltaV - Global Nuke SFX
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(nuke.AlertSound), new AudioParams{Volume = -5f});
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
nuke.PlayedAlertSound = true;
UpdateAppearance(uid, nuke);
}
@@ -475,7 +475,7 @@ public sealed class NukeSystem : EntitySystem
/// <summary>
/// Force a nuclear bomb to start a countdown timer
/// </summary>
public void ArmBomb(EntityUid uid, NukeComponent? component = null)
public void ArmBomb(EntityUid uid, NukeComponent? component = null, string? announcementId = null) // DeltaV - custom announcements
{
if (!Resolve(uid, ref component))
return;
@@ -500,13 +500,13 @@ public sealed class NukeSystem : EntitySystem
_selectedNukeSong = _audio.ResolveSound(component.ArmMusic);
// warn a crew
var announcement = Loc.GetString("nuke-component-announcement-armed",
var announcement = Loc.GetString(announcementId ?? "nuke-component-announcement-armed", // DeltaV - custom announcements
("time", (int) component.RemainingTime),
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false, null, Color.Red); // DeltaV - Global Nuke Annoucement
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red);
_sound.PlayGlobal(_audio.ResolveSound(component.ArmSound)); // DeltaV - Global Nuke Music
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.ArmSound));
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
// turn on the spinny light
@@ -544,11 +544,11 @@ public sealed class NukeSystem : EntitySystem
// warn a crew
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false); // DeltaV - Global Nuke Announcement
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
component.PlayedNukeSong = false;
_sound.PlayGlobal(_audio.ResolveSound(component.DisarmSound)); // DeltaV - Global Nuke SFX
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.DisarmSound));
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
// reset nuke remaining time to either itself or the minimum time, whichever is higher
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
@@ -610,7 +610,7 @@ public sealed class NukeSystem : EntitySystem
OwningStation = transform.GridUid,
});
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
Del(uid);
}
@@ -157,8 +157,7 @@ public sealed class RadioSystem : EntitySystem
message,
wrappedMessage,
NetEntity.Invalid,
null,
radioChannelProto: channel.ID); // DeltaV - Add RadioChannel for committing sins
null);
var chatMsg = new MsgChatMessage { Message = chat };
var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
@@ -210,7 +209,7 @@ public sealed class RadioSystem : EntitySystem
}
/// <inheritdoc cref="TelecomServerComponent"/>
public bool HasActiveServer(MapId mapId, string channelId) // DeltaV - we need this
private bool HasActiveServer(MapId mapId, string channelId)
{
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
foreach (var (_, keys, power, transform) in servers)
@@ -6,41 +6,4 @@ namespace Content.Server.StationEvents.Components;
[RegisterComponent]
public sealed partial class IonStormRuleComponent : Component
{
// DV start - Synthetic tweaks
/// <summary>
/// The chance that a synth gets electrocuted by the ion storm.
/// </summary>
[DataField]
public float SynthElectrocutionChance = 0.6f;
/// <summary>
/// The minimum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionDelayMin = 3;
/// <summary>
/// The maximum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionDelayMax = 10;
/// <summary>
/// The minimum damage dealt by the synth electrocution. (shock damage)
/// </summary>
[DataField]
public int SynthElectrocutionDamageMin = 5;
/// <summary>
/// The maximum damage dealt by the synth electrocution. (shock damage)
/// </summary>8
[DataField]
public int SynthElectrocutionDamageMax = 10;
/// <summary>
/// How long the synth is stunned for by the electrocution. (in seconds)
/// </summary>
[DataField]
public int SynthElectrocutionStunDuration = 3;
// DV end - synthetic tweaks
}
@@ -6,12 +6,9 @@ using Content.Shared.Silicons.Laws.Components;
using Content.Shared.Station.Components;
// CD - start synth trait
using Content.Server.Chat.Managers;
using Content.Server.Electrocution; // DV - added for synth trait
using Content.Shared.Chat;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Content.Shared._ES.Sparks; // Delta-V change - added for synth trait (where they get affected by ion storms)
using Timer = Robust.Shared.Timing.Timer; // DV - used for synth trait delayed damage
// CD - end synth trait
namespace Content.Server.StationEvents.Events;
@@ -20,8 +17,6 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
{
[Dependency] private readonly IonStormSystem _ionStorm = default!;
[Dependency] private readonly IChatManager _chatManager = default!; // CD - Used for synth trait
[Dependency] private readonly ElectrocutionSystem _electrocution = default!; // DV - used for synth trait
[Dependency] private readonly ESSparksSystem _esSparks = default!; // DV - used for synth trait
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
@@ -31,32 +26,18 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
return;
// CD - Go through everyone with the SynthComponent and inform them a storm is happening.
// DV - buuuuuut only if IonStormAffected is present so that not all synthetics are affected by this
// but they are still notified about incoming ion storms regardless.
var synthQuery = EntityQueryEnumerator<SynthComponent>();
while (synthQuery.MoveNext(out var ent, out var synthComp))
{
if (RobustRandom.Prob(synthComp.AlertChance))
continue;
if (!TryComp<ActorComponent>(ent, out var actor))
continue;
if (RobustRandom.Prob(synthComp.AlertChance))
{
var msg = Loc.GetString("station-event-ion-storm-synth");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
}
// DV start - super duper fucked up thing for synths
if (synthComp.IonStormAffected && RobustRandom.Prob(comp.SynthElectrocutionChance))
{
var delay = RobustRandom.Next(TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMin), TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMax)); // they'll never know when it'll hit them
Timer.Spawn(delay, () =>
{
_electrocution.TryDoElectrocution(ent, null, RobustRandom.Next(comp.SynthElectrocutionDamageMin, comp.SynthElectrocutionDamageMax), TimeSpan.FromSeconds(comp.SynthElectrocutionStunDuration), true, ignoreInsulation: true);
_esSparks.DoSparks(ent);
});
}
// DV end
var msg = Loc.GetString("station-event-ion-storm-synth");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
}
// CD - End of synth trait
@@ -0,0 +1,84 @@
using Content.Server.Revolutionary.Components;
using Content.Shared._DV.Uprising;
using Content.Shared.Cuffs.Components;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Objectives.Components;
using Content.Shared.Roles;
using Content.Shared.Station;
namespace Content.Server.Uprising;
public sealed class CommandStatusObjectiveSystem : EntitySystem
{
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly SharedStationSystem _station = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CommandStatusObjectiveComponent, ObjectiveGetProgressEvent>(OnGetProgress);
}
private bool CheckIncapacitated(EntityUid uid)
{
if (TryComp<CuffableComponent>(uid, out var cuffed) && cuffed.CuffedHandCount > 0)
return true;
if (!TryComp<MobStateComponent>(uid, out var state))
return true;
if (state.CurrentState is MobState.Dead or MobState.Invalid)
return true;
if (_station.GetOwningStation(uid) == null)
return true;
return false;
}
private void OnGetProgress(Entity<CommandStatusObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
{
var total = 0;
var counted = 0;
var query = AllEntityQuery<MindContainerComponent, CommandStaffComponent>();
while (query.MoveNext(out var uid, out var mind, out _))
{
if (mind.Mind is not { } mindUid)
continue;
total++;
var converted = false;
foreach (var role in ent.Comp.ConvertedRoles)
{
if (!EntityManager.ComponentFactory.TryGetRegistration(role, out var roleReg))
{
Log.Error($"Role component not found for CrewConversionObjective: {role}");
continue;
}
if (!_role.MindHasRole(mindUid, roleReg.Type, out _))
continue;
converted = true;
break;
}
var incapacitated = CheckIncapacitated(uid);
if (converted && !incapacitated || !converted && ent.Comp.ShouldUnconvertedBeIncapacitated && incapacitated)
{
counted++;
}
}
if (total == 0)
return;
args.Progress = (float)counted / (float)total;
}
}
+1 -10
View File
@@ -7,16 +7,7 @@ namespace Content.Server._CD.Traits;
public sealed partial class SynthComponent : Component
{
/// <summary>
/// Whether this synthetic is affected by ion storms (electrocution/sparks and
/// alerts when one occurs). False by default. set true by the
/// "Susceptible to Ion Storms" meta trait.
/// </summary>
[DataField]
public bool IonStormAffected;
/// <summary>
/// The chance that the synth is alerted of an ion storm. Only relevant if
/// <see cref="IonStormAffected"/> is true.
/// The chance that the synth is alerted of an ion storm
/// </summary>
[DataField]
public float AlertChance = 0.3f;
+2 -27
View File
@@ -1,12 +1,8 @@
using Content.Server.Body.Systems;
using Content.Server.Chat.Systems;
using Content.Server.Database;
using Content.Shared.Body.Components;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Chat.TypingIndicator;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Speech.Components;
using Content.Shared.Tag;
using Robust.Shared.Prototypes;
namespace Content.Server._CD.Traits;
@@ -16,22 +12,15 @@ public sealed class SynthSystem : EntitySystem
// Begin DeltaV - make strings static readonly
private static readonly ProtoId<TypingIndicatorPrototype> RobotTypingIndicator = "robot";
private static readonly ProtoId<ReagentPrototype> SynthBloodReagent = "SynthBlood";
private static readonly ProtoId<TagPrototype> SyntheticEmotesTag = "SyntheticEmotes";
private static readonly ProtoId<EmoteSoundsPrototype> SyntheticEmoteSounds = "SyntheticEmoteSounds";
// End DeltaV
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ChatSystem _chat = default!;
private EmoteSoundsPrototype? _syntheticEmoteSounds; // delta-v - proper synthe emote implementation
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SynthComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<SynthComponent, EmoteEvent>(OnEmote); // delta-v - proper synthe emote implementation
}
private void OnStartup(EntityUid uid, SynthComponent component, ComponentStartup args)
@@ -49,19 +38,5 @@ public sealed class SynthSystem : EntitySystem
_bloodstream.ChangeBloodReagents((uid, bloodstream), new([new(SynthBloodReagent, bloodstream.BloodReferenceSolution.Volume)]));
}
// End DeltaV
_tag.AddTag(uid, SyntheticEmotesTag); // delta-v - proper synthe emote implementation
}
// Start DeltaV - proper synthe emote implementation
private void OnEmote(EntityUid uid, SynthComponent component, ref EmoteEvent args)
{
if (args.Handled)
return;
_syntheticEmoteSounds ??= _proto.Index(SyntheticEmoteSounds);
args.Handled = _chat.TryPlayEmoteSound(uid, _syntheticEmoteSounds, args.Emote);
}
// End DeltaV
}
}
@@ -1,73 +0,0 @@
using Content.Shared.Audio;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Map;
namespace Content.Server.Audio;
/// <summary>
/// Extends upstream's Content.Server/Audio/ServerGlobalSoundSystem.cs.
/// </summary>
public sealed partial class ServerGlobalSoundSystem
{
/// <summary>
/// DeltaV - Plays a sound globally for all players, no matter where they are.
/// </summary>
/// <param name="specifier"></param>
/// <param name="audioParams"></param>
public void PlayGlobal(ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
{
var msg = new GameGlobalSoundEvent(specifier, audioParams);
RaiseNetworkEvent(msg);
}
public void StopGlobalEventMusic(StationEventMusicType type)
{
var msg = new StopStationEventMusic(type);
RaiseNetworkEvent(msg);
}
public void DispatchGlobalEventMusic(SoundSpecifier sound, StationEventMusicType type)
{
DispatchGlobalEventMusic(_audio.ResolveSound(sound), type);
}
public void DispatchGlobalEventMusic(ResolvedSoundSpecifier specifier, StationEventMusicType type)
{
var audio = AudioParams.Default.WithVolume(-8);
var msg = new StationEventMusicEvent(specifier, type, audio);
RaiseNetworkEvent(msg);
}
/// <summary>
/// DeltaV - Plays a sound globally for all players on a specified Map.
/// </summary>
/// <param name="specifier"></param>
/// <param name="audioParams"></param>
public void PlayGlobalOnMap(MapId map, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
{
var msg = new GameGlobalSoundEvent(specifier, audioParams);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
public void StopMapEventMusic(MapId map, StationEventMusicType type)
{
var msg = new StopStationEventMusic(type);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
public void DispatchMapEventMusic(MapId map, SoundSpecifier sound, StationEventMusicType type)
{
DispatchMapEventMusic(map, _audio.ResolveSound(sound), type);
}
public void DispatchMapEventMusic(MapId map, ResolvedSoundSpecifier specifier, StationEventMusicType type)
{
var audio = AudioParams.Default.WithVolume(-8);
var msg = new StationEventMusicEvent(specifier, type, audio);
var filter = Filter.Empty().AddInMap(map);
RaiseNetworkEvent(msg, filter);
}
}
@@ -6,6 +6,12 @@ namespace Content.Server._DV.CartridgeLoader.Cartridges;
[RegisterComponent, Access(typeof(NanoChatCartridgeSystem))]
public sealed partial class NanoChatCartridgeComponent : Component
{
/// <summary>
/// Station entity to keep track of.
/// </summary>
[DataField]
public EntityUid? Station;
/// <summary>
/// The NanoChat card to keep track of.
/// </summary>
@@ -2,8 +2,9 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.CartridgeLoader;
using Content.Server.Power.Components;
using Content.Server.Radio;
using Content.Server.Radio.EntitySystems;
using Content.Server.Station.Systems;
using Content.Shared.Access.Components;
using Content.Shared.CartridgeLoader;
using Content.Shared.CCVar;
@@ -29,7 +30,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedNanoChatSystem _nanoChat = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
[Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly StationSystem _station = default!;
private EntityQuery<PdaComponent> _pdaQuery;
private EntityQuery<NanoChatCardComponent> _cardQuery;
@@ -455,7 +456,9 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
{
// First verify we can send from this device
var channel = _prototype.Index(sender.Comp.RadioChannel);
if (!CanSend(sender))
var sendAttemptEvent = new RadioSendAttemptEvent(channel, sender);
RaiseLocalEvent(ref sendAttemptEvent);
if (sendAttemptEvent.Cancelled)
return (true, new List<Entity<NanoChatCardComponent>>());
var foundRecipients = new List<Entity<NanoChatCardComponent>>();
@@ -484,19 +487,26 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
if (receiverCart.Card != recipient.Owner)
continue;
var receiverMapId = Transform(receiverUid).MapID;
var senderMapId = Transform(sender).MapID;
// Check if devices are on same station/map
var recipientStation = _station.GetOwningStation(receiverUid);
var senderStation = _station.GetOwningStation(sender);
// Must be on the same map unless long range is allowed.
if (!channel.LongRange && receiverMapId != senderMapId)
// Both entities must be on a station
if (recipientStation == null || senderStation == null)
continue;
// Check if telecommunications are active on both ends
if (!_radio.HasActiveServer(receiverMapId, receiverCart.RadioChannel) || !_radio.HasActiveServer(senderMapId, sender.Comp.RadioChannel))
// Must be on same map/station unless long range allowed
if (!channel.LongRange && recipientStation != senderStation)
continue;
// Needs telecomms
if (!HasActiveServer(senderStation.Value) || !HasActiveServer(recipientStation.Value))
continue;
// Check if recipient can receive
if (!CanReceive(sender, receiverUid))
var receiveAttemptEv = new RadioReceiveAttemptEvent(channel, sender, receiverUid);
RaiseLocalEvent(ref receiveAttemptEv);
if (receiveAttemptEv.Cancelled)
continue;
// Found valid cartridge that can receive
@@ -509,26 +519,21 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
}
/// <summary>
/// Tests if a NanoChat cartridge can send messages
/// Checks if there are any active telecomms servers on the given station
/// </summary>
/// <param name="sender">The NanoChat cartridge trying to send</param>
private bool CanSend(Entity<NanoChatCartridgeComponent> sender)
private bool HasActiveServer(EntityUid station)
{
var sendAttemptEvent = new RadioSendAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender);
RaiseLocalEvent(ref sendAttemptEvent);
return !sendAttemptEvent.Cancelled;
}
// I have no idea why this isn't public in the RadioSystem
var query =
EntityQueryEnumerator<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent>();
/// <summary>
/// Tests if a receiver can receive from a given NanoChat cartridge
/// </summary>
/// <param name="sender">The NanoChat cartridge trying to send</param>
/// <param name="receiver">The receiver cartridge trying to receive</param>
private bool CanReceive(Entity<NanoChatCartridgeComponent> sender, EntityUid receiver)
{
var receiveAttemptEv = new RadioReceiveAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender, receiver);
RaiseLocalEvent(ref receiveAttemptEv);
return !receiveAttemptEv.Cancelled;
while (query.MoveNext(out var uid, out _, out _, out var power))
{
if (_station.GetOwningStation(uid) == station && power.Powered)
return true;
}
return false;
}
/// <summary>
@@ -683,15 +688,16 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
private void UpdateUI(Entity<NanoChatCartridgeComponent> ent, EntityUid loader)
{
List<NanoChatRecipient>? contacts;
if (CanSend(ent) && _radio.HasActiveServer(Transform(ent).MapID, ent.Comp.RadioChannel))
if (_station.GetOwningStation(loader) is { } station)
{
ent.Comp.Station = station;
contacts = [];
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
{
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName)
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName && _station.GetOwningStation(entityId) == station)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
}
@@ -700,7 +706,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
var borgQuery = AllEntityQuery<NanoChatCardComponent, BorgChassisComponent>();
while (borgQuery.MoveNext(out var borgId, out var borgChatCard, out var _))
{
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber)
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(borgId) == station)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(borgId).EntityName));
}
@@ -709,7 +715,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
var aiQuery = AllEntityQuery<NanoChatCardComponent, StationAiHeldComponent>();
while (aiQuery.MoveNext(out var aiId, out var aiChatCard, out var _))
{
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber)
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(aiId) == station)
{
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(aiId).EntityName));
}
@@ -521,7 +521,7 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
{
finComp.CurrentState = FinaleState.Unavailable;
_popup.PopupCoordinates(Loc.GetString("cosmiccult-monument-powerdown"), Transform(gameruleMonument).Coordinates, PopupType.Large);
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
_sound.StopStationEventMusic(gameruleMonument, StationEventMusicType.CosmicCult);
_monument.UpdateMonumentAppearance(gameruleMonument, false);
}
@@ -82,9 +82,11 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(1));
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 2);
_sound.DispatchGlobalEventMusic(comp.SelectedSong, StationEventMusicType.CosmicCult);
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
null, false, null, Color.FromHex("#cae8e8"));
_sound.DispatchStationEventMusic(uid, comp.SelectedSong, StationEventMusicType.CosmicCult);
_chatSystem.DispatchStationAnnouncement(uid,
Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
null, false, null,
Color.FromHex("#cae8e8"));
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
if (stationUid != null)
@@ -126,8 +128,8 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
if (stationUid != null)
_alert.SetLevel(stationUid.Value, "green", true, true, true);
_sound.PlayGlobal(_audio.ResolveSound(comp.CancelEventSound));
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(comp.CancelEventSound));
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
uid.Comp.CurrentState = FinaleState.ReadyFinale;
if (TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
@@ -8,7 +8,6 @@ using Content.Shared.Mind;
using Content.Shared.Roles;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -26,7 +25,6 @@ public sealed class CosmicChantrySystem : EntitySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
/// <summary>
@@ -81,11 +79,11 @@ public sealed class CosmicChantrySystem : EntitySystem
comp.SpawnTimer = _timing.CurTime + comp.SpawningTime;
comp.CountdownTimer = _timing.CurTime + comp.EventTime;
var targetMap = _transform.GetMapId(ent.Owner);
_sound.PlayGlobalOnMap(targetMap, _audio.ResolveSound(comp.ChantryAlarm));
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-chantry-location",
("location", indicatedLocation)), null, false, null, Color.FromHex("#cae8e8"));
_sound.PlayGlobalOnStation(ent, _audio.ResolveSound(comp.ChantryAlarm));
_chatSystem.DispatchStationAnnouncement(ent,
Loc.GetString("cosmiccult-chantry-location", ("location", indicatedLocation)),
null, false, null,
Color.FromHex("#cae8e8"));
if (_mind.TryGetMind(comp.InternalVictim, out _, out var mind))
mind.PreventGhosting = true;
@@ -79,13 +79,13 @@ public sealed class MonumentSystem : SharedMonumentSystem
{
comp.SongTimer = null;
if (comp.SelectedSong is { } song)
_sound.DispatchGlobalEventMusic(song, StationEventMusicType.CosmicCult);
_sound.DispatchStationEventMusic(uid, song, StationEventMusicType.CosmicCult);
}
if (comp.CurrentState == FinaleState.ActiveFinale && comp.FinaleAnnounceCheck && comp.FinaleTimer - _timing.CurTime < comp.VisualsThreshold)
{
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
_chatSystem.DispatchStationAnnouncement(uid, Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
comp.FinaleAnnounceCheck = false;
}
@@ -90,7 +90,6 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
private void OnPowerChanged(Entity<DeepFryerComponent> ent, ref PowerChangedEvent args)
{
UpdateAppearance(ent);
ResetCookingItemsStartTime(ent);
}
private void OnThrowHitBy(Entity<DeepFryerComponent> ent, ref ThrowHitByEvent args)
@@ -675,26 +674,4 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
// Add the split solution to the food
Solution.AddSolution(foodSolutionEnt.Value, transferredSolution);
}
/// <summary>
/// Resets the starttime for cooking items (on power change)
/// </summary>
private void ResetCookingItemsStartTime(Entity<DeepFryerComponent> ent)
{
if (_power.IsPowered(ent.Owner) && !ent.Comp.WasPreviouslyPowered)
{
// Power just came back on - reset all cooking timers
foreach (var itemUid in ent.Comp.CookingItems.Keys.ToList())
{
var cookingItem = ent.Comp.CookingItems[itemUid];
cookingItem.TimeStarted = _timing.CurTime;
ent.Comp.CookingItems[itemUid] = cookingItem;
}
ent.Comp.WasPreviouslyPowered = true;
}
else if (!_power.IsPowered(ent.Owner) && ent.Comp.WasPreviouslyPowered)
{
ent.Comp.WasPreviouslyPowered = false;
}
}
}
@@ -1,39 +0,0 @@
using Content.Server.Fax;
using Content.Shared._DV.Silicons;
using Content.Shared.Fax;
using Content.Shared.Fax.Components;
namespace Content.Server._DV.Silicons;
public sealed class StationAiFaxSystem : SharedStationAiFaxSystem
{
[Dependency] private readonly FaxSystem _fax = default!;
public override void Initialize()
{
base.Initialize();
Subs.BuiEvents<StationAiFaxComponent>(FaxUiKey.Key,
subs =>
{
subs.Event<StationAiFaxSendMessage>(OnSend);
subs.Event<StationAiFaxDuplicateMessage>(OnDuplicate);
});
}
private void OnSend(Entity<StationAiFaxComponent> ent, ref StationAiFaxSendMessage args)
{
if (GetDocument(ent, args.Document) is not { } document)
return;
_fax.Send(ent, Comp<FaxMachineComponent>(ent), args, document);
}
private void OnDuplicate(Entity<StationAiFaxComponent> ent, ref StationAiFaxDuplicateMessage args)
{
if (GetDocument(ent, args.Document) is not { } document)
return;
_fax.Copy(ent, Comp<FaxMachineComponent>(ent), args, document);
}
}
@@ -1,9 +1,9 @@
using Content.Server.Power.Components;
using Content.Server._DV.StationEvents.Events;
using Content.Server.StationEvents.Events;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server._DV.StationEvents.Components;
namespace Content.Server.StationEvents.Components;
/// <summary>
/// When fired, turns off power on the station for a few seconds, playing <see cref="EpsilonEventRuleComponent.PowerOffSound"/>
@@ -1,15 +1,14 @@
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.StationEvents.Events;
using Content.Server.StationEvents.Components;
using Content.Shared.GameTicking.Components;
using Content.Shared.Station.Components;
using Content.Server.AlertLevel;
using Content.Server.Audio;
using Content.Server._DV.StationEvents.Components;
using Robust.Shared.Audio.Systems;
namespace Content.Server._DV.StationEvents.Events;
namespace Content.Server.StationEvents.Events
{
public sealed class EpsilonEventRule : StationEventSystem<EpsilonEventRuleComponent>
{
[Dependency] private readonly ApcSystem _apcSystem = default!;
@@ -26,7 +25,7 @@ public sealed class EpsilonEventRule : StationEventSystem<EpsilonEventRuleCompon
return;
component.AffectedStation = chosenStation.Value;
// Plays the power off sound for those who are on the station.
// Plays the power off sound to the station.
_sound.PlayGlobalOnStation(component.AffectedStation, _audio.ResolveSound(component.PowerOffSound));
var query = AllEntityQuery<ApcComponent, TransformComponent>();
@@ -71,3 +70,4 @@ public sealed class EpsilonEventRule : StationEventSystem<EpsilonEventRuleCompon
_alertLevelSystem.SetLevel(component.AffectedStation, "epsilon", true, true, true);
}
}
}
+1 -2
View File
@@ -109,8 +109,7 @@ public sealed class TraitSystem : EntitySystem
JobId = jobId,
SpeciesId = speciesId,
Profile = profile,
StatusEffects = _statusEffects,
SelectedTraits = selectedTraits
StatusEffects = _statusEffects
};
foreach (var traitId in selectedTraits)
@@ -0,0 +1,8 @@
namespace Content.Server._DV.Uprising;
[RegisterComponent, Access(typeof(UprisingRuleSystem))]
public sealed partial class UprisingConsoleComponent : Component
{
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(5);
}
@@ -0,0 +1,54 @@
using Content.Shared._DV.Roles;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server._DV.Uprising;
[RegisterComponent, Access(typeof(UprisingRuleSystem)), AutoGenerateComponentPause]
public sealed partial class UprisingRuleComponent : Component
{
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? NukeAnnouncementAt;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? NukeTimeAt;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? FirstWarningAt;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? ImpendingWarningAt;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? FinalWarningAt;
[DataField]
public TimeSpan NukeAnnouncementDelay = TimeSpan.FromMinutes(30);
[DataField]
public TimeSpan NukeTimeDelay = TimeSpan.FromMinutes(90);
[DataField]
public TimeSpan FirstWarning = TimeSpan.FromMinutes(30);
[DataField]
public TimeSpan ImpendingWarning = TimeSpan.FromMinutes(15);
[DataField]
public TimeSpan FinalWarning = TimeSpan.FromMinutes(5);
[DataField]
public float NukeDuration = 60f;
[DataField]
public (UprisingSide, TimeSpan)? SideWinsAt;
[DataField]
public UprisingSide? SideWon;
[DataField]
public Dictionary<UprisingSide, TimeSpan> SideTimes = new()
{
{ UprisingSide.Insurgent , TimeSpan.FromMinutes(3) },
{ UprisingSide.Loyalist , TimeSpan.FromMinutes(3) },
};
}
@@ -0,0 +1,341 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Antag;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Nuke;
using Content.Server.Pinpointer;
using Content.Server.Roles;
using Content.Shared._DV.Roles;
using Content.Shared._DV.Uprising;
using Content.Shared.Chat;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.DoAfter;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Content.Shared.Roles;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server._DV.Uprising;
public sealed class UprisingRuleSystem : GameRuleSystem<UprisingRuleComponent>
{
[Dependency] private readonly SharedChatSystem _chat = default!;
[Dependency] private readonly NukeSystem _nuke = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LoyalistRoleComponent, GetBriefingEvent>(OnGetLoyalistBriefing);
SubscribeLocalEvent<InsurgentRoleComponent, GetBriefingEvent>(OnGetInsurgentBriefing);
SubscribeLocalEvent<UprisingVictoryObjectiveComponent, ObjectiveGetProgressEvent>(OnUprisingVictoryProgress);
SubscribeLocalEvent<UprisingConsoleComponent, GetVerbsEvent<AlternativeVerb>>(OnGetConsoleVerbs);
SubscribeLocalEvent<UprisingConsoleComponent, UprisingArmDoAfter>(OnArm);
SubscribeLocalEvent<UprisingConsoleComponent, UprisingDisarmDoAfter>(OnDisarm);
}
private void OnUprisingVictoryProgress(Entity<UprisingVictoryObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
{
if (GetRule() is not { } rule)
return;
if (rule.Comp1.SideWinsAt is var (side, _) && ent.Comp.Side == side)
args.Progress = 0.5f;
else if (rule.Comp1.SideWon == ent.Comp.Side)
args.Progress = 1f;
else
args.Progress = 0f;
}
protected override void Started(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
component.NukeAnnouncementAt = Timing.CurTime + component.NukeAnnouncementDelay;
component.NukeTimeAt = Timing.CurTime + component.NukeTimeDelay;
component.FirstWarningAt = component.NukeTimeAt - component.FirstWarning;
component.ImpendingWarningAt = component.NukeTimeAt - component.ImpendingWarning;
component.FinalWarningAt = component.NukeTimeAt - component.FinalWarning;
}
protected override void ActiveTick(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
base.ActiveTick(uid, component, gameRule, frameTime);
if (component.NukeAnnouncementAt is { } nukeAnnouncement && nukeAnnouncement <= Timing.CurTime)
{
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.NukeTimeDelay;
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-nuke-announcement", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
component.NukeAnnouncementAt = null;
}
if (component.FirstWarningAt is { } firstWarning && firstWarning <= Timing.CurTime)
{
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FirstWarning;
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-first-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
component.FirstWarningAt = null;
}
if (component.ImpendingWarningAt is { } impendingWarning && impendingWarning <= Timing.CurTime)
{
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.ImpendingWarning;
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-impending-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
component.ImpendingWarningAt = null;
}
if (component.FinalWarningAt is { } finalWarning && finalWarning <= Timing.CurTime)
{
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FinalWarning;
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-final-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
component.FinalWarningAt = null;
}
if (component.NukeTimeAt is { } nukeTime && nukeTime <= Timing.CurTime)
{
component.NukeTimeAt = null;
var query = EntityQueryEnumerator<NukeComponent>();
while (query.MoveNext(out var bombUid, out var bomb))
{
_nuke.SetRemainingTime(bombUid, component.NukeDuration, bomb);
_itemSlots.SetLock(bombUid, bomb.DiskSlot, false);
_itemSlots.TryEject(bombUid, bomb.DiskSlot, null, out _, true);
_itemSlots.SetLock(bombUid, bomb.DiskSlot, true);
_nuke.ArmBomb(bombUid, bomb, "uprising-announcement-nuke-armed");
}
}
if (component.SideWinsAt is var (winningSide, winningTime) && winningTime <= Timing.CurTime)
{
component.SideWinsAt = null;
var query = EntityQueryEnumerator<NukeComponent>();
while (query.MoveNext(out var bombUid, out var bomb))
{
_nuke.DisarmBomb(bombUid, bomb);
}
component.SideWon = winningSide;
_chat.DispatchGlobalAnnouncement(
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}"),
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.sender"),
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.color")));
GameTicker.EndRound();
}
}
protected override void AppendRoundEndText(EntityUid uid,
UprisingRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
if (component.SideWinsAt is var (partialSide, _))
{
args.AddLine(Loc.GetString($"uprising-outcome-{partialSide}-partial"));
}
else if (component.SideWon is { } winningSide)
{
args.AddLine(Loc.GetString($"uprising-outcome-{winningSide}-full"));
}
else
{
args.AddLine(Loc.GetString("uprising-outcome-none"));
}
args.AddLine(Loc.GetString("uprising-leads"));
var antags = _antag.GetAntagIdentifiers(uid);
var leads = new HashSet<EntityUid>();
foreach (var (mind, sessionData, name) in antags)
{
leads.Add(mind);
if (_role.MindHasRole<UprisingRoleComponent>(mind, out var it))
args.AddLine(Loc.GetString($"uprising-leads-user.{it.Value.Comp2.Side}", ("name", name), ("user", sessionData.UserName)));
else
args.AddLine(Loc.GetString("uprising-leads-user.Unknown", ("name", name), ("user", sessionData.UserName)));
}
args.AddLine(string.Empty);
var lines = new Dictionary<UprisingSide, List<string>>();
foreach (var value in Enum.GetValues<UprisingSide>())
{
lines[value] = new();
}
var minds = EntityQueryEnumerator<MindComponent>();
while (minds.MoveNext(out var mindUid, out var mindComponent))
{
if (leads.Contains(mindUid))
continue;
if (!_role.MindHasRole<UprisingRoleComponent>((mindUid, mindComponent), out var role) || mindComponent.OriginalOwnerUserId is not { } owner)
continue;
if (!_player.TryGetPlayerData(owner, out var data))
continue;
lines[role.Value.Comp2.Side].Add(Loc.GetString($"uprising-members-user", ("name", mindComponent.CharacterName ?? string.Empty), ("user", data.UserName)));
}
foreach (var value in Enum.GetValues<UprisingSide>())
{
args.AddLine(Loc.GetString($"uprising-members.{value}"));
foreach (var line in lines[value])
{
args.AddLine(line);
}
args.AddLine(string.Empty);
}
}
private void OnGetLoyalistBriefing(Entity<LoyalistRoleComponent> ent, ref GetBriefingEvent args)
{
args.Append(Loc.GetString("antag-Loyalist.briefing"));
}
private void OnGetInsurgentBriefing(Entity<InsurgentRoleComponent> ent, ref GetBriefingEvent args)
{
args.Append(Loc.GetString("antag-Insurgent.briefing"));
}
private Entity<UprisingRuleComponent, GameRuleComponent>? GetRule()
{
var enumerator = QueryActiveRules();
while (enumerator.MoveNext(out var uid, out _, out var uprising, out var gamerule))
{
return (uid, uprising, gamerule);
}
return null;
}
private void StartWinning(UprisingSide side, EntityUid console)
{
if (GetRule() is not { } rule)
return;
var location = _navMap.GetNearestBeaconString(console);
var announcement = Loc.GetString($"uprising-announcement-start-victory-announcement-{side}",
("time", (int) Math.Round(rule.Comp1.SideTimes[side].TotalSeconds)),
("location", FormattedMessage.RemoveMarkupOrThrow(location)));
_chat.DispatchGlobalAnnouncement(
announcement,
Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.sender"),
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.color")));
rule.Comp1.SideWinsAt = (side, Timing.CurTime + rule.Comp1.SideTimes[side]);
}
private void StopWinning()
{
if (GetRule() is not { } rule || rule.Comp1.SideWinsAt is not var (side, time))
return;
rule.Comp1.SideTimes[side] = Timing.CurTime - time;
_chat.DispatchGlobalAnnouncement(
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}"),
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.sender"),
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.color")));
rule.Comp1.SideWinsAt = null;
}
private void OnGetConsoleVerbs(Entity<UprisingConsoleComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
if (GetRule() is not { } rule)
return;
var user = args.User;
if (rule.Comp1.SideWinsAt is var (disarmSide, _))
{
args.Verbs.Add(new ()
{
Act = () => StartDisarming(ent, user),
Text = Loc.GetString($"uprising-disarm-verb.{disarmSide}"),
});
return;
}
if (!CheckCanArm(args.User, out var armSide))
return;
args.Verbs.Add(new()
{
Act = () => StartArming(ent, user),
Text = Loc.GetString($"uprising-arm-verb.{armSide}"),
});
}
private bool CheckCanArm(EntityUid user, [NotNullWhen(true)] out UprisingSide? side)
{
side = null;
if (_mind.GetMind(user) is not { } mind || !TryComp<MindComponent>(mind, out var mindComp))
return false;
if (!_role.MindHasRole<UprisingRoleComponent>(mind, out var uprisingRole))
return false;
foreach (var objective in mindComp.Objectives)
{
if (!HasComp<UprisingArmRequiredObjectiveComponent>(objective))
continue;
if (_objectives.GetProgress(objective, (mind, mindComp)) is not >= 1)
return false;
}
side = uprisingRole.Value.Comp2.Side;
return true;
}
private void StartDisarming(Entity<UprisingConsoleComponent> ent, EntityUid user)
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
user,
ent.Comp.Delay,
new UprisingDisarmDoAfter(),
ent.Owner));
}
private void StartArming(Entity<UprisingConsoleComponent> ent, EntityUid user)
{
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
user,
ent.Comp.Delay,
new UprisingArmDoAfter(),
ent.Owner));
}
private void OnArm(Entity<UprisingConsoleComponent> ent, ref UprisingArmDoAfter args)
{
if (args.Handled || args.Cancelled || !CheckCanArm(args.User, out var side))
return;
StartWinning(side.Value, ent);
}
private void OnDisarm(Entity<UprisingConsoleComponent> ent, ref UprisingDisarmDoAfter args)
{
if (args.Handled || args.Cancelled)
return;
StopWinning();
}
}
+1 -9
View File
@@ -41,13 +41,7 @@ namespace Content.Shared.Chat
[NonSerialized]
public bool Read;
/// <summary>
/// DeltaV - If not null, contains the radio channel proto ID that the message was sent on.
/// Used to replace the channel color client-side.
/// </summary>
public string? RadioChannelProtoId = null;
public ChatMessage(ChatChannel channel, string message, string wrappedMessage, NetEntity source, int? senderKey, bool hideChat = false, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, string? radioChannelProto = null) // DeltaV - added radioChannelProto
public ChatMessage(ChatChannel channel, string message, string wrappedMessage, NetEntity source, int? senderKey, bool hideChat = false, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0)
{
Channel = channel;
Message = message;
@@ -58,7 +52,6 @@ namespace Content.Shared.Chat
MessageColorOverride = colorOverride;
AudioPath = audioPath;
AudioVolume = audioVolume;
RadioChannelProtoId = radioChannelProto; // DeltaV
}
public ChatMessage(ChatMessage copyFrom)
@@ -73,7 +66,6 @@ namespace Content.Shared.Chat
AudioPath = copyFrom.AudioPath;
AudioVolume = copyFrom.AudioVolume;
Read = copyFrom.Read;
RadioChannelProtoId = copyFrom.RadioChannelProtoId; // DeltaV
}
}
@@ -2,7 +2,6 @@ using System.Linq;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Ghost;
using Content.Shared.Interaction;
using Content.Shared.Inventory; // Harmony - for lanyards
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
@@ -294,7 +293,7 @@ namespace Content.Shared.Examine
/// If you're pushing multiple messages that should be grouped together (or ordered in some way),
/// call <see cref="PushGroup"/> before pushing and <see cref="PopGroup"/> when finished.
/// </summary>
public sealed class ExaminedEvent : EntityEventArgs, IInventoryRelayEvent //Harmony InventoryRelayEvent for Lanyards
public sealed class ExaminedEvent : EntityEventArgs
{
/// <summary>
/// The message that will be displayed as the examine text.
@@ -530,7 +529,6 @@ namespace Content.Shared.Examine
}
private record ExamineMessagePart(FormattedMessage Message, int Priority, bool DoNewLine, string? Group);
public SlotFlags TargetSlots { get; } = SlotFlags.NECK; // Harmony - For targeting lanyards.
}
@@ -63,7 +63,7 @@ public sealed partial class FaxMachineComponent : Component
/// Sound to play when fax printing new message
/// </summary>
[DataField]
public SoundSpecifier? PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg"); // DeltaV - nullable
public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg");
/// <summary>
/// Sound to play when fax successfully send message
+1 -4
View File
@@ -17,15 +17,13 @@ public sealed class FaxUiState : BoundUserInterfaceState
public bool IsPaperInserted { get; }
public bool CanSend { get; }
public bool CanCopy { get; }
public bool CanSendCooldown; // DeltaV - AI fax
public FaxUiState(string deviceName,
Dictionary<string, string> peers,
bool canSend,
bool canCopy,
bool isPaperInserted,
string? destAddress,
bool canSendCooldown) // DeltaV - AI fax
string? destAddress)
{
DeviceName = deviceName;
AvailablePeers = peers;
@@ -33,7 +31,6 @@ public sealed class FaxUiState : BoundUserInterfaceState
CanSend = canSend;
CanCopy = canCopy;
DestinationAddress = destAddress;
CanSendCooldown = canSendCooldown; // DeltaV - AI fax
}
}
@@ -33,7 +33,6 @@ using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Wieldable;
using Content.Shared.Zombies;
using Content.Shared.Examine; //Harmony
namespace Content.Shared.Inventory;
@@ -65,7 +64,6 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, BeforeEmoteEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, StoodEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, DownedEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, ExaminedEvent>(RelayInventoryEvent); // Harmony - added for lanyards.
// by-ref events
SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);
@@ -1,8 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement; //Harmony Lanyards
using Content.Shared.Inventory; //Harmony Lanyards
using Content.Shared.Labels.Components;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Paper;
@@ -10,7 +8,6 @@ using Content.Shared.Tag; // DeltaV
using Robust.Shared.Containers;
using Robust.Shared.Prototypes; // DeltaV
using Robust.Shared.Utility;
using System.Linq; //Harmony Lanyards
namespace Content.Shared.Labels.EntitySystems;
@@ -37,11 +34,6 @@ public sealed partial class LabelSystem : EntitySystem
SubscribeLocalEvent<PaperLabelComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
SubscribeLocalEvent<PaperLabelComponent, EntRemovedFromContainerMessage>(OnContainerModified);
SubscribeLocalEvent<PaperLabelComponent, ExaminedEvent>(OnExamined);
// Harmony - OnExamined can now be inventory relayed from the neck slot for lanyard implementation,
// getting the entity's pronouns and changing the label inspection text to reflect it being from a lanyard.
// This would cause any other neck slot item with the label component to be described as a lanyard on inspection,
// but currently no others exist.
SubscribeLocalEvent<PaperLabelComponent, InventoryRelayedEvent<ExaminedEvent>>((e, c, ev) => OnExaminedInInventory(e, c, ev.Args));
}
private void OnLabelCompMapInit(Entity<LabelComponent> ent, ref MapInitEvent args)
@@ -131,65 +123,9 @@ public sealed partial class LabelSystem : EntitySystem
args.PushMarkup(Loc.GetString("comp-paper-label-has-label"));
var text = paper.Content;
args.PushMarkup(text.TrimEnd());
// Harmony addition begins - shows which stamps have been applied to a label when inspected. Copied from PaperSystem.
if (paper.StampedBy.Count > 0)
{
var commaSeparated =
string.Join(", ", paper.StampedBy.Select(s => Loc.GetString(s.StampedName)));
args.PushMarkup(
Loc.GetString(
"comp-label-examine-detail-stamped-by",
("stamps", commaSeparated))
);
}
// Harmony addition ends
}
}
// Harmony addition begins - version of OnExamined for when the event is inventory relayed. Used when reading from a worn lanyard.
private void OnExaminedInInventory(EntityUid uid, PaperLabelComponent comp, ExaminedEvent args)
{
if (comp.LabelSlot.Item is not { Valid: true } item)
return;
using (args.PushGroup(nameof(PaperLabelComponent)))
{
// UID's parent is saved to be used for localisation grammar when the label is from a lanyard, since the text is changed.
var user = Comp<TransformComponent>(uid).ParentUid;
if (!args.IsInDetailsRange)
{
args.PushMarkup(Loc.GetString("comp-lanyard-has-lanyard-cant-read", ("user", Identity.Entity(user, EntityManager))));
return;
}
if (!TryComp(item, out PaperComponent? paper))
// Assuming yaml has the correct entity whitelist, this should not happen.
return;
if (string.IsNullOrWhiteSpace(paper.Content))
{
args.PushMarkup(Loc.GetString("comp-lanyard-has-lanyard-blank", ("user", Identity.Entity(user, EntityManager))));
return;
}
args.PushMarkup(Loc.GetString("comp-lanyard-has-lanyard", ("user", Identity.Entity(user, EntityManager))));
var text = paper.Content;
args.PushMarkup(text.TrimEnd());
// Harmony - shows which stamps have been applied to a lanyard's label when inspected. Copied from PaperSystem.
if (paper.StampedBy.Count > 0)
{
var commaSeparated =
string.Join(", ", paper.StampedBy.Select(s => Loc.GetString(s.StampedName)));
args.PushMarkup(
Loc.GetString(
"comp-lanyard-examine-detail-stamped-by",
("stamps", commaSeparated))
);
}
}
}
// Harmony Addition Ends
// Not ref-sub due to being used for multiple subscriptions.
private void OnContainerModified(EntityUid uid, PaperLabelComponent label, ContainerModifiedMessage args)
{
@@ -68,11 +68,6 @@ public abstract class SharedSuitSensorSystem : EntitySystem
/// <returns>True if the sensor is assigned to a station or assigning it was successful. False otherwise.</returns>
public bool CheckSensorAssignedStation(Entity<SuitSensorComponent> sensor)
{
// Begin DeltaV - fallback
if (sensor.Comp.StationId.HasValue && _stationSystem.GetOwningStation(sensor.Owner) is null)
return true;
// End DeltaV - fallback
if (!sensor.Comp.StationId.HasValue && Transform(sensor.Owner).GridUid == null)
return false;
@@ -353,7 +348,7 @@ public abstract class SharedSuitSensorSystem : EntitySystem
var transform = ent.Comp2;
// check if sensor is enabled and worn by user
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User)) // DeltaV - don't block on grids here
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User) || transform.GridUid == null)
return null;
// try to get mobs id from ID slot
+1 -1
View File
@@ -276,7 +276,7 @@ public sealed class PaperSystem : EntitySystem
_appearance.SetData(entity, PaperVisuals.Status, status, appearance);
}
public void UpdateUserInterface(Entity<PaperComponent> entity) // DeltaV - station AI
private void UpdateUserInterface(Entity<PaperComponent> entity)
{
_uiSystem.SetUiState(entity.Owner, PaperUiKey.Key, new PaperBoundUserInterfaceState(entity.Comp.Content, entity.Comp.StampedBy, entity.Comp.Mode));
}
@@ -120,23 +120,10 @@ public abstract partial class SharedBatterySystem : EntitySystem
var rechargerQuery = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
while (rechargerQuery.MoveNext(out var uid, out var recharger, out var battery))
{
// BEGIN DeltaV - Battery Drain
// Currently, NextAutoRecharge is only set when there's an explicit power draw from
// the battery, and it doesn't count a negative charge rate as battery charge being used
// so, this is to patch that in the case that the drain exceeds the recharge.
if (recharger.AutoRechargePauseTime > TimeSpan.Zero && battery.ChargeRate < 0)
{
TrySetChargeCooldown((uid, recharger));
continue;
}
// END DeltaV
if (recharger.NextAutoRecharge == null || curTime < recharger.NextAutoRecharge)
continue;
recharger.NextAutoRecharge = null; // Don't refresh every tick.
Dirty(uid, recharger);
RefreshChargeRate((uid, battery)); // Cooldown is over, apply the new recharge rate.
}
@@ -32,10 +32,4 @@ public sealed partial class PowerCellDrawComponent : Component
/// </summary>
[DataField, AutoNetworkedField]
public float UseCharge;
/// <summary>
/// DeltaV - If the battery's recharge should be paused while power draw is active.
/// </summary>
[DataField, AutoNetworkedField]
public bool PauseRecharging;
}
@@ -17,14 +17,8 @@ public sealed partial class PowerCellSystem
ent.Comp.Enabled = enabled;
Dirty(ent, ent.Comp);
// BEGIN DeltaV - Allow PowerDraw to prevent recharge
if (TryGetBatteryFromSlot(ent.Owner, out var battery) && battery is { } existingBattery)
{
_battery.RefreshChargeRate(existingBattery.AsNullable());
if (ent.Comp.PauseRecharging) _battery.TrySetChargeCooldown(existingBattery.Owner);
}
// END DeltaV
if (TryGetBatteryFromSlot(ent.Owner, out var battery))
_battery.RefreshChargeRate(battery.Value.AsNullable());
}
@@ -24,7 +24,7 @@ public sealed partial class RadioChannelPrototype : IPrototype
public int Frequency { get; private set; } = 0;
[DataField("color")]
public Color Color { get; set; } = Color.Lime; // DeltaV - Client-side radio channel colors, no longer private set
public Color Color { get; private set; } = Color.Lime;
[IdDataField, ViewVariables]
public string ID { get; private set; } = default!;
@@ -0,0 +1,14 @@
using Content.Shared.Actions;
using Content.Shared.Roles.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared.Uprising;
public sealed partial class ConversionAcceptActionEvent : InstantActionEvent
{
[DataField(required: true)]
public EntProtoId MindRole;
[DataField(required: true)]
public EntProtoId<MindRoleComponent> RemoveMindRole;
}
@@ -0,0 +1,33 @@
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Robust.Shared.Network;
namespace Content.Shared.Uprising;
public sealed class ConversionAcceptActionSystem : EntitySystem
{
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
[Dependency] private readonly INetManager _net = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindContainerComponent, ConversionAcceptActionEvent>(OnAcceptConversion);
}
private void OnAcceptConversion(Entity<MindContainerComponent> ent, ref ConversionAcceptActionEvent args)
{
if (_mind.GetMind(ent) is not { } mind)
return;
args.Handled = true;
if (_net.IsServer)
{
_role.MindAddRole(mind, args.MindRole);
_role.MindRemoveRole(mind, args.RemoveMindRole);
}
}
}
@@ -0,0 +1,13 @@
using Content.Shared.Actions;
using Robust.Shared.Prototypes;
namespace Content.Shared.Uprising;
public sealed partial class ConversionOfferActionEvent : EntityTargetActionEvent
{
[DataField(readOnly: true)]
public List<Type> IncompatibleMindRoleTypes;
[DataField]
public EntProtoId AcceptAction;
}
@@ -0,0 +1,35 @@
using Content.Shared.Actions;
using Content.Shared.Mind;
using Content.Shared.Roles;
using Content.Shared.Roles.Components;
namespace Content.Shared.Uprising;
public sealed class ConversionOfferActionSystem : EntitySystem
{
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MindRoleComponent, ConversionOfferActionEvent>(OnOffer);
}
private void OnOffer(Entity<MindRoleComponent> ent, ref ConversionOfferActionEvent args)
{
if (_mind.GetMind(args.Target) is not { } mind)
return;
foreach (var incompatible in args.IncompatibleMindRoleTypes)
{
if (_role.MindHasRole(mind, incompatible, out _))
return;
}
_actions.AddAction(args.Target, args.AcceptAction);
args.Handled = true;
}
}
@@ -0,0 +1,6 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Actions;
[RegisterComponent, NetworkedComponent]
public sealed partial class DeleteOnPerformedActionComponent : Component;
@@ -0,0 +1,18 @@
using Content.Shared.Actions.Events;
namespace Content.Shared._DV.Actions;
public sealed class DeleteOnPerformedActionSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeleteOnPerformedActionComponent, ActionPerformedEvent>(OnActionPerformed);
}
private void OnActionPerformed(Entity<DeleteOnPerformedActionComponent> ent, ref ActionPerformedEvent args)
{
PredictedQueueDel(ent);
}
}
@@ -1,73 +0,0 @@
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
namespace Content.Shared._DV.CCVars;
public sealed partial class DCCVars
{
public static readonly CVarDef<string> RadioColorCommon =
CVarDef.Create("deltav.options.radio.common_color",
"#2cdb2c",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Common radio channel.");
public static readonly CVarDef<string> RadioColorCommand =
CVarDef.Create("deltav.options.radio.command_color",
"#fcdf03",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Command radio channel.");
public static readonly CVarDef<string> RadioColorSecurity =
CVarDef.Create("deltav.options.radio.security_color",
"#8c93f5",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Security radio channel.");
public static readonly CVarDef<string> RadioColorMedical =
CVarDef.Create("deltav.options.radio.medical_color",
"#57b8f0",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Medical radio channel.");
public static readonly CVarDef<string> RadioColorEngineering =
CVarDef.Create("deltav.options.radio.engineering_color",
"#ff733c",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Engineering radio channel.");
public static readonly CVarDef<string> RadioColorScience =
CVarDef.Create("deltav.options.radio.science_color",
"#cd7ccd",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Science radio channel.");
public static readonly CVarDef<string> RadioColorCargo =
CVarDef.Create("deltav.options.radio.cargo_color",
"#b48b57",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Cargo radio channel.");
public static readonly CVarDef<string> RadioColorService =
CVarDef.Create("deltav.options.radio.service_color",
"#539c00",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Service radio channel.");
public static readonly CVarDef<string> RadioColorBinary =
CVarDef.Create("deltav.options.radio.binary_color",
"#5ed7aa",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Binary radio channel.");
public static readonly CVarDef<string> RadioColorJustice =
CVarDef.Create("deltav.options.radio.justice_color",
"#c70667",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Justice radio channel.");
public static readonly CVarDef<string> RadioColorPrison =
CVarDef.Create("deltav.options.radio.prison_color",
"#FFA500",
CVar.CLIENTONLY | CVar.ARCHIVE,
"The color of the Prison radio channel.");
}
@@ -1,4 +0,0 @@
namespace Content.Shared._DV.Fax;
[ByRefEvent]
public readonly record struct FaxPrintedEvent(EntityUid Fax);
@@ -137,12 +137,6 @@ public sealed partial class DeepFryerComponent : Component
/// <seealso cref="ProfessionalChefComponent"/>
[DataField]
public float MissChance = 0.25f;
/// <summary>
/// Check for whether the cooking timer needs to be reset after powering down
/// </summary>
[DataField]
public bool WasPreviouslyPowered = true;
}
/// <summary>
@@ -1,4 +1,3 @@
using Content.Shared.CartridgeLoader;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
@@ -55,11 +54,5 @@ public sealed class PageSenderSystem : EntitySystem
{ PagerConstants.DataBody, body },
};
_deviceNetwork.QueuePacket(ent, null, payload);
if (HasComp<PagerComponent>(ent))
{
var evt = new CartridgeLoaderNotificationSentEvent(Loc.GetString("pager-self-notification"), body);
RaiseLocalEvent(ent, ref evt);
}
}
}
@@ -0,0 +1,36 @@
using Content.Shared.Actions;
using Content.Shared.Mind;
using Robust.Shared.Containers;
using Robust.Shared.Network;
namespace Content.Shared._DV.Roles;
public sealed class ActionGrantRoleSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActionGrantRoleComponent, EntGotInsertedIntoContainerMessage>(OnGotInserted);
SubscribeLocalEvent<ActionGrantRoleComponent, EntGotRemovedFromContainerMessage>(OnGotRemoved);
}
private void OnGotInserted(Entity<ActionGrantRoleComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
if (_net.IsClient || !TryComp<MindComponent>(args.Container.Owner, out var mind) || mind.CurrentEntity is not { } mindContainer)
return;
_actions.GrantContainedActions(mindContainer, ent.Owner);
}
private void OnGotRemoved(Entity<ActionGrantRoleComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
if (_net.IsClient || !TryComp<MindComponent>(args.Container.Owner, out var mind) || mind.CurrentEntity is not { } mindContainer)
return;
_actions.RemoveProvidedActions(mindContainer, ent.Owner);
}
}
@@ -0,0 +1,6 @@
using Robust.Shared.GameStates;
namespace Content.Shared._DV.Roles;
[RegisterComponent, NetworkedComponent]
public sealed partial class ActionGrantRoleComponent : Component;
@@ -0,0 +1,6 @@
using Content.Shared.Roles.Components;
namespace Content.Shared._DV.Roles;
[RegisterComponent]
public sealed partial class InsurgentRoleComponent : BaseMindRoleComponent;
@@ -0,0 +1,6 @@
using Content.Shared.Roles.Components;
namespace Content.Shared._DV.Roles;
[RegisterComponent]
public sealed partial class LoyalistRoleComponent : BaseMindRoleComponent;
@@ -0,0 +1,12 @@
using Content.Shared.Objectives.Components;
using Content.Shared.Roles.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Roles;
[RegisterComponent]
public sealed partial class ObjectiveRoleComponent : BaseMindRoleComponent
{
[DataField(required: true)]
public List<EntProtoId<ObjectiveComponent>> Objectives;
}
@@ -0,0 +1,6 @@
using Content.Shared.Roles.Components;
namespace Content.Shared._DV.Roles;
[RegisterComponent]
public sealed partial class UprisingInconvertibleRoleComponent : BaseMindRoleComponent;
@@ -0,0 +1,16 @@
using Content.Shared.Roles.Components;
namespace Content.Shared._DV.Roles;
[RegisterComponent]
public sealed partial class UprisingRoleComponent : BaseMindRoleComponent
{
[DataField(required: true)]
public UprisingSide Side;
}
public enum UprisingSide : byte
{
Insurgent,
Loyalist,
}
@@ -0,0 +1,60 @@
using Content.Shared.Mind;
using Robust.Shared.Containers;
using Robust.Shared.Network;
namespace Content.Shared._DV.Roles;
public sealed class ObjectiveRoleSystem : EntitySystem
{
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ObjectiveRoleComponent, EntGotInsertedIntoContainerMessage>(OnInsertedIntoMind);
SubscribeLocalEvent<ObjectiveRoleComponent, ComponentShutdown>(OnShutdown);
}
private void OnInsertedIntoMind(Entity<ObjectiveRoleComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
if (_net.IsClient)
return;
if (!TryComp<MindComponent>(args.Container.Owner, out var mind))
return;
foreach (var objective in ent.Comp.Objectives)
{
_mind.TryAddObjective(args.Container.Owner, mind, objective);
}
}
private void OnShutdown(Entity<ObjectiveRoleComponent> ent, ref ComponentShutdown args)
{
if (_net.IsClient)
return;
if (!_container.TryGetContainingContainer(ent.Owner, out var container) ||
!TryComp<MindComponent>(container.Owner, out var mind))
return;
var indices = new List<int>();
for (var i = mind.Objectives.Count; i >= 0; i--)
{
var objective = mind.Objectives[i];
if (MetaData(objective).EntityPrototype?.ID is { } id && ent.Comp.Objectives.Contains(id))
{
indices.Add(i);
}
}
foreach (var index in indices)
{
_mind.TryRemoveObjective(container.Owner, mind, index);
}
}
}
@@ -1,82 +0,0 @@
using Content.Shared._DV.Fax;
using Content.Shared.Fax;
using Content.Shared.Paper;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Containers;
namespace Content.Shared._DV.Silicons;
public abstract class SharedStationAiFaxSystem : EntitySystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
[Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StationAiFaxComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<StationAiFaxComponent, FaxPrintedEvent>(OnFaxPrinted);
Subs.BuiEvents<StationAiFaxComponent>(FaxUiKey.Key,
subs =>
{
subs.Event<StationAiFaxExamineMessage>(OnExamine);
subs.Event<StationAiFaxShredMessage>(OnShred);
});
}
protected virtual void OnInserted(Entity<StationAiFaxComponent> ent, ref EntInsertedIntoContainerMessage args)
{
if (HasComp<PaperComponent>(args.Entity))
EnsureComp<StationAiWhitelistComponent>(args.Entity);
}
private void OnExamine(Entity<StationAiFaxComponent> ent, ref StationAiFaxExamineMessage args)
{
if (GetDocument(ent, args.Document) is not { } document)
return;
if (TryComp<PaperComponent>(document, out var paper))
{
paper.Mode = paper.EditingDisabled || paper.StampedBy.Count != 0
? PaperComponent.PaperAction.Read
: PaperComponent.PaperAction.Write;
_paper.UpdateUserInterface((document, paper));
}
_userInterface.OpenUi(document, PaperComponent.PaperUiKey.Key, args.Actor);
}
private void OnShred(Entity<StationAiFaxComponent> ent, ref StationAiFaxShredMessage args)
{
if (GetDocument(ent, args.Document) is { } document)
{
PredictedQueueDel(document);
}
}
protected EntityUid? GetDocument(Entity<StationAiFaxComponent> ent, NetEntity netEntity)
{
if (GetEntity(netEntity) is not { Valid: true } entity)
return null;
if (!_container.TryGetContainingContainer(entity, out var container))
return null;
if (container.Owner != ent.Owner)
return null;
return entity;
}
private void OnFaxPrinted(Entity<StationAiFaxComponent> ent, ref FaxPrintedEvent args)
{
var container = _container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container);
if (!_container.Insert(args.Fax, container, force: true))
{
PredictedQueueDel(args.Fax);
}
}
}
@@ -1,34 +0,0 @@
using Content.Shared._DV.Fax;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Silicons;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedStationAiFaxSystem))]
public sealed partial class StationAiFaxComponent : Component
{
public const string Container = "PaperLibrary";
}
[Serializable, NetSerializable]
public abstract partial class StationAiFaxFileMessage : BoundUserInterfaceMessage
{
public readonly NetEntity Document;
public StationAiFaxFileMessage(NetEntity document)
{
Document = document;
}
}
[Serializable, NetSerializable]
public sealed partial class StationAiFaxSendMessage(NetEntity document) : StationAiFaxFileMessage(document);
[Serializable, NetSerializable]
public sealed partial class StationAiFaxExamineMessage(NetEntity document) : StationAiFaxFileMessage(document);
[Serializable, NetSerializable]
public sealed partial class StationAiFaxDuplicateMessage(NetEntity document) : StationAiFaxFileMessage(document);
[Serializable, NetSerializable]
public sealed partial class StationAiFaxShredMessage(NetEntity document) : StationAiFaxFileMessage(document);
@@ -65,10 +65,4 @@ public sealed class TraitConditionContext
/// The <see cref="HumanoidCharacterProfile"/> of the player, if available.
/// </summary>
public HumanoidCharacterProfile? Profile { get; init; }
/// <summary>
/// The set of trait IDs currently selected by the player, if available.
/// Used by <see cref="TraitDependencyCondition"/> to check conflicts and requirements.
/// </summary>
public IReadOnlySet<ProtoId<TraitPrototype>>? SelectedTraits { get; init; }
}
@@ -1,80 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Traits.Conditions;
/// <summary>
/// Condition that checks trait conflicts and requirements.
/// - Conflicts: if any listed trait is selected, this condition fails.
/// - Requires: all listed traits must be selected for this condition to pass.
/// Tooltips are generated per-entry by the UI rather than via GetTooltip,
/// so GetTooltip returns empty to avoid duplication.
/// </summary>
public sealed partial class TraitDependencyCondition : BaseTraitCondition
{
/// <summary>
/// Traits that are mutually exclusive with this one.
/// If any of these are selected, this condition fails.
/// </summary>
[DataField]
public List<ProtoId<TraitPrototype>> Conflicts = new();
/// <summary>
/// Traits that must already be selected for this condition to pass.
/// All listed traits must be present.
/// </summary>
[DataField]
public List<ProtoId<TraitPrototype>> Requires = new();
protected override bool EvaluateImplementation(TraitConditionContext ctx)
{
if (ctx.SelectedTraits is { } selectedTraits)
{
foreach (var conflict in Conflicts)
{
if (selectedTraits.Contains(conflict))
return false;
}
}
if (Requires.Count > 0)
{
if (ctx.SelectedTraits is not { } selected)
return false;
foreach (var required in Requires)
{
if (!selected.Contains(required))
return false;
}
}
return true;
}
/// <summary>
/// Returns empty. the UI generates conflict/require tooltips directly via
/// <see cref="GetTooltips"/> to avoid duplicating lines across the two tooltip paths.
/// </summary>
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc) => string.Empty;
/// <summary>
/// Returns individual tooltip lines for each conflict and requirement.
/// Used by the UI to build both the requirements preview and the failed-conditions tooltip.
/// </summary>
public IEnumerable<string> GetTooltips(IPrototypeManager proto, ILocalizationManager loc)
{
foreach (var conflict in Conflicts)
{
if (!proto.TryIndex(conflict, out var traitProto))
continue;
yield return loc.GetString("trait-condition-trait-conflict", ("trait", loc.GetString(traitProto.Name)));
}
foreach (var required in Requires)
{
if (!proto.TryIndex(required, out var traitProto))
continue;
yield return loc.GetString("trait-condition-trait-required", ("trait", loc.GetString(traitProto.Name)));
}
}
}
@@ -0,0 +1,14 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Content.Shared._DV.Uprising;
[RegisterComponent]
public sealed partial class CommandStatusObjectiveComponent : Component
{
[DataField(required: true, customTypeSerializer: typeof(CustomHashSetSerializer<string, ComponentNameSerializer>))]
public HashSet<string> ConvertedRoles;
[DataField]
public bool ShouldUnconvertedBeIncapacitated;
}
@@ -0,0 +1,19 @@
using Content.Shared.NPC.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Content.Shared._DV.Uprising;
[RegisterComponent]
public sealed partial class CrewConversionObjectiveComponent : Component
{
[DataField(required: true, customTypeSerializer: typeof(CustomHashSetSerializer<string, ComponentNameSerializer>))]
public HashSet<string> ConvertedRoles;
[DataField(required: true)]
public HashSet<ProtoId<NpcFactionPrototype>> ConversionSourceFactions;
[DataField(required: true)]
public float TargetFraction;
}

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