Compare commits
35 Commits
ed-god-hai
...
ed-2025-02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f20828b1bb | ||
|
|
469bb1a9ec | ||
|
|
ceff2bea00 | ||
|
|
02d3595faa | ||
|
|
ce7bb813d2 | ||
|
|
51754f09ce | ||
|
|
7ea586cc1c | ||
|
|
a8ebcac5c9 | ||
|
|
ba1504d0d6 | ||
|
|
d9e86b3e81 | ||
|
|
66e926843f | ||
|
|
0563e0d67e | ||
|
|
a926c53979 | ||
|
|
bb0c4c66fa | ||
|
|
9c970d203d | ||
|
|
a54960eb81 | ||
|
|
5bdc93b102 | ||
|
|
deea33a36a | ||
|
|
10c868011e | ||
|
|
e8c812f90f | ||
|
|
212e942d21 | ||
|
|
5169ad4e8f | ||
|
|
e540f9cd50 | ||
|
|
aa05cbb49b | ||
|
|
1b20121114 | ||
|
|
6b84315928 | ||
|
|
30a6ebdb26 | ||
|
|
01e4029a11 | ||
|
|
8ea888d821 | ||
|
|
0c6db4db18 | ||
|
|
4442d5e277 | ||
|
|
d8838e31d5 | ||
|
|
38d72434fb | ||
|
|
6b6ac9ac53 | ||
|
|
1047e32944 |
@@ -44,7 +44,7 @@ namespace Content.Benchmarks
|
||||
for (var i = 0; i < Aabbs1.Length; i++)
|
||||
{
|
||||
var aabb = Aabbs1[i];
|
||||
_b2Tree.CreateProxy(aabb, uint.MaxValue, i);
|
||||
_b2Tree.CreateProxy(aabb, i);
|
||||
_tree.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
|
||||
var currentOffset = Vector2.Zero;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
|
||||
|
||||
@@ -2,26 +2,24 @@
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Vertical">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="1" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -36,9 +36,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var newPlayerThreshold = 0;
|
||||
_cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true);
|
||||
|
||||
var uiController = _ui.GetUIController<AHelpUIController>();
|
||||
if (uiController.UIHelper is not AdminAHelpUIHandler helper)
|
||||
return;
|
||||
@@ -62,9 +59,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (info.Connected)
|
||||
sb.Append(info.ActiveThisRound ? '⚫' : '◐');
|
||||
sb.Append('●');
|
||||
else
|
||||
sb.Append(info.ActiveThisRound ? '⭘' : '·');
|
||||
sb.Append(info.ActiveThisRound ? '○' : '·');
|
||||
|
||||
sb.Append(' ');
|
||||
if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
|
||||
@@ -76,12 +73,10 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
// Mark antagonists with symbol
|
||||
if (info.Antag && info.ActiveThisRound)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
// Mark new players with symbol
|
||||
if (IsNewPlayer(info))
|
||||
if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
sb.Append(new Rune(0x23F2)); // ⏲
|
||||
|
||||
sb.AppendFormat("\"{0}\"", text);
|
||||
@@ -89,19 +84,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return sb.ToString();
|
||||
};
|
||||
|
||||
// <summary>
|
||||
// Returns true if the player's overall playtime is under the set threshold
|
||||
// </summary>
|
||||
bool IsNewPlayer(PlayerInfo info)
|
||||
{
|
||||
// Don't show every disconnected player as new, don't show 0-minute players as new if threshold is
|
||||
if (newPlayerThreshold <= 0 || info.OverallPlaytime is null && !info.Connected)
|
||||
return false;
|
||||
|
||||
return (info.OverallPlaytime is null
|
||||
|| info.OverallPlaytime < TimeSpan.FromMinutes(newPlayerThreshold));
|
||||
}
|
||||
|
||||
ChannelSelector.Comparison = (a, b) =>
|
||||
{
|
||||
var ach = AHelpHelper.EnsurePanel(a.SessionId);
|
||||
@@ -111,37 +93,31 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
if (a.IsPinned != b.IsPinned)
|
||||
return a.IsPinned ? -1 : 1;
|
||||
|
||||
// Then, any chat with unread messages.
|
||||
// First, sort by unread. Any chat with unread messages appears first.
|
||||
var aUnread = ach.Unread > 0;
|
||||
var bUnread = bch.Unread > 0;
|
||||
if (aUnread != bUnread)
|
||||
return aUnread ? -1 : 1;
|
||||
|
||||
// Then, any chat with recent messages from the current round
|
||||
// Sort by recent messages during the current round.
|
||||
var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
|
||||
var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
|
||||
if (aRecent != bRecent)
|
||||
return aRecent ? -1 : 1;
|
||||
|
||||
// Sort by connection status. Disconnected players will be last.
|
||||
// Next, sort by connection status. Any disconnected players are grouped towards the end.
|
||||
if (a.Connected != b.Connected)
|
||||
return a.Connected ? -1 : 1;
|
||||
|
||||
// Sort connected players by whether they have joined the round, then by New Player status, then by Antag status
|
||||
// Sort connected players by New Player status, then by Antag status
|
||||
if (a.Connected && b.Connected)
|
||||
{
|
||||
var aNewPlayer = IsNewPlayer(a);
|
||||
var bNewPlayer = IsNewPlayer(b);
|
||||
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
|
||||
// Players who have joined the round will be listed before players in the lobby
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
|
||||
// Within both the joined group and lobby group, new players will be grouped and listed first
|
||||
if (aNewPlayer != bNewPlayer)
|
||||
return aNewPlayer ? -1 : 1;
|
||||
|
||||
// Within all four previous groups, antagonists will be listed first.
|
||||
if (a.Antag != b.Antag)
|
||||
return a.Antag ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return;
|
||||
}
|
||||
|
||||
Title = $"{sel.CharacterName} / {sel.Username} | {Loc.GetString("generic-playtime-title")}: ";
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
|
||||
Title += sel.OverallPlaytime != null ? sel.PlaytimeString : Loc.GetString("generic-unknown-title");
|
||||
if (sel.OverallPlaytime != null)
|
||||
{
|
||||
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
|
||||
}
|
||||
};
|
||||
|
||||
OnOpen += () =>
|
||||
|
||||
@@ -28,6 +28,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
component.InitialName = meta.EntityName;
|
||||
component.InitialDescription = meta.EntityDescription;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,29 @@ internal sealed class ShowSubFloor : LocalizedCommands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowSubFloorForever : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
public const string CommandName = "showsubfloorforever";
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<SubFloorHideSystem>().ShowAll = true;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var components = entMan.EntityQuery<SubFloorHideComponent, SpriteComponent>(true);
|
||||
|
||||
foreach (var (_, sprite) in components)
|
||||
{
|
||||
sprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotifyCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand("showsubfloor");
|
||||
shell.ExecuteCommand("showsubfloorforever");
|
||||
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared.Delivery;
|
||||
|
||||
namespace Content.Client.Delivery;
|
||||
|
||||
public sealed class DeliverySystem : SharedDeliverySystem;
|
||||
@@ -1,45 +0,0 @@
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Delivery;
|
||||
|
||||
public sealed class DeliveryVisualizerSystem : VisualizerSystem<DeliveryComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private static readonly ProtoId<JobIconPrototype> UnknownIcon = "JobIconUnknown";
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, DeliveryComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
_appearance.TryGetData(uid, DeliveryVisuals.JobIcon, out string job, args.Component);
|
||||
|
||||
if (string.IsNullOrEmpty(job))
|
||||
job = UnknownIcon;
|
||||
|
||||
if (!_prototype.TryIndex<JobIconPrototype>(job, out var icon))
|
||||
{
|
||||
args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(_prototype.Index("JobIconUnknown")));
|
||||
return;
|
||||
}
|
||||
|
||||
args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(icon.Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeliveryVisualLayers : byte
|
||||
{
|
||||
Icon,
|
||||
Lock,
|
||||
FragileStamp,
|
||||
JobStamp,
|
||||
PriorityTape,
|
||||
Breakage,
|
||||
Trash,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Orientation="Vertical"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
||||
@@ -349,12 +349,7 @@ namespace Content.Client.Hands.Systems
|
||||
sprite.LayerSetData(index, layerData);
|
||||
|
||||
//Add displacement maps
|
||||
if (hand.Location == HandLocation.Left && handComp.LeftHandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.LeftHandDisplacement, sprite, index, key, revealedLayers);
|
||||
else if (hand.Location == HandLocation.Right && handComp.RightHandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.RightHandDisplacement, sprite, index, key, revealedLayers);
|
||||
//Fallback to default displacement map
|
||||
else if (handComp.HandDisplacement is not null)
|
||||
if (handComp.HandDisplacement is not null)
|
||||
_displacement.TryAddDisplacement(handComp.HandDisplacement, sprite, index, key, revealedLayers);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -14,15 +12,12 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -30,15 +25,6 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
UpdateSprite(component, Comp<SpriteComponent>(uid));
|
||||
}
|
||||
|
||||
private void OnCvarChanged(bool value)
|
||||
{
|
||||
var humanoidQuery = EntityManager.AllEntityQueryEnumerator<HumanoidAppearanceComponent, SpriteComponent>();
|
||||
while (humanoidQuery.MoveNext(out var _, out var humanoidComp, out var spriteComp))
|
||||
{
|
||||
UpdateSprite(humanoidComp, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
UpdateLayers(component, sprite);
|
||||
@@ -232,30 +218,16 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
// Really, markings should probably be a separate component altogether.
|
||||
ClearAllMarkings(humanoid, sprite);
|
||||
|
||||
var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) ||
|
||||
_configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity);
|
||||
// The reason we're splitting this up is in case the character already has undergarment equipped in that slot.
|
||||
var applyUndergarmentTop = censorNudity;
|
||||
var applyUndergarmentBottom = censorNudity;
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
{
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
|
||||
applyUndergarmentTop = false;
|
||||
else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
|
||||
applyUndergarmentBottom = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
|
||||
|
||||
AddUndergarments(humanoid, sprite, applyUndergarmentTop, applyUndergarmentBottom);
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(HumanoidAppearanceComponent humanoid, SpriteComponent sprite)
|
||||
@@ -303,31 +275,6 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUndergarments(HumanoidAppearanceComponent humanoid, SpriteComponent sprite, bool undergarmentTop, bool undergarmentBottom)
|
||||
{
|
||||
if (undergarmentTop && humanoid.UndergarmentTop != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentTop, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
// Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off.
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List<Marking>{ marking });
|
||||
ApplyMarking(prototype, null, true, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (undergarmentBottom && humanoid.UndergarmentBottom != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentBottom, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List<Marking>{ marking });
|
||||
ApplyMarking(prototype, null, true, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
|
||||
@@ -26,12 +26,6 @@ namespace Content.Client.IconSmoothing
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("key")]
|
||||
public string? SmoothKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional keys to smooth with.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> AdditionalKeys = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prepended to the RSI state.
|
||||
/// </summary>
|
||||
|
||||
@@ -376,8 +376,7 @@ namespace Content.Client.IconSmoothing
|
||||
while (candidates.MoveNext(out var entity))
|
||||
{
|
||||
if (smoothQuery.TryGetComponent(entity, out var other) &&
|
||||
other.SmoothKey != null &&
|
||||
(other.SmoothKey == smooth.SmoothKey || smooth.AdditionalKeys.Contains(other.SmoothKey)) &&
|
||||
other.SmoothKey == smooth.SmoothKey &&
|
||||
other.Enabled)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
PlaceHolder="0"
|
||||
Text="1"
|
||||
HorizontalExpand="True" />
|
||||
<Label Name="RecipeCount" Margin="8 0 8 0" MinWidth="90" Align="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -120,8 +120,6 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0)
|
||||
quantity = 1;
|
||||
|
||||
RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count));
|
||||
|
||||
var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
|
||||
RecipeList.Children.Clear();
|
||||
_entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
|
||||
|
||||
@@ -54,6 +54,6 @@ public sealed class AfterLightTargetOverlay : Overlay
|
||||
|
||||
worldHandle.SetTransform(localMatrix);
|
||||
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
}, Color.Transparent);
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ public sealed class PlanetLightSystem : EntitySystem
|
||||
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new LightBlurOverlay());
|
||||
_overlayMan.AddOverlay(new SunShadowOverlay());
|
||||
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
|
||||
}
|
||||
|
||||
@@ -32,7 +31,6 @@ public sealed class PlanetLightSystem : EntitySystem
|
||||
_overlayMan.RemoveOverlay<RoofOverlay>();
|
||||
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
|
||||
_overlayMan.RemoveOverlay<LightBlurOverlay>();
|
||||
_overlayMan.RemoveOverlay<SunShadowOverlay>();
|
||||
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class SunShadowSystem : SharedSunShadowSystem
|
||||
{
|
||||
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var mapQuery = AllEntityQuery<SunShadowCycleComponent, SunShadowComponent>();
|
||||
while (mapQuery.MoveNext(out var uid, out var cycle, out var shadow))
|
||||
{
|
||||
if (!cycle.Running || cycle.Directions.Count == 0)
|
||||
continue;
|
||||
|
||||
var pausedTime = _metadata.GetPauseTime(uid);
|
||||
|
||||
var time = (float)(_timing.CurTime
|
||||
.Add(cycle.Offset)
|
||||
.Subtract(_ticker.RoundStartTimeSpan)
|
||||
.Subtract(pausedTime)
|
||||
.TotalSeconds % cycle.Duration.TotalSeconds);
|
||||
|
||||
var (direction, alpha) = GetShadow((uid, cycle), time);
|
||||
shadow.Direction = direction;
|
||||
shadow.Alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 Direction, float Alpha) GetShadow(Entity<SunShadowCycleComponent> entity, float time)
|
||||
{
|
||||
// So essentially the values are stored as the percentages of the total duration just so it adjusts the speed
|
||||
// dynamically and we don't have to manually handle it.
|
||||
// It will lerp from each value to the next one with angle and length handled separately
|
||||
var ratio = (float) (time / entity.Comp.Duration.TotalSeconds);
|
||||
|
||||
for (var i = entity.Comp.Directions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var dir = entity.Comp.Directions[i];
|
||||
|
||||
if (ratio > dir.Ratio)
|
||||
{
|
||||
var next = entity.Comp.Directions[(i + 1) % entity.Comp.Directions.Count];
|
||||
float nextRatio;
|
||||
|
||||
// Last entry
|
||||
if (i == entity.Comp.Directions.Count - 1)
|
||||
{
|
||||
nextRatio = next.Ratio + 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextRatio = next.Ratio;
|
||||
}
|
||||
|
||||
var range = nextRatio - dir.Ratio;
|
||||
var diff = (ratio - dir.Ratio) / range;
|
||||
DebugTools.Assert(diff is >= 0f and <= 1f);
|
||||
|
||||
// We lerp angle + length separately as we don't want a straight-line lerp and want the rotation to be consistent.
|
||||
var currentAngle = dir.Direction.ToAngle();
|
||||
var nextAngle = next.Direction.ToAngle();
|
||||
|
||||
var angle = Angle.Lerp(currentAngle, nextAngle, diff);
|
||||
// This is to avoid getting weird issues where the angle gets pretty close but length still noticeably catches up.
|
||||
var lengthDiff = MathF.Pow(diff, 1f / 2f);
|
||||
var length = float.Lerp(dir.Direction.Length(), next.Direction.Length(), lengthDiff);
|
||||
|
||||
var vector = angle.ToVec() * length;
|
||||
var alpha = float.Lerp(dir.Alpha, next.Alpha, diff);
|
||||
return (vector, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -12,29 +11,19 @@ public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||
{
|
||||
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var mapQuery = AllEntityQuery<LightCycleComponent, MapLightComponent>();
|
||||
while (mapQuery.MoveNext(out var uid, out var cycle, out var map))
|
||||
{
|
||||
if (!cycle.Running)
|
||||
continue;
|
||||
|
||||
// We still iterate paused entities as we still want to override the lighting color and not have
|
||||
// it apply the server state
|
||||
var pausedTime = _metadata.GetPauseTime(uid);
|
||||
|
||||
var time = (float) _timing.CurTime
|
||||
.Add(cycle.Offset)
|
||||
.Subtract(_ticker.RoundStartTimeSpan)
|
||||
.Subtract(pausedTime)
|
||||
.TotalSeconds;
|
||||
|
||||
var color = GetColor((uid, cycle), cycle.OriginalColor, time);
|
||||
|
||||
@@ -94,15 +94,13 @@ public sealed class RoofOverlay : Overlay
|
||||
// Due to stencilling we essentially draw on unrooved tiles
|
||||
while (tileEnumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var color = _roof.GetColor(roofEnt, tileRef.GridIndices);
|
||||
|
||||
if (color == null)
|
||||
if (!_roof.IsRooved(roofEnt, tileRef.GridIndices))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize);
|
||||
worldHandle.DrawRect(local, color.Value);
|
||||
worldHandle.DrawRect(local, roof.Color);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class SunShadowOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
private readonly HashSet<Entity<SunShadowCastComponent>> _shadows = new();
|
||||
|
||||
private IRenderTexture? _blurTarget;
|
||||
private IRenderTexture? _target;
|
||||
|
||||
public SunShadowOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
ZIndex = AfterLightTargetOverlay.ContentZIndex + 1;
|
||||
}
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.Viewport;
|
||||
var eye = viewport.Eye;
|
||||
|
||||
if (eye == null)
|
||||
return;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId,
|
||||
args.WorldBounds.Enlarged(SunShadowComponent.MaxLength),
|
||||
ref _grids);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var mapId = args.MapId;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var targetSize = viewport.LightRenderTarget.Size;
|
||||
|
||||
if (_target?.Size != targetSize)
|
||||
{
|
||||
_target = _clyde
|
||||
.CreateRenderTarget(targetSize,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: "sun-shadow-target");
|
||||
|
||||
if (_blurTarget?.Size != targetSize)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
.CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-blur");
|
||||
}
|
||||
}
|
||||
|
||||
var lightScale = viewport.LightRenderTarget.Size / (Vector2)viewport.Size;
|
||||
var scale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (!_entManager.TryGetComponent(grid.Owner, out SunShadowComponent? sun))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var direction = sun.Direction;
|
||||
var alpha = Math.Clamp(sun.Alpha, 0f, 1f);
|
||||
|
||||
// Nowhere to cast to so ignore it.
|
||||
if (direction.Equals(Vector2.Zero) || alpha == 0f)
|
||||
continue;
|
||||
|
||||
// Feature todo: dynamic shadows for mobs and trees. Also ideally remove the fake tree shadows.
|
||||
// TODO: Jittering still not quite perfect
|
||||
|
||||
var expandedBounds = worldBounds.Enlarged(direction.Length() + 0.01f);
|
||||
_shadows.Clear();
|
||||
|
||||
// Draw shadow polys to stencil
|
||||
args.WorldHandle.RenderInRenderTarget(_target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix =
|
||||
_target.GetWorldToLocalMatrix(eye, scale);
|
||||
var indices = new Vector2[PhysicsConstants.MaxPolygonVertices * 2];
|
||||
|
||||
// Go through shadows in range.
|
||||
|
||||
// For each one we:
|
||||
// - Get the original vertices.
|
||||
// - Extrapolate these along the sun direction.
|
||||
// - Combine the above into 1 single polygon to draw.
|
||||
|
||||
// Note that this is range-limited for accuracy; if you set it too high it will clip through walls or other undesirable entities.
|
||||
// This is probably not noticeable most of the time but if you want something "accurate" you'll want to code a solution.
|
||||
// Ideally the CPU would have its own shadow-map copy that we could just ray-cast each vert into though
|
||||
// You might need to batch verts or the likes as this could get expensive.
|
||||
_lookup.GetEntitiesIntersecting(mapId, expandedBounds, _shadows);
|
||||
|
||||
foreach (var ent in _shadows)
|
||||
{
|
||||
var xform = _entManager.GetComponent<TransformComponent>(ent.Owner);
|
||||
var (worldPos, worldRot) = _xformSys.GetWorldPositionRotation(xform);
|
||||
// Need no rotation on matrix as sun shadow direction doesn't care.
|
||||
var worldMatrix = Matrix3x2.CreateTranslation(worldPos);
|
||||
var renderMatrix = Matrix3x2.Multiply(worldMatrix, invMatrix);
|
||||
var pointCount = ent.Comp.Points.Length;
|
||||
|
||||
Array.Copy(ent.Comp.Points, indices, pointCount);
|
||||
|
||||
for (var i = 0; i < pointCount; i++)
|
||||
{
|
||||
// Update point based on entity rotation.
|
||||
indices[i] = worldRot.RotateVec(indices[i]);
|
||||
|
||||
// Add the offset point by the sun shadow direction.
|
||||
indices[pointCount + i] = indices[i] + direction;
|
||||
}
|
||||
|
||||
var points = PhysicsHull.ComputePoints(indices, pointCount * 2);
|
||||
worldHandle.SetTransform(renderMatrix);
|
||||
|
||||
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, points, Color.White);
|
||||
}
|
||||
},
|
||||
Color.Transparent);
|
||||
|
||||
// Slightly blur it just to avoid aliasing issues on the later viewport-wide blur.
|
||||
_clyde.BlurRenderTarget(viewport, _target, _blurTarget!, eye, 1f);
|
||||
|
||||
// Draw stencil (see roofoverlay).
|
||||
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||
() =>
|
||||
{
|
||||
var invMatrix =
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(eye, scale);
|
||||
worldHandle.SetTransform(invMatrix);
|
||||
|
||||
var maskShader = _protoManager.Index<ShaderPrototype>("Mix").Instance();
|
||||
worldHandle.UseShader(maskShader);
|
||||
|
||||
worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<Label Text="{Loc 'ui-options-accessability-header-visuals'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
||||
@@ -14,9 +12,6 @@
|
||||
<ui:OptionSlider Name="SpeechBubbleTextOpacitySlider" Title="{Loc 'ui-options-speech-bubble-text-opacity'}" />
|
||||
<ui:OptionSlider Name="SpeechBubbleSpeakerOpacitySlider" Title="{Loc 'ui-options-speech-bubble-speaker-opacity'}" />
|
||||
<ui:OptionSlider Name="SpeechBubbleBackgroundOpacitySlider" Title="{Loc 'ui-options-speech-bubble-background-opacity'}" />
|
||||
<Label Text="{Loc 'ui-options-accessability-header-content'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="CensorNudityCheckBox" Text="{Loc 'ui-options-censor-nudity'}" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
|
||||
@@ -21,8 +21,6 @@ public sealed partial class AccessibilityTab : Control
|
||||
Control.AddOptionPercentSlider(CCVars.SpeechBubbleSpeakerOpacity, SpeechBubbleSpeakerOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.SpeechBubbleBackgroundOpacity, SpeechBubbleBackgroundOpacitySlider);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AccessibilityClientCensorNudity, CensorNudityCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,51 +265,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(EngineKeyFunctions.HideUI);
|
||||
AddButton(ContentKeyFunctions.InspectEntity);
|
||||
|
||||
AddHeader("ui-options-header-text-cursor");
|
||||
AddButton(EngineKeyFunctions.TextCursorLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorUp);
|
||||
AddButton(EngineKeyFunctions.TextCursorDown);
|
||||
AddButton(EngineKeyFunctions.TextCursorWordLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorWordRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorBegin);
|
||||
AddButton(EngineKeyFunctions.TextCursorEnd);
|
||||
|
||||
AddHeader("ui-options-header-text-cursor-select");
|
||||
AddButton(EngineKeyFunctions.TextCursorSelect);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectUp);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectDown);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectWordLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectWordRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectBegin);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectEnd);
|
||||
|
||||
AddHeader("ui-options-header-text-edit");
|
||||
AddButton(EngineKeyFunctions.TextBackspace);
|
||||
AddButton(EngineKeyFunctions.TextDelete);
|
||||
AddButton(EngineKeyFunctions.TextWordBackspace);
|
||||
AddButton(EngineKeyFunctions.TextWordDelete);
|
||||
AddButton(EngineKeyFunctions.TextNewline);
|
||||
AddButton(EngineKeyFunctions.TextSubmit);
|
||||
AddButton(EngineKeyFunctions.MultilineTextSubmit);
|
||||
AddButton(EngineKeyFunctions.TextSelectAll);
|
||||
AddButton(EngineKeyFunctions.TextCopy);
|
||||
AddButton(EngineKeyFunctions.TextCut);
|
||||
AddButton(EngineKeyFunctions.TextPaste);
|
||||
|
||||
AddHeader("ui-options-header-text-chat");
|
||||
AddButton(EngineKeyFunctions.TextHistoryPrev);
|
||||
AddButton(EngineKeyFunctions.TextHistoryNext);
|
||||
AddButton(EngineKeyFunctions.TextReleaseFocus);
|
||||
AddButton(EngineKeyFunctions.TextScrollToBottom);
|
||||
|
||||
AddHeader("ui-options-header-text-other");
|
||||
AddButton(EngineKeyFunctions.TextTabComplete);
|
||||
AddButton(EngineKeyFunctions.TextCompleteNext);
|
||||
AddButton(EngineKeyFunctions.TextCompletePrev);
|
||||
|
||||
//CP14
|
||||
AddHeader("ui-options-header-cp14");
|
||||
AddButton(ContentKeyFunctions.CP14OpenKnowledgeMenu);
|
||||
|
||||
@@ -27,8 +27,6 @@ public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
return;
|
||||
|
||||
component.Powered = state.Powered;
|
||||
component.NeedsPower = state.NeedsPower;
|
||||
component.PowerDisabled = state.PowerDisabled;
|
||||
}
|
||||
|
||||
public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component)
|
||||
|
||||
@@ -64,15 +64,9 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
|
||||
args.Sprite.Visible = hasVisibleLayer || revealed;
|
||||
|
||||
if (ShowAll)
|
||||
// allows a t-ray to show wires/pipes above carpets/puddles
|
||||
if (scannerRevealed)
|
||||
{
|
||||
// Allows sandbox mode to make wires visible over other stuff.
|
||||
component.OriginalDrawDepth ??= args.Sprite.DrawDepth;
|
||||
args.Sprite.DrawDepth = (int)Shared.DrawDepth.DrawDepth.Overdoors;
|
||||
}
|
||||
else if (scannerRevealed)
|
||||
{
|
||||
// Allows a t-ray to show wires/pipes above carpets/puddles.
|
||||
if (component.OriginalDrawDepth is not null)
|
||||
return;
|
||||
component.OriginalDrawDepth = args.Sprite.DrawDepth;
|
||||
|
||||
@@ -467,9 +467,8 @@ public sealed class ChatUIController : UIController
|
||||
|
||||
if (existing.Count > SpeechBubbleCap)
|
||||
{
|
||||
// Get the next speech bubble to fade
|
||||
// Any speech bubbles before it are already fading
|
||||
var last = existing[^(SpeechBubbleCap + 1)];
|
||||
// Get the oldest to start fading fast.
|
||||
var last = existing[0];
|
||||
last.FadeNow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
_overlay.DeadLevel = 0f;
|
||||
_overlay.CritLevel = 0f;
|
||||
_overlay.PainLevel = 0f;
|
||||
_overlay.BruteLevel = 0f;
|
||||
_overlay.OxygenLevel = 0f;
|
||||
}
|
||||
|
||||
@@ -95,22 +95,13 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
case MobState.Alive:
|
||||
{
|
||||
FixedPoint2 painLevel = 0;
|
||||
_overlay.PainLevel = 0;
|
||||
|
||||
if (!EntityManager.HasComponent<PainNumbnessComponent>(entity))
|
||||
if (EntityManager.HasComponent<PainNumbnessComponent>(entity))
|
||||
{
|
||||
foreach (var painDamageType in damageable.PainDamageGroups)
|
||||
{
|
||||
damageable.DamagePerGroup.TryGetValue(painDamageType, out var painDamage);
|
||||
painLevel += painDamage;
|
||||
}
|
||||
_overlay.PainLevel = FixedPoint2.Min(1f, painLevel / critThreshold).Float();
|
||||
|
||||
if (_overlay.PainLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.PainLevel = 0;
|
||||
}
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
else if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
|
||||
{
|
||||
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
|
||||
}
|
||||
|
||||
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
|
||||
@@ -118,6 +109,11 @@ public sealed class DamageOverlayUiController : UIController
|
||||
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
|
||||
}
|
||||
|
||||
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
|
||||
_overlay.CritLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
@@ -129,13 +125,13 @@ public sealed class DamageOverlayUiController : UIController
|
||||
return;
|
||||
_overlay.CritLevel = critLevel.Value.Float();
|
||||
|
||||
_overlay.PainLevel = 0;
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
case MobState.Dead:
|
||||
{
|
||||
_overlay.PainLevel = 0;
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.CritLevel = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ public sealed class DamageOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Handles the red pulsing overlay
|
||||
/// </summary>
|
||||
public float PainLevel = 0f;
|
||||
public float BruteLevel = 0f;
|
||||
|
||||
private float _oldPainLevel = 0f;
|
||||
private float _oldBruteLevel = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the darkening overlay.
|
||||
@@ -92,14 +92,14 @@ public sealed class DamageOverlay : Overlay
|
||||
DeadLevel = 0f;
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseTo(_oldPainLevel, PainLevel, 0.001f))
|
||||
if (!MathHelper.CloseTo(_oldBruteLevel, BruteLevel, 0.001f))
|
||||
{
|
||||
var diff = PainLevel - _oldPainLevel;
|
||||
_oldPainLevel += GetDiff(diff, lastFrameTime);
|
||||
var diff = BruteLevel - _oldBruteLevel;
|
||||
_oldBruteLevel += GetDiff(diff, lastFrameTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_oldPainLevel = PainLevel;
|
||||
_oldBruteLevel = BruteLevel;
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseTo(_oldOxygenLevel, OxygenLevel, 0.001f))
|
||||
@@ -135,7 +135,7 @@ public sealed class DamageOverlay : Overlay
|
||||
|
||||
// Makes debugging easier don't @ me
|
||||
float level = 0f;
|
||||
level = _oldPainLevel;
|
||||
level = _oldBruteLevel;
|
||||
|
||||
// TODO: Lerping
|
||||
if (level > 0f && _oldCritLevel <= 0f)
|
||||
@@ -165,7 +165,7 @@ public sealed class DamageOverlay : Overlay
|
||||
}
|
||||
else
|
||||
{
|
||||
_oldPainLevel = PainLevel;
|
||||
_oldBruteLevel = BruteLevel;
|
||||
}
|
||||
|
||||
level = State != MobState.Critical ? _oldOxygenLevel : 1f;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.DisplacementMap;
|
||||
|
||||
namespace Content.Client._CP14.Displacement;
|
||||
|
||||
/// <summary>
|
||||
/// Simply apply displacements to sprite layers
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CP14SimpleDisplacementMapComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<string, DisplacementData> Displacements = new();
|
||||
|
||||
public readonly HashSet<string> RevealedLayers = new();
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using Content.Client.DisplacementMap;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client._CP14.Displacement;
|
||||
|
||||
public sealed class CP14SimpleDisplacementMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14SimpleDisplacementMapComponent, VisualsChangedEvent>(OnVisualChanged);
|
||||
}
|
||||
|
||||
private void OnVisualChanged(Entity<CP14SimpleDisplacementMapComponent> ent, ref VisualsChangedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
foreach (var key in ent.Comp.RevealedLayers)
|
||||
{
|
||||
sprite.RemoveLayer(key);
|
||||
}
|
||||
ent.Comp.RevealedLayers.Clear();
|
||||
|
||||
foreach (var (key, dData) in ent.Comp.Displacements)
|
||||
{
|
||||
if (!sprite.LayerMapTryGet(key, out var index))
|
||||
continue;
|
||||
|
||||
_displacement.TryAddDisplacement(dData, sprite, index, key, ent.Comp.RevealedLayers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
using Content.Shared._CP14.Knowledge;
|
||||
using Content.Shared._CP14.Knowledge.Events;
|
||||
using Content.Shared._CP14.Knowledge.Prototypes;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._CP14.Knowledge;
|
||||
|
||||
public sealed class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
|
||||
public sealed partial class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
@@ -25,7 +24,7 @@ public sealed class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
|
||||
if (entity is null)
|
||||
return;
|
||||
|
||||
RaiseNetworkEvent(new CP14RequestKnowledgeInfoEvent(GetNetEntity(entity.Value)));
|
||||
RaiseNetworkEvent(new RequestKnowledgeInfoEvent(GetNetEntity(entity.Value)));
|
||||
}
|
||||
|
||||
private void OnCharacterKnowledgeEvent(CP14KnowledgeInfoEvent msg, EntitySessionEventArgs args)
|
||||
@@ -38,6 +37,6 @@ public sealed class ClientCP14KnowledgeSystem : SharedCP14KnowledgeSystem
|
||||
|
||||
public readonly record struct KnowledgeData(
|
||||
EntityUid Entity,
|
||||
HashSet<ProtoId<CP14KnowledgePrototype>> AllKnowledge
|
||||
HashSet<ProtoId<CP14KnowledgePrototype>> AllKnowledges
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared._CP14.Configuration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -12,8 +12,8 @@ public sealed partial class CP14OptionsMenuMainTab : Control
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.WaveShaderEnabled, WaveShaderEnabled);
|
||||
Control.AddOptionCheckBox(CCVars.PostProcess, PostProcessCheckBox);
|
||||
Control.AddOptionCheckBox(CP14ConfigVars.WaveShaderEnabled, WaveShaderEnabled);
|
||||
Control.AddOptionCheckBox(CP14ConfigVars.PostProcess, PostProcessCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Shared._CP14.Configuration;
|
||||
using System.Numerics;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -32,7 +32,7 @@ public sealed class CP14BasePostProcessOverlay : Overlay
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_configManager.GetCVar(CCVars.PostProcess))
|
||||
if (!_configManager.GetCVar(CP14ConfigVars.PostProcess))
|
||||
return false;
|
||||
|
||||
if (!_entityManager.TryGetComponent(_playerManager.LocalSession?.AttachedEntity, out EyeComponent? eyeComp))
|
||||
@@ -74,4 +74,4 @@ public sealed class CP14BasePostProcessOverlay : Overlay
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,11 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<Button Name="ProductButton" Access="Public">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<EntityPrototypeView Name="EntityView"
|
||||
MinSize="48 48"
|
||||
MaxSize="48 48"
|
||||
Scale="2,2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Visible="False"/>
|
||||
<TextureRect Name="View"
|
||||
MinSize="48 48"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Stretch="KeepAspectCentered"
|
||||
Visible="False"/>
|
||||
Stretch="KeepAspectCentered" />
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="SpecialLabel" Text="{Loc 'cp14-store-ui-tab-special'}" VerticalAlignment="Center" Access="Public" Visible="False" />
|
||||
<RichTextLabel Name="ProductName" VerticalAlignment="Center" Access="Public" />
|
||||
|
||||
@@ -3,6 +3,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._CP14.TravelingStoreShip;
|
||||
|
||||
@@ -25,16 +26,10 @@ public sealed partial class CP14StoreProductControl : Control
|
||||
ProductName.Text = $"[bold]{entry.Name}[/bold]";
|
||||
|
||||
SpecialLabel.Visible = entry.Special;
|
||||
View.Texture = _sprite.Frame0(entry.Icon);
|
||||
}
|
||||
|
||||
if (entry.Icon is not null)
|
||||
{
|
||||
View.Visible = true;
|
||||
View.Texture = _sprite.Frame0(entry.Icon);
|
||||
}
|
||||
else if (entry.EntityView is not null)
|
||||
{
|
||||
EntityView.Visible = true;
|
||||
EntityView.SetPrototype(entry.EntityView);
|
||||
}
|
||||
private void UpdateView(SpriteSpecifier spriteSpecifier)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Name="Window"
|
||||
Title=""
|
||||
Title="{Loc 'cp14-store-ui-title'}"
|
||||
MinSize="800 600"
|
||||
SetSize="800 600">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
|
||||
@@ -11,6 +11,9 @@ public sealed partial class CP14StoreWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private TimeSpan? _nextTravelTime;
|
||||
private bool _onStation;
|
||||
|
||||
public CP14StoreWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -22,8 +25,24 @@ public sealed partial class CP14StoreWindow : DefaultWindow
|
||||
|
||||
public void UpdateUI(CP14StoreUiState state)
|
||||
{
|
||||
Window.Title = Loc.GetString("cp14-store-ui-title", ("name", state.ShopName));
|
||||
UpdateProducts(state);
|
||||
|
||||
_nextTravelTime = state.NextTravelTime;
|
||||
_onStation = state.OnStation;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
//Updating time
|
||||
if (_nextTravelTime is not null)
|
||||
{
|
||||
var time = _nextTravelTime.Value - _timing.CurTime;
|
||||
|
||||
TravelTimeLabel.Text =
|
||||
$"{Loc.GetString(_onStation ? "cp14-store-ui-next-travel-out" : "cp14-store-ui-next-travel-in")} {Math.Max(time.Minutes, 0):00}:{Math.Max(time.Seconds, 0):00}";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProducts(CP14StoreUiState state)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using Content.Shared._CP14.Vampire;
|
||||
using Content.Shared.Humanoid;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client._CP14.Vampire;
|
||||
|
||||
public sealed class CP14ClientVampireVisualsSystem : CP14SharedVampireVisualsSystem
|
||||
{
|
||||
protected override void OnVampireVisualsInit(Entity<CP14VampireVisualsComponent> vampire, ref ComponentInit args)
|
||||
{
|
||||
base.OnVampireVisualsInit(vampire, ref args);
|
||||
|
||||
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
if (sprite.LayerMapTryGet(vampire.Comp.FangsMap, out var fangsLayerIndex))
|
||||
sprite.LayerSetVisible(fangsLayerIndex, true);
|
||||
|
||||
}
|
||||
|
||||
protected override void OnVampireVisualsShutdown(Entity<CP14VampireVisualsComponent> vampire, ref ComponentShutdown args)
|
||||
{
|
||||
base.OnVampireVisualsShutdown(vampire, ref args);
|
||||
|
||||
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
if (sprite.LayerMapTryGet(vampire.Comp.FangsMap, out var fangsLayerIndex))
|
||||
sprite.LayerSetVisible(fangsLayerIndex, false);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared._CP14.Configuration;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -21,7 +23,7 @@ public sealed class CP14WaveShaderSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
_shader = _protoMan.Index<ShaderPrototype>("Wave").InstanceUnique();
|
||||
_enabled = _configuration.GetCVar(CCVars.WaveShaderEnabled);
|
||||
_enabled = _configuration.GetCVar(CP14ConfigVars.WaveShaderEnabled);
|
||||
|
||||
SubscribeLocalEvent<CP14WaveShaderComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<CP14WaveShaderComponent, ComponentShutdown>(OnShutdown);
|
||||
|
||||
@@ -59,32 +59,6 @@ namespace Content.IntegrationTests.Tests.Chemistry
|
||||
#pragma warning restore NUnit2045
|
||||
}
|
||||
|
||||
//------------CP14 - improve test for alchemy
|
||||
//Get all possible reactions with the current reagents
|
||||
var possibleReactions = prototypeManager.EnumeratePrototypes<ReactionPrototype>()
|
||||
.Where(x => x.Reactants.All(id => solution.Contents.Any(s => s.Reagent.Prototype == id.Key)))
|
||||
.ToList();
|
||||
|
||||
//Check if the reaction is the first to occur when heated
|
||||
foreach (var possibleReaction in possibleReactions.OrderBy(r => r.MinimumTemperature))
|
||||
{
|
||||
if (possibleReaction.MinimumTemperature < reactionPrototype.MinimumTemperature && possibleReaction.MixingCategories == reactionPrototype.MixingCategories)
|
||||
{
|
||||
Assert.Fail($"The {possibleReaction.ID} reaction may occur before {reactionPrototype.ID} when heated.");
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the reaction is the first to occur when freezing
|
||||
foreach (var possibleReaction in possibleReactions.OrderBy(r => r.MaximumTemperature))
|
||||
{
|
||||
if (possibleReaction.MaximumTemperature > reactionPrototype.MaximumTemperature && possibleReaction.MixingCategories == reactionPrototype.MixingCategories)
|
||||
{
|
||||
Assert.Fail($"The {possibleReaction.ID} reaction may occur before {reactionPrototype.ID} when freezing.");
|
||||
}
|
||||
}
|
||||
|
||||
//Now safe set the temperature and mix the reagents
|
||||
//----------CP14 - improve test for alchemy end
|
||||
solutionContainerSystem.SetTemperature(solutionEnt.Value, reactionPrototype.MinimumTemperature);
|
||||
|
||||
if (reactionPrototype.MixingCategories != null)
|
||||
|
||||
@@ -54,9 +54,6 @@ public sealed class CraftingTests : InteractionTest
|
||||
await CraftItem(Spear);
|
||||
await FindEntity(Spear);
|
||||
|
||||
// Reset target because entitylookup will dump this.
|
||||
Target = null;
|
||||
|
||||
// Player's hands should be full of the remaining rods, except those dropped during the failed crafting attempt.
|
||||
// Spear and left over stacks should be on the floor.
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
|
||||
|
||||
@@ -17,26 +17,23 @@ public sealed class ContrabandTest
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (!proto.TryGetComponent<ContrabandComponent>(out var contraband, componentFactory))
|
||||
continue;
|
||||
if (!proto.TryGetComponent<ContrabandComponent>(out var contraband, componentFactory))
|
||||
continue;
|
||||
|
||||
Assert.That(protoMan.TryIndex(contraband.Severity, out var severity, false),
|
||||
@$"{proto.ID} has a ContrabandComponent with a unknown severity.");
|
||||
Assert.That(protoMan.TryIndex(contraband.Severity, out var severity, false),
|
||||
@$"{proto.ID} has a ContrabandComponent with a unknown severity.");
|
||||
|
||||
if (!severity.ShowDepartmentsAndJobs)
|
||||
continue;
|
||||
if (!severity.ShowDepartmentsAndJobs)
|
||||
continue;
|
||||
|
||||
Assert.That(contraband.AllowedDepartments.Count + contraband.AllowedJobs.Count, Is.Not.EqualTo(0),
|
||||
@$"{proto.ID} has a ContrabandComponent with ShowDepartmentsAndJobs but no allowed departments or jobs.");
|
||||
}
|
||||
});
|
||||
Assert.That(contraband.AllowedDepartments.Count + contraband.AllowedJobs.Count, Is.Not.EqualTo(0),
|
||||
@$"{proto.ID} has a ContrabandComponent with ShowDepartmentsAndJobs but no allowed departments or jobs.");
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
@@ -62,10 +62,7 @@ public abstract partial class InteractionTest
|
||||
|
||||
// Please someone purge async construction code
|
||||
Task<bool> task = default!;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
task = SConstruction.TryStartItemConstruction(prototype, SEntMan.GetEntity(Player));
|
||||
});
|
||||
await Server.WaitPost(() => task = SConstruction.TryStartItemConstruction(prototype, SEntMan.GetEntity(Player)));
|
||||
|
||||
Task? tickTask = null;
|
||||
while (!task.IsCompleted)
|
||||
|
||||
@@ -23,49 +23,46 @@ public sealed class MagazineVisualsSpriteTest
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
foreach (var proto in protoMan.EnumeratePrototypes<EntityPrototype>())
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (!proto.TryGetComponent<MagazineVisualsComponent>(out var visuals, componentFactory))
|
||||
continue;
|
||||
|
||||
Assert.That(proto.TryGetComponent<SpriteComponent>(out var sprite, componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no SpriteComponent.");
|
||||
Assert.That(proto.HasComponent<AppearanceComponent>(componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no AppearanceComponent.");
|
||||
|
||||
var toTest = new List<(int, string)>();
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out var magLayerId))
|
||||
toTest.Add((magLayerId, ""));
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out var magUnshadedLayerId))
|
||||
toTest.Add((magUnshadedLayerId, "-unshaded"));
|
||||
|
||||
Assert.That(toTest, Is.Not.Empty,
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no Mag or MagUnshaded layer map.");
|
||||
|
||||
var start = visuals.ZeroVisible ? 0 : 1;
|
||||
foreach (var (id, midfix) in toTest)
|
||||
{
|
||||
if (proto.Abstract || pair.IsTestPrototype(proto))
|
||||
continue;
|
||||
|
||||
if (!proto.TryGetComponent<MagazineVisualsComponent>(out var visuals, componentFactory))
|
||||
continue;
|
||||
|
||||
Assert.That(proto.TryGetComponent<SpriteComponent>(out var sprite, componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no SpriteComponent.");
|
||||
Assert.That(proto.HasComponent<AppearanceComponent>(componentFactory),
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no AppearanceComponent.");
|
||||
|
||||
var toTest = new List<(int, string)>();
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out var magLayerId))
|
||||
toTest.Add((magLayerId, ""));
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out var magUnshadedLayerId))
|
||||
toTest.Add((magUnshadedLayerId, "-unshaded"));
|
||||
|
||||
Assert.That(toTest, Is.Not.Empty,
|
||||
@$"{proto.ID} has MagazineVisualsComponent but no Mag or MagUnshaded layer map.");
|
||||
|
||||
var start = visuals.ZeroVisible ? 0 : 1;
|
||||
foreach (var (id, midfix) in toTest)
|
||||
Assert.That(sprite.TryGetLayer(id, out var layer));
|
||||
var rsi = layer.ActualRsi;
|
||||
for (var i = start; i < visuals.MagSteps; i++)
|
||||
{
|
||||
Assert.That(sprite.TryGetLayer(id, out var layer));
|
||||
var rsi = layer.ActualRsi;
|
||||
for (var i = start; i < visuals.MagSteps; i++)
|
||||
{
|
||||
var state = $"{visuals.MagState}{midfix}-{i}";
|
||||
Assert.That(rsi.TryGetState(state, out _),
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but {rsi.Path} doesn't have state {state}!");
|
||||
}
|
||||
|
||||
// MagSteps includes the 0th step, so sometimes people are off by one.
|
||||
var extraState = $"{visuals.MagState}{midfix}-{visuals.MagSteps}";
|
||||
Assert.That(rsi.TryGetState(extraState, out _), Is.False,
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but more states exist!");
|
||||
var state = $"{visuals.MagState}{midfix}-{i}";
|
||||
Assert.That(rsi.TryGetState(state, out _),
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but {rsi.Path} doesn't have state {state}!");
|
||||
}
|
||||
|
||||
// MagSteps includes the 0th step, so sometimes people are off by one.
|
||||
var extraState = $"{visuals.MagState}{midfix}-{visuals.MagSteps}";
|
||||
Assert.That(rsi.TryGetState(extraState, out _), Is.False,
|
||||
@$"{proto.ID} has MagazineVisualsComponent with MagSteps = {visuals.MagSteps}, but more states exist!");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class ForceGhostCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
public override string Command => "forceghost";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0 || args.Length > 1)
|
||||
{
|
||||
shell.WriteError(LocalizationManager.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_playerManager.TryGetSessionByUsername(args[0], out var player))
|
||||
{
|
||||
shell.WriteError(LocalizationManager.GetString("shell-target-player-does-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_gameTicker.PlayerGameStatuses.TryGetValue(player.UserId, out var playerStatus) ||
|
||||
playerStatus is not PlayerGameStatus.JoinedGame)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-forceghost-error-lobby"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mind.TryGetMind(player, out var mindId, out var mind))
|
||||
(mindId, mind) = _mind.CreateMind(player.UserId);
|
||||
|
||||
if (!_ghost.OnGhostAttempt(mindId, false, true, true, mind))
|
||||
shell.WriteLine(Loc.GetString("cmd-forceghost-denied"));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.SessionNames(players: _playerManager),
|
||||
Loc.GetString("cmd-forceghost-hint"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
@@ -17,7 +15,6 @@ public sealed class RoleBanCommand : IConsoleCommand
|
||||
[Dependency] private readonly IPlayerLocator _locator = default!;
|
||||
[Dependency] private readonly IBanManager _bans = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public string Command => "roleban";
|
||||
public string Description => Loc.GetString("cmd-roleban-desc");
|
||||
@@ -79,12 +76,6 @@ public sealed class RoleBanCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_proto.HasIndex<JobPrototype>(job))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-roleban-job-parse",("job", job)));
|
||||
return;
|
||||
}
|
||||
|
||||
var located = await _locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
{
|
||||
|
||||
@@ -222,7 +222,6 @@ public sealed class AdminSystem : EntitySystem
|
||||
var entityName = string.Empty;
|
||||
var identityName = string.Empty;
|
||||
|
||||
// Visible (identity) name can be different from real name
|
||||
if (session?.AttachedEntity != null)
|
||||
{
|
||||
entityName = EntityManager.GetComponent<MetaDataComponent>(session.AttachedEntity.Value).EntityName;
|
||||
@@ -231,7 +230,6 @@ public sealed class AdminSystem : EntitySystem
|
||||
|
||||
var antag = false;
|
||||
|
||||
// Starting role, antagonist status and role type
|
||||
RoleTypePrototype roleType = new();
|
||||
var startingRole = string.Empty;
|
||||
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
|
||||
@@ -245,13 +243,8 @@ public sealed class AdminSystem : EntitySystem
|
||||
startingRole = _jobs.MindTryGetJobName(mindId);
|
||||
}
|
||||
|
||||
// Connection status and playtime
|
||||
var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame;
|
||||
|
||||
// Start with the last available playtime data
|
||||
var cachedInfo = GetCachedPlayerInfo(data.UserId);
|
||||
var overallPlaytime = cachedInfo?.OverallPlaytime;
|
||||
// Overwrite with current playtime data, unless it's null (such as if the player just disconnected)
|
||||
TimeSpan? overallPlaytime = null;
|
||||
if (session != null &&
|
||||
_playTime.TryGetTrackerTimes(session, out var playTimes) &&
|
||||
playTimes.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out var playTime))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server._CP14.GameTicking.Rules.Components;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
@@ -37,11 +36,6 @@ public sealed partial class AdminVerbSystem
|
||||
[ValidatePrototypeId<StartingGearPrototype>]
|
||||
private const string PirateGearId = "PirateGear";
|
||||
|
||||
//CP14
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string CP14VampireRule = "CP14Vampire";
|
||||
//CP14 end
|
||||
|
||||
// All antag verbs have names so invokeverb works.
|
||||
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
|
||||
{
|
||||
@@ -58,21 +52,6 @@ public sealed partial class AdminVerbSystem
|
||||
|
||||
var targetPlayer = targetActor.PlayerSession;
|
||||
|
||||
Verb vampire = new()
|
||||
{
|
||||
Text = Loc.GetString("cp14-admin-verb-text-make-vampire"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Actions/Spells/vampire.rsi"),
|
||||
"bite"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<CP14VampireRuleComponent>(targetPlayer, CP14VampireRule);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("cp14-admin-verb-make-vampire"),
|
||||
};
|
||||
args.Verbs.Add(vampire);
|
||||
|
||||
/* CP14 disable default antags
|
||||
var traitorName = Loc.GetString("admin-verb-text-make-traitor");
|
||||
Verb traitor = new()
|
||||
|
||||
@@ -3,9 +3,7 @@ using System.Linq;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Preferences;
|
||||
using JetBrains.Annotations;
|
||||
@@ -27,7 +25,7 @@ public sealed partial class AntagSelectionSystem
|
||||
definition = null;
|
||||
|
||||
var totalTargetCount = GetTargetAntagCount(ent, players);
|
||||
var mindCount = ent.Comp.AssignedMinds.Count;
|
||||
var mindCount = ent.Comp.SelectedMinds.Count;
|
||||
if (mindCount >= totalTargetCount)
|
||||
return false;
|
||||
|
||||
@@ -97,7 +95,7 @@ public sealed partial class AntagSelectionSystem
|
||||
var countOffset = 0;
|
||||
foreach (var otherDef in ent.Comp.Definitions)
|
||||
{
|
||||
countOffset += Math.Clamp((poolSize - countOffset) / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio; // Note: Is the PlayerRatio necessary here? Seems like it can cause issues for defs with varied PlayerRatio.
|
||||
countOffset += Math.Clamp((poolSize - countOffset) / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio;
|
||||
}
|
||||
// make sure we don't double-count the current selection
|
||||
countOffset -= Math.Clamp(poolSize / def.PlayerRatio, def.Min, def.Max) * def.PlayerRatio;
|
||||
@@ -117,7 +115,7 @@ public sealed partial class AntagSelectionSystem
|
||||
return new List<(EntityUid, SessionData, string)>();
|
||||
|
||||
var output = new List<(EntityUid, SessionData, string)>();
|
||||
foreach (var (mind, name) in ent.Comp.AssignedMinds)
|
||||
foreach (var (mind, name) in ent.Comp.SelectedMinds)
|
||||
{
|
||||
if (!TryComp<MindComponent>(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null)
|
||||
continue;
|
||||
@@ -139,7 +137,7 @@ public sealed partial class AntagSelectionSystem
|
||||
return new();
|
||||
|
||||
var output = new List<Entity<MindComponent>>();
|
||||
foreach (var (mind, _) in ent.Comp.AssignedMinds)
|
||||
foreach (var (mind, _) in ent.Comp.SelectedMinds)
|
||||
{
|
||||
if (!TryComp<MindComponent>(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null)
|
||||
continue;
|
||||
@@ -157,7 +155,7 @@ public sealed partial class AntagSelectionSystem
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return new();
|
||||
|
||||
return ent.Comp.AssignedMinds.Select(p => p.Item1).ToList();
|
||||
return ent.Comp.SelectedMinds.Select(p => p.Item1).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,7 +247,7 @@ public sealed partial class AntagSelectionSystem
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
return GetAliveAntagCount(ent) == ent.Comp.AssignedMinds.Count;
|
||||
return GetAliveAntagCount(ent) == ent.Comp.SelectedMinds.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -354,66 +352,8 @@ public sealed partial class AntagSelectionSystem
|
||||
var ruleEnt = GameTicker.AddGameRule(id);
|
||||
RemComp<LoadMapRuleComponent>(ruleEnt);
|
||||
var antag = Comp<AntagSelectionComponent>(ruleEnt);
|
||||
antag.AssignmentComplete = true; // don't do normal selection.
|
||||
antag.SelectionsComplete = true; // don't do normal selection.
|
||||
GameTicker.StartGameRule(ruleEnt);
|
||||
return (ruleEnt, antag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all sessions that have been preselected for antag.
|
||||
/// </summary>
|
||||
/// <param name="except">A specific definition to be excluded from the check.</param>
|
||||
public HashSet<ICommonSession> GetPreSelectedAntagSessions(AntagSelectionDefinition? except = null)
|
||||
{
|
||||
var result = new HashSet<ICommonSession>();
|
||||
var query = QueryAllRules();
|
||||
while (query.MoveNext(out var uid, out var comp, out _))
|
||||
{
|
||||
if (HasComp<EndedGameRuleComponent>(uid))
|
||||
continue;
|
||||
|
||||
foreach (var def in comp.Definitions)
|
||||
{
|
||||
if (def.Equals(except))
|
||||
continue;
|
||||
|
||||
if (comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
result.UnionWith(set);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all sessions that have been preselected for antag and are exclusive, i.e. should not be paired with other antags.
|
||||
/// </summary>
|
||||
/// <param name="except">A specific definition to be excluded from the check.</param>
|
||||
// Note: This is a bit iffy since technically this exclusive definition is defined via the MultiAntagSetting, while there's a separately tracked antagExclusive variable in the mindrole.
|
||||
// We can't query that however since there's no guarantee the mindrole has been given out yet when checking pre-selected antags.
|
||||
// I don't think there's any instance where they differ, but it's something to be aware of for a potential future refactor.
|
||||
public HashSet<ICommonSession> GetPreSelectedExclusiveAntagSessions(AntagSelectionDefinition? except = null)
|
||||
{
|
||||
var result = new HashSet<ICommonSession>();
|
||||
var query = QueryAllRules();
|
||||
while (query.MoveNext(out var uid, out var comp, out _))
|
||||
{
|
||||
if (HasComp<EndedGameRuleComponent>(uid))
|
||||
continue;
|
||||
|
||||
foreach (var def in comp.Definitions)
|
||||
{
|
||||
if (def.Equals(except))
|
||||
continue;
|
||||
|
||||
if (def.MultiAntagSetting == AntagAcceptability.None && comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
{
|
||||
result.UnionWith(set);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,8 @@ using Content.Server.Preferences.Managers;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Roles.Jobs;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Events;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Ghost;
|
||||
@@ -49,7 +46,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
[Dependency] private readonly RoleSystem _role = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
// arbitrary random number to give late joining some mild interest.
|
||||
public const float LateJoinRandomChance = 0.5f;
|
||||
@@ -93,33 +89,19 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var comp, out _))
|
||||
{
|
||||
if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn && comp.SelectionTime != AntagSelectionTime.IntraPlayerSpawn)
|
||||
if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn)
|
||||
continue;
|
||||
|
||||
if (comp.AssignmentComplete)
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp), pool); // We choose the antags here...
|
||||
|
||||
if (comp.SelectionTime == AntagSelectionTime.PrePlayerSpawn)
|
||||
{
|
||||
AssignPreSelectedSessions((uid, comp)); // ...But only assign them if PrePlayerSpawn
|
||||
foreach (var session in comp.AssignedSessions)
|
||||
{
|
||||
args.PlayerPool.Remove(session);
|
||||
GameTicker.PlayerJoinGame(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If IntraPlayerSpawn is selected, delayed rules should choose at this point too.
|
||||
var queryDelayed = QueryDelayedRules();
|
||||
while (queryDelayed.MoveNext(out var uid, out _, out var comp, out _))
|
||||
{
|
||||
if (comp.SelectionTime != AntagSelectionTime.IntraPlayerSpawn)
|
||||
if (comp.SelectionsComplete)
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp), pool);
|
||||
|
||||
foreach (var session in comp.SelectedSessions)
|
||||
{
|
||||
args.PlayerPool.Remove(session);
|
||||
GameTicker.PlayerJoinGame(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,11 +110,10 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var comp, out _))
|
||||
{
|
||||
if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn && comp.SelectionTime != AntagSelectionTime.IntraPlayerSpawn)
|
||||
if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn)
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp), args.Players);
|
||||
AssignPreSelectedSessions((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,13 +126,11 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
// eventually this should probably store the players per definition with some kind of unique identifier.
|
||||
// something to figure out later.
|
||||
|
||||
var query = QueryAllRules();
|
||||
var query = QueryActiveRules();
|
||||
var rules = new List<(EntityUid, AntagSelectionComponent)>();
|
||||
while (query.MoveNext(out var uid, out var antag, out _))
|
||||
while (query.MoveNext(out var uid, out _, out var antag, out _))
|
||||
{
|
||||
if (HasComp<ActiveGameRuleComponent>(uid) ||
|
||||
(HasComp<DelayedStartRuleComponent>(uid) && antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn)) //IntraPlayerSpawn selects antags before spawning, but doesn't activate until after.
|
||||
rules.Add((uid, antag));
|
||||
rules.Add((uid, antag));
|
||||
}
|
||||
RobustRandom.Shuffle(rules);
|
||||
|
||||
@@ -163,7 +142,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!antag.Definitions.Any(p => p.LateJoinAdditional))
|
||||
continue;
|
||||
|
||||
DebugTools.AssertNotEqual(antag.SelectionTime, AntagSelectionTime.PrePlayerSpawn);
|
||||
DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn);
|
||||
|
||||
// do not count players in the lobby for the antag ratio
|
||||
var players = _playerManager.NetworkedSessions.Count(x => x.AttachedEntity != null);
|
||||
@@ -171,9 +150,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def, players))
|
||||
continue;
|
||||
|
||||
var onlyPreSelect = (antag.SelectionTime == AntagSelectionTime.IntraPlayerSpawn && !antag.AssignmentComplete); // Don't wanna give them antag status if the rule hasn't assigned its existing ones yet
|
||||
|
||||
if (TryMakeAntag((uid, antag), args.Player, def.Value, onlyPreSelect: onlyPreSelect))
|
||||
if (TryMakeAntag((uid, antag), args.Player, def.Value))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -206,20 +183,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
||||
if (component.AssignmentComplete)
|
||||
if (component.SelectionsComplete)
|
||||
return;
|
||||
|
||||
if (!component.PreSelectionsComplete)
|
||||
{
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) &&
|
||||
status == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses.TryGetValue(x.UserId, out var status) && status == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
ChooseAntags((uid, component), players, midround: true);
|
||||
}
|
||||
|
||||
AssignPreSelectedSessions((uid, component));
|
||||
ChooseAntags((uid, component), players, midround: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,7 +201,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
|
||||
{
|
||||
if (ent.Comp.PreSelectionsComplete)
|
||||
if (ent.Comp.SelectionsComplete)
|
||||
return;
|
||||
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
@@ -238,7 +209,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
ChooseAntags(ent, pool, def, midround: midround);
|
||||
}
|
||||
|
||||
ent.Comp.PreSelectionsComplete = true;
|
||||
ent.Comp.SelectionsComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -279,53 +250,21 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
break;
|
||||
}
|
||||
|
||||
if (session != null && ent.Comp.PreSelectedSessions.Values.Any(x => x.Contains(session)))
|
||||
if (session != null && ent.Comp.SelectedSessions.Contains(session))
|
||||
{
|
||||
Log.Warning($"Somehow picked {session} for an antag when this rule already selected them previously");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (session == null)
|
||||
MakeAntag(ent, null, def); // This is for spawner antags
|
||||
else
|
||||
{
|
||||
if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
|
||||
set.Add(session); // Selection done!
|
||||
Log.Debug($"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}");
|
||||
_adminLogger.Add(LogType.AntagSelection, $"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}");
|
||||
}
|
||||
MakeAntag(ent, session, def);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns antag roles to sessions selected for it.
|
||||
/// </summary>
|
||||
public void AssignPreSelectedSessions(Entity<AntagSelectionComponent> ent)
|
||||
{
|
||||
// Only assign if there's been a pre-selection, and the selection hasn't already been made
|
||||
if (!ent.Comp.PreSelectionsComplete || ent.Comp.AssignmentComplete)
|
||||
return;
|
||||
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
continue;
|
||||
|
||||
foreach (var session in set)
|
||||
{
|
||||
TryMakeAntag(ent, session, def);
|
||||
}
|
||||
}
|
||||
|
||||
ent.Comp.AssignmentComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to makes a given player into the specified antagonist.
|
||||
/// </summary>
|
||||
public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true, bool onlyPreSelect = false)
|
||||
public bool TryMakeAntag(Entity<AntagSelectionComponent> ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true)
|
||||
{
|
||||
if (checkPref && !HasPrimaryAntagPreference(session, def))
|
||||
return false;
|
||||
@@ -333,19 +272,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def))
|
||||
return false;
|
||||
|
||||
if (onlyPreSelect && session != null)
|
||||
{
|
||||
if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
|
||||
set.Add(session);
|
||||
Log.Debug($"Pre-selected {session!.Name} as antagonist: {ToPrettyString(ent)}");
|
||||
_adminLogger.Add(LogType.AntagSelection, $"Pre-selected {session.Name} as antagonist: {ToPrettyString(ent)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
MakeAntag(ent, session, def, ignoreSpawner);
|
||||
}
|
||||
|
||||
MakeAntag(ent, session, def, ignoreSpawner);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -359,10 +286,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
if (!ent.Comp.PreSelectedSessions.TryGetValue(def, out var set))
|
||||
ent.Comp.PreSelectedSessions.Add(def, set = new HashSet<ICommonSession>());
|
||||
set.Add(session);
|
||||
ent.Comp.AssignedSessions.Add(session);
|
||||
ent.Comp.SelectedSessions.Add(session);
|
||||
|
||||
// we shouldn't be blocking the entity if they're just a ghost or smth.
|
||||
if (!HasComp<GhostComponent>(session.AttachedEntity))
|
||||
@@ -385,11 +309,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
Log.Error($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player.");
|
||||
if (session != null)
|
||||
{
|
||||
ent.Comp.AssignedSessions.Remove(session);
|
||||
ent.Comp.PreSelectedSessions[def].Remove(session);
|
||||
}
|
||||
|
||||
ent.Comp.SelectedSessions.Remove(session);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -410,11 +330,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
Log.Error($"Antag spawner {player} does not have a GhostRoleAntagSpawnerComponent.");
|
||||
if (session != null)
|
||||
{
|
||||
ent.Comp.AssignedSessions.Remove(session);
|
||||
ent.Comp.PreSelectedSessions[def].Remove(session);
|
||||
}
|
||||
|
||||
ent.Comp.SelectedSessions.Remove(session);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -447,11 +363,10 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
|
||||
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
|
||||
ent.Comp.AssignedMinds.Add((curMind.Value, Name(player)));
|
||||
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
|
||||
SendBriefing(session, def.Briefing);
|
||||
|
||||
Log.Debug($"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
|
||||
_adminLogger.Add(LogType.AntagSelection, $"Assigned {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
|
||||
Log.Debug($"Selected {ToPrettyString(curMind)} as antagonist: {ToPrettyString(ent)}");
|
||||
}
|
||||
|
||||
var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def);
|
||||
@@ -497,11 +412,15 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
|
||||
return false;
|
||||
|
||||
if (ent.Comp.AssignedSessions.Contains(session))
|
||||
if (ent.Comp.SelectedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
mind ??= session.GetMind();
|
||||
|
||||
// If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity.
|
||||
if (mind == null)
|
||||
return true;
|
||||
|
||||
//todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds)
|
||||
|
||||
switch (def.MultiAntagSetting)
|
||||
@@ -510,16 +429,12 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
if (_role.MindIsAntagonist(mind))
|
||||
return false;
|
||||
if (GetPreSelectedAntagSessions(def).Contains(session)) // Used for rules where the antag has been selected, but not started yet
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
case AntagAcceptability.NotExclusive:
|
||||
{
|
||||
if (_role.MindIsExclusiveAntagonist(mind))
|
||||
return false;
|
||||
if (GetPreSelectedExclusiveAntagSessions(def).Contains(session))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -566,7 +481,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (ent.Comp.AgentName is not { } name)
|
||||
return;
|
||||
|
||||
args.Minds = ent.Comp.AssignedMinds;
|
||||
args.Minds = ent.Comp.SelectedMinds;
|
||||
args.AgentName = Loc.GetString(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,10 @@ namespace Content.Server.Antag.Components;
|
||||
public sealed partial class AntagSelectionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Has the primary assignment of antagonists finished yet?
|
||||
/// Has the primary selection of antagonists finished yet?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AssignmentComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Has the antagonists been preselected but yet to be fully assigned?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PreSelectionsComplete;
|
||||
public bool SelectionsComplete;
|
||||
|
||||
/// <summary>
|
||||
/// The definitions for the antagonists
|
||||
@@ -32,10 +26,10 @@ public sealed partial class AntagSelectionComponent : Component
|
||||
public List<AntagSelectionDefinition> Definitions = new();
|
||||
|
||||
/// <summary>
|
||||
/// The minds and original names of the players assigned to be antagonists.
|
||||
/// The minds and original names of the players selected to be antagonists.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<(EntityUid, string)> AssignedMinds = new();
|
||||
public List<(EntityUid, string)> SelectedMinds = new();
|
||||
|
||||
/// <summary>
|
||||
/// When the antag selection will occur.
|
||||
@@ -43,17 +37,11 @@ public sealed partial class AntagSelectionComponent : Component
|
||||
[DataField]
|
||||
public AntagSelectionTime SelectionTime = AntagSelectionTime.PostPlayerSpawn;
|
||||
|
||||
/// <summary>
|
||||
/// Cached sessions of antag definitions and selected players. Players in this dict are not guaranteed to have been assigned the role yet.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<AntagSelectionDefinition, HashSet<ICommonSession>>PreSelectedSessions = new();
|
||||
|
||||
/// <summary>
|
||||
/// Cached sessions of players who are chosen. Used so we don't have to rebuild the pool multiple times in a tick.
|
||||
/// Is not serialized.
|
||||
/// </summary>
|
||||
public HashSet<ICommonSession> AssignedSessions = new();
|
||||
public HashSet<ICommonSession> SelectedSessions = new();
|
||||
|
||||
/// <summary>
|
||||
/// Locale id for the name of the antag.
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Content.Server.Cargo.Systems
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
UpdateBankAccount((stationUid.Value, bank), (int) price);
|
||||
UpdateBankAccount(stationUid.Value, bank, (int) price);
|
||||
QueueDel(args.Used);
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace Content.Server.Cargo.Systems
|
||||
while (stationQuery.MoveNext(out var uid, out var bank))
|
||||
{
|
||||
var balanceToAdd = bank.IncreasePerSecond * Delay;
|
||||
UpdateBankAccount((uid, bank), balanceToAdd);
|
||||
UpdateBankAccount(uid, bank, balanceToAdd);
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CargoOrderConsoleComponent>();
|
||||
@@ -229,7 +229,7 @@ namespace Content.Server.Cargo.Systems
|
||||
$"{ToPrettyString(player):user} approved order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}] with balance at {bank.Balance}");
|
||||
|
||||
orderDatabase.Orders.Remove(order);
|
||||
UpdateBankAccount((station.Value, bank), -cost);
|
||||
UpdateBankAccount(station.Value, bank, -cost);
|
||||
UpdateOrders(station.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,23 +76,19 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void UpdateBankAccount(Entity<StationBankAccountComponent?> ent, int balanceAdded)
|
||||
public void UpdateBankAccount(EntityUid uid, StationBankAccountComponent component, int balanceAdded)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Balance += balanceAdded;
|
||||
|
||||
var ev = new BankBalanceUpdatedEvent(ent, ent.Comp.Balance);
|
||||
|
||||
component.Balance += balanceAdded;
|
||||
var query = EntityQueryEnumerator<BankClientComponent, TransformComponent>();
|
||||
|
||||
var ev = new BankBalanceUpdatedEvent(uid, component.Balance);
|
||||
while (query.MoveNext(out var client, out var comp, out var xform))
|
||||
{
|
||||
var station = _station.GetOwningStation(client, xform);
|
||||
if (station != ent)
|
||||
if (station != uid)
|
||||
continue;
|
||||
|
||||
comp.Balance = ent.Comp.Balance;
|
||||
comp.Balance = component.Balance;
|
||||
Dirty(client, comp);
|
||||
RaiseLocalEvent(client, ref ev);
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Delivery;
|
||||
|
||||
/// <summary>
|
||||
/// Component given to a station to indicate it can have deliveries spawn on it.
|
||||
/// </summary>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class CargoDeliveryDataComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the next delivery will spawn.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan NextDelivery;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum cooldown after a delivery spawns.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan MinDeliveryCooldown = TimeSpan.FromMinutes(3);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum cooldown after a delivery spawns.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan MaxDeliveryCooldown = TimeSpan.FromMinutes(7);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The ratio at which deliveries will spawn, based on the amount of people in the crew manifest.
|
||||
/// 1 delivery per X players.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PlayerToDeliveryRatio = 7f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum amount of deliveries that will spawn.
|
||||
/// This is not per spawner unless DistributeRandomly is false.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MinimumDeliverySpawn = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Should deliveries be randomly split between spawners?
|
||||
/// If true, the amount of deliveries will be spawned randomly across all spawners.
|
||||
/// If false, an amount of mail based on PlayerToDeliveryRatio will be spawned on all spawners.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool DistributeRandomly = true;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.EntityTable;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Delivery;
|
||||
|
||||
/// <summary>
|
||||
/// System for managing deliveries spawned by the mail teleporter.
|
||||
/// This covers for spawning deliveries.
|
||||
/// </summary>
|
||||
public sealed partial class DeliverySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EntityTableSystem _entityTable = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
|
||||
private void InitializeSpawning()
|
||||
{
|
||||
SubscribeLocalEvent<CargoDeliveryDataComponent, MapInitEvent>(OnDataMapInit);
|
||||
}
|
||||
|
||||
private void OnDataMapInit(Entity<CargoDeliveryDataComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
ent.Comp.NextDelivery = _timing.CurTime + ent.Comp.MinDeliveryCooldown; // We want an early wave of mail so cargo doesn't have to wait
|
||||
}
|
||||
|
||||
private void SpawnDelivery(Entity<DeliverySpawnerComponent?> ent, int amount)
|
||||
{
|
||||
if (!Resolve(ent.Owner, ref ent.Comp))
|
||||
return;
|
||||
|
||||
var coords = Transform(ent).Coordinates;
|
||||
|
||||
_audio.PlayPvs(ent.Comp.SpawnSound, ent.Owner);
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
var spawns = _entityTable.GetSpawns(ent.Comp.Table);
|
||||
|
||||
foreach (var id in spawns)
|
||||
{
|
||||
Spawn(id, coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnStationDeliveries(Entity<CargoDeliveryDataComponent> ent)
|
||||
{
|
||||
if (!TryComp<StationRecordsComponent>(ent, out var records))
|
||||
return;
|
||||
|
||||
var spawners = GetValidSpawners(ent);
|
||||
|
||||
// Skip if theres no spawners available
|
||||
if (spawners.Count == 0)
|
||||
return;
|
||||
|
||||
// Skip if there's nobody in crew manifest
|
||||
if (records.Records.Keys.Count == 0)
|
||||
return;
|
||||
|
||||
// We take the amount of mail calculated based on player amount or the minimum, whichever is higher.
|
||||
// We don't want stations with less than the player ratio to not get mail at all
|
||||
var initialDeliveryCount = (int)Math.Ceiling(records.Records.Keys.Count / ent.Comp.PlayerToDeliveryRatio);
|
||||
var deliveryCount = Math.Max(initialDeliveryCount, ent.Comp.MinimumDeliverySpawn);
|
||||
|
||||
if (!ent.Comp.DistributeRandomly)
|
||||
{
|
||||
foreach (var spawner in spawners)
|
||||
{
|
||||
SpawnDelivery(spawner, deliveryCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] amounts = new int[spawners.Count];
|
||||
|
||||
// Distribute items randomly
|
||||
for (int i = 0; i < deliveryCount; i++)
|
||||
{
|
||||
var randomListIndex = _random.Next(spawners.Count);
|
||||
amounts[randomListIndex]++;
|
||||
}
|
||||
for (int j = 0; j < spawners.Count; j++)
|
||||
{
|
||||
SpawnDelivery(spawners[j], amounts[j]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<EntityUid> GetValidSpawners(Entity<CargoDeliveryDataComponent> ent)
|
||||
{
|
||||
var validSpawners = new List<EntityUid>();
|
||||
|
||||
var spawners = EntityQueryEnumerator<DeliverySpawnerComponent>();
|
||||
while (spawners.MoveNext(out var spawnerUid, out _))
|
||||
{
|
||||
var spawnerStation = _station.GetOwningStation(spawnerUid);
|
||||
|
||||
if (spawnerStation != ent.Owner)
|
||||
continue;
|
||||
|
||||
if (!_power.IsPowered(spawnerUid))
|
||||
continue;
|
||||
|
||||
validSpawners.Add(spawnerUid);
|
||||
}
|
||||
|
||||
return validSpawners;
|
||||
}
|
||||
|
||||
private void UpdateSpawner(float frameTime)
|
||||
{
|
||||
var dataQuery = EntityQueryEnumerator<CargoDeliveryDataComponent>();
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
while (dataQuery.MoveNext(out var uid, out var deliveryData))
|
||||
{
|
||||
if (deliveryData.NextDelivery > curTime)
|
||||
continue;
|
||||
|
||||
deliveryData.NextDelivery += _random.Next(deliveryData.MinDeliveryCooldown, deliveryData.MaxDeliveryCooldown); // Random cooldown between min and max
|
||||
SpawnStationDeliveries((uid, deliveryData));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.FingerprintReader;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Delivery;
|
||||
|
||||
/// <summary>
|
||||
/// System for managing deliveries spawned by the mail teleporter.
|
||||
/// This covers for mail spawning, as well as granting cargo money.
|
||||
/// </summary>
|
||||
public sealed partial class DeliverySystem : SharedDeliverySystem
|
||||
{
|
||||
[Dependency] private readonly CargoSystem _cargo = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _records = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly FingerprintReaderSystem _fingerprintReader = default!;
|
||||
[Dependency] private readonly SharedLabelSystem _label = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeliveryComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
InitializeSpawning();
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<DeliveryComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_container.EnsureContainer<Container>(ent, ent.Comp.Container);
|
||||
|
||||
var stationId = _station.GetStationInMap(Transform(ent).MapID);
|
||||
|
||||
if (stationId == null)
|
||||
return;
|
||||
|
||||
_records.TryGetRandomRecord<GeneralStationRecord>(stationId.Value, out var entry);
|
||||
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
ent.Comp.RecipientName = entry.Name;
|
||||
ent.Comp.RecipientJobTitle = entry.JobTitle;
|
||||
ent.Comp.RecipientStation = stationId.Value;
|
||||
|
||||
_appearance.SetData(ent, DeliveryVisuals.JobIcon, entry.JobIcon);
|
||||
|
||||
_label.Label(ent, ent.Comp.RecipientName);
|
||||
|
||||
if (TryComp<FingerprintReaderComponent>(ent, out var reader) && entry.Fingerprint != null)
|
||||
{
|
||||
_fingerprintReader.AddAllowedFingerprint((ent.Owner, reader), entry.Fingerprint);
|
||||
}
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
protected override void GrantSpesoReward(Entity<DeliveryComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
if (!TryComp<StationBankAccountComponent>(ent.Comp.RecipientStation, out var account))
|
||||
return;
|
||||
|
||||
_cargo.UpdateBankAccount((ent.Comp.RecipientStation.Value, account), ent.Comp.SpesoReward);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
UpdateSpawner(frameTime);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ namespace Content.Server.Entry
|
||||
public static class IgnoredComponents
|
||||
{
|
||||
public static string[] List => new[] {
|
||||
"CP14WaveShader", // CP14
|
||||
"CP14SimpleDisplacementMap", // CP14
|
||||
"CP14WaveShader", // CP14 Wave shader
|
||||
"CP14WorldSprite", // CP14 World Sprite
|
||||
"ConstructionGhost",
|
||||
"IconSmooth",
|
||||
"InteractionOutline",
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a protoype when triggered.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(TriggerSystem))]
|
||||
public sealed partial class SpawnOnTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The prototype to spawn.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntProtoId Proto = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Use MapCoordinates for spawning?
|
||||
/// Set to true if you don't want the new entity parented to the spawner.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool mapCoords;
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string Proto = string.Empty;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers when colliding with another entity.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerOnCollideComponent : Component
|
||||
namespace Content.Server.Explosion.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// The fixture with which to collide.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public string FixtureID = string.Empty;
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerOnCollideComponent : Component
|
||||
{
|
||||
[DataField("fixtureID", required: true)]
|
||||
public string FixtureID = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Doesn't trigger if the other colliding fixture is nonhard.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IgnoreOtherNonHard = true;
|
||||
/// <summary>
|
||||
/// Doesn't trigger if the other colliding fixture is nonhard.
|
||||
/// </summary>
|
||||
[DataField("ignoreOtherNonHard")]
|
||||
public bool IgnoreOtherNonHard = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers on use in hand.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerOnUseComponent : Component { }
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user of a Trigger satisfies a whitelist and blacklist condition.
|
||||
/// Cancels the trigger otherwise.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TriggerWhitelistComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whitelist for what entites can cause this trigger.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Blacklist for what entites can cause this trigger.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
}
|
||||
@@ -14,7 +14,6 @@ using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Explosion.Components.OnTrigger;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
@@ -24,7 +23,6 @@ using Content.Shared.Slippery;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Whitelist;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -33,7 +31,10 @@ using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Coordinates;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems
|
||||
{
|
||||
@@ -52,12 +53,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised before a trigger is activated.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct BeforeTriggerEvent(EntityUid Triggered, EntityUid? User, bool Cancelled = false);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when timer trigger becomes active.
|
||||
/// </summary>
|
||||
@@ -83,7 +78,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -99,7 +93,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnTriggered);
|
||||
SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnTriggerCollide);
|
||||
SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
|
||||
SubscribeLocalEvent<TriggerImplantActionComponent, ActivateImplantEvent>(OnImplantTrigger);
|
||||
SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
|
||||
SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered);
|
||||
@@ -116,13 +109,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
|
||||
SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
|
||||
SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
|
||||
|
||||
SubscribeLocalEvent<TriggerWhitelistComponent, BeforeTriggerEvent>(HandleWhitelist);
|
||||
}
|
||||
|
||||
private void HandleWhitelist(Entity<TriggerWhitelistComponent> ent, ref BeforeTriggerEvent args)
|
||||
{
|
||||
args.Cancelled = !_whitelist.CheckBoth(args.User, ent.Comp.Blacklist, ent.Comp.Whitelist);
|
||||
}
|
||||
|
||||
private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, TriggerEvent args)
|
||||
@@ -169,23 +155,16 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
RemCompDeferred<AnchorOnTriggerComponent>(uid);
|
||||
}
|
||||
|
||||
private void OnSpawnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
private void OnSpawnTrigger(EntityUid uid, SpawnOnTriggerComponent component, TriggerEvent args)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (ent.Comp.mapCoords)
|
||||
{
|
||||
var mapCoords = _transformSystem.GetMapCoordinates(ent, xform);
|
||||
Spawn(ent.Comp.Proto, mapCoords);
|
||||
}
|
||||
else
|
||||
{
|
||||
var coords = xform.Coordinates;
|
||||
if (!coords.IsValid(EntityManager))
|
||||
return;
|
||||
Spawn(ent.Comp.Proto, coords);
|
||||
var coords = xform.Coordinates;
|
||||
|
||||
}
|
||||
if (!coords.IsValid(EntityManager))
|
||||
return;
|
||||
|
||||
Spawn(component.Proto, coords);
|
||||
}
|
||||
|
||||
private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args)
|
||||
@@ -269,15 +248,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnUse(Entity<TriggerOnUseComponent> ent, ref UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Trigger(ent.Owner, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnImplantTrigger(EntityUid uid, TriggerImplantActionComponent component, ActivateImplantEvent args)
|
||||
{
|
||||
args.Handled = Trigger(uid);
|
||||
@@ -305,11 +275,6 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
|
||||
public bool Trigger(EntityUid trigger, EntityUid? user = null)
|
||||
{
|
||||
var beforeTriggerEvent = new BeforeTriggerEvent(trigger, user);
|
||||
RaiseLocalEvent(trigger, ref beforeTriggerEvent);
|
||||
if (beforeTriggerEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
var triggerEvent = new TriggerEvent(trigger, user);
|
||||
EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent, true);
|
||||
return triggerEvent.Handled;
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Content.Server.Forensics
|
||||
|
||||
private void OnDNAInit(Entity<DnaComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
Log.Debug($"Init DNA {Name(ent.Owner)} {ent.Comp.DNA}");
|
||||
if (ent.Comp.DNA == null)
|
||||
RandomizeDNA((ent.Owner, ent.Comp));
|
||||
else
|
||||
@@ -326,6 +327,7 @@ namespace Content.Server.Forensics
|
||||
ent.Comp.DNA = GenerateDNA();
|
||||
Dirty(ent);
|
||||
|
||||
Log.Debug($"Randomize DNA {Name(ent.Owner)} {ent.Comp.DNA}");
|
||||
var ev = new GenerateDnaEvent { Owner = ent.Owner, DNA = ent.Comp.DNA };
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.Cloning;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Gamerule component for spawning a paradox clone antagonist.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ParadoxCloneRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Cloning settings to be used.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> Settings = "BaseClone";
|
||||
|
||||
/// <summary>
|
||||
/// Visual effect spawned when gibbing at round end.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId GibProto = "MobParadoxTimed";
|
||||
}
|
||||
@@ -24,13 +24,13 @@ public sealed partial class TraitorRuleComponent : Component
|
||||
public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> CodewordAdjectives = "Adjectives";
|
||||
public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> CodewordVerbs = "Verbs";
|
||||
public ProtoId<DatasetPrototype> CodewordVerbs = "verbs";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<LocalizedDatasetPrototype> ObjectiveIssuers = "TraitorCorporations";
|
||||
public ProtoId<DatasetPrototype> ObjectiveIssuers = "TraitorCorporations";
|
||||
|
||||
/// <summary>
|
||||
/// Give this traitor an Uplink on spawn.
|
||||
|
||||
@@ -19,11 +19,6 @@ public abstract partial class GameRuleSystem<T> where T: IComponent
|
||||
return EntityQueryEnumerator<ActiveGameRuleComponent, T, GameRuleComponent>();
|
||||
}
|
||||
|
||||
protected EntityQueryEnumerator<DelayedStartRuleComponent, T, GameRuleComponent> QueryDelayedRules()
|
||||
{
|
||||
return EntityQueryEnumerator<DelayedStartRuleComponent, T, GameRuleComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries all gamerules, regardless of if they're active or not.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Gibbing.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class ParadoxCloneRuleSystem : GameRuleSystem<ParadoxCloneRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly CloningSystem _cloning = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ParadoxCloneRuleComponent, AntagSelectEntityEvent>(OnAntagSelectEntity);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, ParadoxCloneRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
// check if we got enough potential cloning targets, otherwise cancel the gamerule so that the ghost role does not show up
|
||||
var allHumans = _mind.GetAliveHumans();
|
||||
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
Log.Info("Could not find any alive players to create a paradox clone from! Ending gamerule.");
|
||||
ForceEndSelf(uid, gameRule);
|
||||
}
|
||||
}
|
||||
|
||||
// we have to do the spawning here so we can transfer the mind to the correct entity and can assign the objectives correctly
|
||||
private void OnAntagSelectEntity(Entity<ParadoxCloneRuleComponent> ent, ref AntagSelectEntityEvent args)
|
||||
{
|
||||
if (args.Session?.AttachedEntity is not { } spawner)
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(ent.Comp.Settings, out var settings))
|
||||
{
|
||||
Log.Error($"Used invalid cloning settings {ent.Comp.Settings} for ParadoxCloneRule");
|
||||
return;
|
||||
}
|
||||
|
||||
// get possible targets
|
||||
var allHumans = _mind.GetAliveHumans();
|
||||
|
||||
// we already checked when starting the gamerule, but someone might have died since then.
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
Log.Warning("Could not find any alive players to create a paradox clone from!");
|
||||
return;
|
||||
}
|
||||
|
||||
// pick a random player
|
||||
var playerToClone = _random.Pick(allHumans);
|
||||
var bodyToClone = playerToClone.Comp.OwnedEntity;
|
||||
|
||||
if (bodyToClone == null || !_cloning.TryCloning(bodyToClone.Value, _transform.GetMapCoordinates(spawner), settings, out var clone))
|
||||
{
|
||||
Log.Error($"Unable to make a paradox clone of entity {ToPrettyString(bodyToClone)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetComp = EnsureComp<TargetOverrideComponent>(clone.Value);
|
||||
targetComp.Target = playerToClone.Owner; // set the kill target
|
||||
|
||||
var gibComp = EnsureComp<GibOnRoundEndComponent>(clone.Value);
|
||||
gibComp.SpawnProto = ent.Comp.GibProto;
|
||||
gibComp.PreventGibbingObjectives = new() { "ParadoxCloneKillObjective" }; // don't gib them if they killed the original.
|
||||
|
||||
args.Entity = clone;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
commandList.Add(id);
|
||||
}
|
||||
|
||||
return IsGroupDetainedOrDead(commandList, true, true, true);
|
||||
return IsGroupDetainedOrDead(commandList, true, true);
|
||||
}
|
||||
|
||||
private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev)
|
||||
@@ -214,7 +214,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
|
||||
// If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen
|
||||
// Cuffing Head Revs is not enough - they must be killed.
|
||||
if (IsGroupDetainedOrDead(headRevList, false, false, false))
|
||||
if (IsGroupDetainedOrDead(headRevList, false, false))
|
||||
{
|
||||
var rev = AllEntityQuery<RevolutionaryComponent, MindContainerComponent>();
|
||||
while (rev.MoveNext(out var uid, out _, out var mc))
|
||||
@@ -251,45 +251,34 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
/// <param name="list">The list of the entities</param>
|
||||
/// <param name="checkOffStation">Bool for if you want to check if someone is in space and consider them missing in action. (Won't check when emergency shuttle arrives just in case)</param>
|
||||
/// <param name="countCuffed">Bool for if you don't want to count cuffed entities.</param>
|
||||
/// <param name="countRevolutionaries">Bool for if you want to count revolutionaries.</param>
|
||||
/// <returns></returns>
|
||||
private bool IsGroupDetainedOrDead(List<EntityUid> list, bool checkOffStation, bool countCuffed, bool countRevolutionaries)
|
||||
private bool IsGroupDetainedOrDead(List<EntityUid> list, bool checkOffStation, bool countCuffed)
|
||||
{
|
||||
var gone = 0;
|
||||
|
||||
foreach (var entity in list)
|
||||
{
|
||||
if (TryComp<CuffableComponent>(entity, out var cuffed) && cuffed.CuffedHandCount > 0 && countCuffed)
|
||||
{
|
||||
gone++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryComp<MobStateComponent>(entity, out var state))
|
||||
{
|
||||
if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
|
||||
{
|
||||
gone++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
|
||||
{
|
||||
gone++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//If they don't have the MobStateComponent they might as well be dead.
|
||||
else
|
||||
{
|
||||
gone++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((HasComp<RevolutionaryComponent>(entity) || HasComp<HeadRevolutionaryComponent>(entity)) && countRevolutionaries)
|
||||
{
|
||||
gone++;
|
||||
continue;
|
||||
if (TryComp<MobStateComponent>(entity, out var state))
|
||||
{
|
||||
if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid)
|
||||
{
|
||||
gone++;
|
||||
}
|
||||
else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived)
|
||||
{
|
||||
gone++;
|
||||
}
|
||||
}
|
||||
//If they don't have the MobStateComponent they might as well be dead.
|
||||
else
|
||||
{
|
||||
gone++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
@@ -75,7 +74,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
string[] codewords = new string[finalCodewordCount];
|
||||
for (var i = 0; i < finalCodewordCount; i++)
|
||||
{
|
||||
codewords[i] = Loc.GetString(_random.PickAndTake(codewordPool));
|
||||
codewords[i] = _random.PickAndTake(codewordPool);
|
||||
}
|
||||
return codewords;
|
||||
}
|
||||
@@ -99,7 +98,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
|
||||
}
|
||||
|
||||
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers));
|
||||
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values);
|
||||
|
||||
// Uplink code will go here if applicable, but we still need the variable if there aren't any
|
||||
Note[]? code = null;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.Ghost
|
||||
mind = _entities.GetComponent<MindComponent>(mindId);
|
||||
}
|
||||
|
||||
if (!_entities.System<GhostSystem>().OnGhostAttempt(mindId, true, true, mind: mind))
|
||||
if (!_entities.System<GhostSystem>().OnGhostAttempt(mindId, true, true, mind))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("ghost-command-denied"));
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ namespace Content.Server.Ghost
|
||||
return ghost;
|
||||
}
|
||||
|
||||
public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaCommand = false, bool forced = false, MindComponent? mind = null)
|
||||
public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaCommand = false, MindComponent? mind = null)
|
||||
{
|
||||
if (!Resolve(mindId, ref mind))
|
||||
return false;
|
||||
@@ -512,12 +512,7 @@ namespace Content.Server.Ghost
|
||||
var playerEntity = mind.CurrentEntity;
|
||||
|
||||
if (playerEntity != null && viaCommand)
|
||||
{
|
||||
if (forced)
|
||||
_adminLog.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} was forced to ghost via command");
|
||||
else
|
||||
_adminLog.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
|
||||
}
|
||||
_adminLog.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} is attempting to ghost via command");
|
||||
|
||||
var handleEv = new GhostAttemptHandleEvent(mind, canReturnGlobal);
|
||||
RaiseLocalEvent(handleEv);
|
||||
@@ -526,7 +521,7 @@ namespace Content.Server.Ghost
|
||||
if (handleEv.Handled)
|
||||
return handleEv.Result;
|
||||
|
||||
if (mind.PreventGhosting && !forced)
|
||||
if (mind.PreventGhosting)
|
||||
{
|
||||
if (mind.Session != null) // Logging is suppressed to prevent spam from ghost attempts caused by movement attempts
|
||||
{
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Gibbing.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Server.Body.Systems;
|
||||
|
||||
namespace Content.Server.Gibbing.Systems;
|
||||
public sealed class GibOnRoundEndSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// this is raised after RoundEndTextAppendEvent, so they can successfully greentext before we gib them
|
||||
SubscribeLocalEvent<RoundEndMessageEvent>(OnRoundEnd);
|
||||
}
|
||||
|
||||
private void OnRoundEnd(RoundEndMessageEvent args)
|
||||
{
|
||||
var gibQuery = EntityQueryEnumerator<GibOnRoundEndComponent>();
|
||||
|
||||
// gib everyone with the component
|
||||
while (gibQuery.MoveNext(out var uid, out var gibComp))
|
||||
{
|
||||
var gib = false;
|
||||
// if they fulfill all objectives given in the component they are not gibbed
|
||||
if (_mind.TryGetMind(uid, out var mindId, out var mindComp))
|
||||
{
|
||||
foreach (var objectiveId in gibComp.PreventGibbingObjectives)
|
||||
{
|
||||
if (!_mind.TryFindObjective((mindId, mindComp), objectiveId, out var objective)
|
||||
|| !_objectives.IsCompleted(objective.Value, (mindId, mindComp)))
|
||||
{
|
||||
gib = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
gib = true;
|
||||
|
||||
if (!gib)
|
||||
continue;
|
||||
|
||||
if (gibComp.SpawnProto != null)
|
||||
SpawnAtPosition(gibComp.SpawnProto, Transform(uid).Coordinates);
|
||||
|
||||
_body.GibBody(uid, splatModifier: 5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems;
|
||||
@@ -16,7 +15,8 @@ public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||
|
||||
if (ent.Comp.InitialOffset)
|
||||
{
|
||||
SetOffset(ent, _random.Next(ent.Comp.Duration));
|
||||
ent.Comp.Offset = _random.Next(ent.Comp.Duration);
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems;
|
||||
|
||||
public sealed class SunShadowSystem : SharedSunShadowSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Revolutionary.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Mindshield;
|
||||
|
||||
@@ -30,7 +29,6 @@ public sealed class MindShieldSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SubdermalImplantComponent, ImplantImplantedEvent>(ImplantCheck);
|
||||
SubscribeLocalEvent<MindShieldImplantComponent, EntGotRemovedFromContainerMessage>(OnImplantDraw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,10 +61,4 @@ public sealed class MindShieldSystem : EntitySystem
|
||||
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(implanted)} was deconverted due to being implanted with a Mindshield.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImplantDraw(Entity<MindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
RemComp<MindShieldComponent>(args.Container.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random head.
|
||||
/// If there are no heads it will fallback to any person.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PickRandomHeadComponent : Component;
|
||||
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
|
||||
public sealed partial class PickRandomHeadComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random person.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PickRandomPersonComponent : Component;
|
||||
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
|
||||
public sealed partial class PickRandomPersonComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets this objective's target to the one given in <see cref="TargetOverrideComponent"/>, if the entity has it.
|
||||
/// This component needs to be added to objective entity itself.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PickSpecificPersonComponent : Component;
|
||||
@@ -1,7 +1,11 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target for <see cref="KeepAliveConditionComponent"/> to a random traitor.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RandomTraitorAliveComponent : Component;
|
||||
[RegisterComponent, Access(typeof(KeepAliveConditionSystem))]
|
||||
public sealed partial class RandomTraitorAliveComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target for <see cref="HelpProgressConditionComponent"/> to a random traitor.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RandomTraitorProgressComponent : Component;
|
||||
[RegisterComponent, Access(typeof(HelpProgressConditionSystem))]
|
||||
public sealed partial class RandomTraitorProgressComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
18
Content.Server/Objectives/Components/StealTargetComponent.cs
Normal file
18
Content.Server/Objectives/Components/StealTargetComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Objectives.Components.Targets;
|
||||
|
||||
/// <summary>
|
||||
/// Allows an object to become the target of a StealCollection objection
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class StealTargetComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The theft group to which this item belongs.
|
||||
/// </summary>
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string StealGroup;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a target objective to a specific target when receiving it.
|
||||
/// The objective entity needs to have <see cref="PickSpecificPersonComponent"/>.
|
||||
/// This component needs to be added to entity receiving the objective.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TargetOverrideComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that should be targeted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Target;
|
||||
}
|
||||
@@ -12,11 +12,9 @@ using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.Objectives.Commands;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Objectives;
|
||||
@@ -29,20 +27,15 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
[Dependency] private readonly SharedJobSystem _job = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private IEnumerable<string>? _objectives;
|
||||
|
||||
private bool _showGreentext;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||
|
||||
Subs.CVar(_cfg, CCVars.GameShowGreentext, value => _showGreentext = value, true);
|
||||
|
||||
_prototypeManager.PrototypesReloaded += CreateCompletions;
|
||||
}
|
||||
|
||||
@@ -169,11 +162,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
totalObjectives++;
|
||||
|
||||
agentSummary.Append("- ");
|
||||
if (!_showGreentext)
|
||||
{
|
||||
agentSummary.AppendLine(objectiveTitle);
|
||||
}
|
||||
else if (progress > 0.99f)
|
||||
if (progress > 0.99f)
|
||||
{
|
||||
agentSummary.AppendLine(Loc.GetString(
|
||||
"objectives-objective-success",
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles help progress condition logic.
|
||||
/// Handles help progress condition logic and picking random help targets.
|
||||
/// </summary>
|
||||
public sealed class HelpProgressConditionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HelpProgressConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
|
||||
SubscribeLocalEvent<RandomTraitorProgressComponent, ObjectiveAssignedEvent>(OnTraitorAssigned);
|
||||
}
|
||||
|
||||
private void OnGetProgress(EntityUid uid, HelpProgressConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
@@ -28,6 +36,55 @@ public sealed class HelpProgressConditionSystem : EntitySystem
|
||||
args.Progress = GetProgress(target.Value);
|
||||
}
|
||||
|
||||
private void OnTraitorAssigned(EntityUid uid, RandomTraitorProgressComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
|
||||
|
||||
// cant help anyone who is tasked with helping:
|
||||
// 1. thats boring
|
||||
// 2. no cyclic progress dependencies!!!
|
||||
foreach (var traitor in traitors)
|
||||
{
|
||||
// TODO: replace this with TryComp<ObjectivesComponent>(traitor) or something when objectives are moved out of mind
|
||||
if (!TryComp<MindComponent>(traitor.Id, out var mind))
|
||||
continue;
|
||||
|
||||
foreach (var objective in mind.Objectives)
|
||||
{
|
||||
if (HasComp<HelpProgressConditionComponent>(objective))
|
||||
traitors.RemoveWhere(x => x.Mind == mind);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't have multiple objectives to help/save the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
|
||||
{
|
||||
if (TryComp<TargetObjectiveComponent>(objective, out var help))
|
||||
{
|
||||
traitors.RemoveWhere(x => x.Id == help.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no more helpable traitors
|
||||
if (traitors.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(traitors).Id, target);
|
||||
}
|
||||
|
||||
private float GetProgress(EntityUid target)
|
||||
{
|
||||
var total = 0f; // how much progress they have
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles keep alive condition logic.
|
||||
/// Handles keep alive condition logic and picking random traitors to keep alive.
|
||||
/// </summary>
|
||||
public sealed class KeepAliveConditionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<KeepAliveConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
|
||||
SubscribeLocalEvent<RandomTraitorAliveComponent, ObjectiveAssignedEvent>(OnAssigned);
|
||||
}
|
||||
|
||||
private void OnGetProgress(EntityUid uid, KeepAliveConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
@@ -27,6 +35,39 @@ public sealed class KeepAliveConditionSystem : EntitySystem
|
||||
args.Progress = GetProgress(target.Value);
|
||||
}
|
||||
|
||||
private void OnAssigned(EntityUid uid, RandomTraitorAliveComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
|
||||
|
||||
// Can't have multiple objectives to help/save the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
|
||||
{
|
||||
if (TryComp<TargetObjectiveComponent>(objective, out var help))
|
||||
{
|
||||
traitors.RemoveWhere(x => x.Id == help.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You are the first/only traitor.
|
||||
if (traitors.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(traitors).Id, target);
|
||||
}
|
||||
|
||||
private float GetProgress(EntityUid target)
|
||||
{
|
||||
if (!TryComp<MindComponent>(target, out var mind))
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
@@ -14,6 +17,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
|
||||
@@ -22,6 +26,10 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<KillPersonConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
|
||||
SubscribeLocalEvent<PickRandomPersonComponent, ObjectiveAssignedEvent>(OnPersonAssigned);
|
||||
|
||||
SubscribeLocalEvent<PickRandomHeadComponent, ObjectiveAssignedEvent>(OnHeadAssigned);
|
||||
}
|
||||
|
||||
private void OnGetProgress(EntityUid uid, KillPersonConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
@@ -32,6 +40,74 @@ public sealed class KillPersonConditionSystem : EntitySystem
|
||||
args.Progress = GetProgress(target.Value, comp.RequireDead);
|
||||
}
|
||||
|
||||
private void OnPersonAssigned(EntityUid uid, PickRandomPersonComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid objective prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// target already assigned
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
|
||||
// Can't have multiple objectives to kill the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<KillPersonConditionComponent>(objective) && TryComp<TargetObjectiveComponent>(objective, out var kill))
|
||||
{
|
||||
allHumans.RemoveWhere(x => x.Owner == kill.Target);
|
||||
}
|
||||
}
|
||||
|
||||
// no other humans to kill
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(allHumans), target);
|
||||
}
|
||||
|
||||
private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(uid, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// target already assigned
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
// no other humans to kill
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var allHeads = new HashSet<Entity<MindComponent>>();
|
||||
foreach (var person in allHumans)
|
||||
{
|
||||
if (TryComp<MindComponent>(person, out var mind) && mind.OwnedEntity is { } ent && HasComp<CommandStaffComponent>(ent))
|
||||
allHeads.Add(person);
|
||||
}
|
||||
|
||||
if (allHeads.Count == 0)
|
||||
allHeads = allHumans; // fallback to non-head target
|
||||
|
||||
_target.SetTarget(uid, _random.Pick(allHeads), target);
|
||||
}
|
||||
|
||||
private float GetProgress(EntityUid target, bool requireDead)
|
||||
{
|
||||
// deleted or gibbed or something, counts as dead
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles assinging a target to an objective entity with <see cref="TargetObjectiveComponent"/> using different components.
|
||||
/// These can be combined with condition components for objective completions in order to create a variety of objectives.
|
||||
/// </summary>
|
||||
public sealed class PickObjectiveTargetSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly TargetObjectiveSystem _target = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PickSpecificPersonComponent, ObjectiveAssignedEvent>(OnSpecificPersonAssigned);
|
||||
SubscribeLocalEvent<PickRandomPersonComponent, ObjectiveAssignedEvent>(OnRandomPersonAssigned);
|
||||
SubscribeLocalEvent<PickRandomHeadComponent, ObjectiveAssignedEvent>(OnRandomHeadAssigned);
|
||||
|
||||
SubscribeLocalEvent<RandomTraitorProgressComponent, ObjectiveAssignedEvent>(OnRandomTraitorProgressAssigned);
|
||||
SubscribeLocalEvent<RandomTraitorAliveComponent, ObjectiveAssignedEvent>(OnRandomTraitorAliveAssigned);
|
||||
}
|
||||
|
||||
private void OnSpecificPersonAssigned(Entity<PickSpecificPersonComponent> ent, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid objective prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(ent.Owner, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// target already assigned
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
if (args.Mind.OwnedEntity == null)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var user = args.Mind.OwnedEntity.Value;
|
||||
if (!TryComp<TargetOverrideComponent>(user, out var targetComp) || targetComp.Target == null)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(ent.Owner, targetComp.Target.Value);
|
||||
}
|
||||
|
||||
private void OnRandomPersonAssigned(Entity<PickRandomPersonComponent> ent, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid objective prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(ent.Owner, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// target already assigned
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
|
||||
// Can't have multiple objectives to kill the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<KillPersonConditionComponent>(objective) && TryComp<TargetObjectiveComponent>(objective, out var kill))
|
||||
{
|
||||
allHumans.RemoveWhere(x => x.Owner == kill.Target);
|
||||
}
|
||||
}
|
||||
|
||||
// no other humans to kill
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(ent.Owner, _random.Pick(allHumans), target);
|
||||
}
|
||||
|
||||
private void OnRandomHeadAssigned(Entity<PickRandomHeadComponent> ent, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(ent.Owner, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// target already assigned
|
||||
if (target.Target != null)
|
||||
return;
|
||||
|
||||
// no other humans to kill
|
||||
var allHumans = _mind.GetAliveHumans(args.MindId);
|
||||
if (allHumans.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var allHeads = new HashSet<Entity<MindComponent>>();
|
||||
foreach (var person in allHumans)
|
||||
{
|
||||
if (TryComp<MindComponent>(person, out var mind) && mind.OwnedEntity is { } owned && HasComp<CommandStaffComponent>(owned))
|
||||
allHeads.Add(person);
|
||||
}
|
||||
|
||||
if (allHeads.Count == 0)
|
||||
allHeads = allHumans; // fallback to non-head target
|
||||
|
||||
_target.SetTarget(ent.Owner, _random.Pick(allHeads), target);
|
||||
}
|
||||
|
||||
private void OnRandomTraitorProgressAssigned(Entity<RandomTraitorProgressComponent> ent, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(ent.Owner, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
|
||||
|
||||
// cant help anyone who is tasked with helping:
|
||||
// 1. thats boring
|
||||
// 2. no cyclic progress dependencies!!!
|
||||
foreach (var traitor in traitors)
|
||||
{
|
||||
// TODO: replace this with TryComp<ObjectivesComponent>(traitor) or something when objectives are moved out of mind
|
||||
if (!TryComp<MindComponent>(traitor.Id, out var mind))
|
||||
continue;
|
||||
|
||||
foreach (var objective in mind.Objectives)
|
||||
{
|
||||
if (HasComp<HelpProgressConditionComponent>(objective))
|
||||
traitors.RemoveWhere(x => x.Mind == mind);
|
||||
}
|
||||
}
|
||||
|
||||
// Can't have multiple objectives to help/save the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
|
||||
{
|
||||
if (TryComp<TargetObjectiveComponent>(objective, out var help))
|
||||
{
|
||||
traitors.RemoveWhere(x => x.Id == help.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no more helpable traitors
|
||||
if (traitors.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(ent.Owner, _random.Pick(traitors).Id, target);
|
||||
}
|
||||
|
||||
private void OnRandomTraitorAliveAssigned(Entity<RandomTraitorAliveComponent> ent, ref ObjectiveAssignedEvent args)
|
||||
{
|
||||
// invalid prototype
|
||||
if (!TryComp<TargetObjectiveComponent>(ent.Owner, out var target))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).ToHashSet();
|
||||
|
||||
// Can't have multiple objectives to help/save the same person
|
||||
foreach (var objective in args.Mind.Objectives)
|
||||
{
|
||||
if (HasComp<RandomTraitorAliveComponent>(objective) || HasComp<RandomTraitorProgressComponent>(objective))
|
||||
{
|
||||
if (TryComp<TargetObjectiveComponent>(objective, out var help))
|
||||
{
|
||||
traitors.RemoveWhere(x => x.Id == help.Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You are the first/only traitor.
|
||||
if (traitors.Count == 0)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_target.SetTarget(ent.Owner, _random.Pick(traitors).Id, target);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Components.Targets;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mind;
|
||||
|
||||
@@ -1033,9 +1033,6 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
|
||||
|
||||
EnsureComp<LightCycleComponent>(mapUid);
|
||||
|
||||
EnsureComp<SunShadowComponent>(mapUid);
|
||||
EnsureComp<SunShadowCycleComponent>(mapUid);
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Polymorph.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Intended for use with the trigger system.
|
||||
/// Polymorphs the user of the trigger.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PolymorphOnTriggerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Polymorph settings.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<PolymorphPrototype> Polymorph;
|
||||
}
|
||||
65
Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs
Normal file
65
Content.Server/Polymorph/Systems/PolymorphSystem.Collide.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Polymorph.Systems;
|
||||
|
||||
public partial class PolymorphSystem
|
||||
{
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Need to do this so we don't get a collection enumeration error in physics by polymorphing
|
||||
/// an entity we're colliding with
|
||||
/// </summary>
|
||||
private Queue<PolymorphQueuedData> _queuedPolymorphUpdates = new();
|
||||
|
||||
private void InitializeCollide()
|
||||
{
|
||||
SubscribeLocalEvent<PolymorphOnCollideComponent, StartCollideEvent>(OnPolymorphCollide);
|
||||
}
|
||||
|
||||
public void UpdateCollide()
|
||||
{
|
||||
while (_queuedPolymorphUpdates.TryDequeue(out var data))
|
||||
{
|
||||
if (Deleted(data.Ent))
|
||||
continue;
|
||||
|
||||
var ent = PolymorphEntity(data.Ent, data.Polymorph);
|
||||
if (ent != null)
|
||||
_audio.PlayPvs(data.Sound, ent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPolymorphCollide(EntityUid uid, PolymorphOnCollideComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != SharedProjectileSystem.ProjectileFixture)
|
||||
return;
|
||||
|
||||
var other = args.OtherEntity;
|
||||
if (_whitelistSystem.IsWhitelistFail(component.Whitelist, other) ||
|
||||
_whitelistSystem.IsBlacklistPass(component.Blacklist, other))
|
||||
return;
|
||||
|
||||
_queuedPolymorphUpdates.Enqueue(new (other, component.Sound, component.Polymorph));
|
||||
}
|
||||
}
|
||||
|
||||
public struct PolymorphQueuedData
|
||||
{
|
||||
public EntityUid Ent;
|
||||
public SoundSpecifier Sound;
|
||||
public ProtoId<PolymorphPrototype> Polymorph;
|
||||
|
||||
public PolymorphQueuedData(EntityUid ent, SoundSpecifier sound, ProtoId<PolymorphPrototype> polymorph)
|
||||
{
|
||||
Ent = ent;
|
||||
Sound = sound;
|
||||
Polymorph = polymorph;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Shared.Polymorph;
|
||||
using Content.Server.Polymorph.Components;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Polymorph.Systems;
|
||||
|
||||
public sealed partial class PolymorphSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Need to do this so we don't get a collection enumeration error in physics by polymorphing
|
||||
/// an entity we're colliding with in case of TriggerOnCollide.
|
||||
/// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed.
|
||||
/// </summary>
|
||||
private Queue<(EntityUid Ent, ProtoId<PolymorphPrototype> Polymorph)> _queuedPolymorphUpdates = new();
|
||||
|
||||
private void InitializeTrigger()
|
||||
{
|
||||
SubscribeLocalEvent<PolymorphOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
private void OnTrigger(Entity<PolymorphOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.User == null)
|
||||
return;
|
||||
|
||||
_queuedPolymorphUpdates.Enqueue((args.User.Value, ent.Comp.Polymorph));
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public void UpdateTrigger()
|
||||
{
|
||||
while (_queuedPolymorphUpdates.TryDequeue(out var data))
|
||||
{
|
||||
if (TerminatingOrDeleted(data.Item1))
|
||||
continue;
|
||||
|
||||
PolymorphEntity(data.Item1, data.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,8 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
|
||||
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
|
||||
|
||||
InitializeCollide();
|
||||
InitializeMap();
|
||||
InitializeTrigger();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -89,7 +89,7 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTrigger();
|
||||
UpdateCollide();
|
||||
}
|
||||
|
||||
private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
|
||||
@@ -204,12 +204,6 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
|
||||
var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid));
|
||||
|
||||
if (configuration.PolymorphPopup != null)
|
||||
_popup.PopupEntity(Loc.GetString(configuration.PolymorphPopup,
|
||||
("parent", Identity.Entity(uid, EntityManager)),
|
||||
("child", Identity.Entity(child, EntityManager))),
|
||||
child);
|
||||
|
||||
MakeSentientCommand.MakeSentient(child, EntityManager);
|
||||
|
||||
var polymorphedComp = _compFact.GetComponent<PolymorphedEntityComponent>();
|
||||
@@ -353,11 +347,10 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
var ev = new PolymorphedEvent(uid, parent, true);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (component.Configuration.ExitPolymorphPopup != null)
|
||||
_popup.PopupEntity(Loc.GetString(component.Configuration.ExitPolymorphPopup,
|
||||
_popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic",
|
||||
("parent", Identity.Entity(uid, EntityManager)),
|
||||
("child", Identity.Entity(parent, EntityManager))),
|
||||
parent);
|
||||
parent);
|
||||
QueueDel(uid);
|
||||
|
||||
return parent;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Server.Power.Components
|
||||
/// When false, causes this to appear powered even if not receiving power from an Apc.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override bool NeedsPower
|
||||
public bool NeedsPower
|
||||
{
|
||||
get => _needsPower;
|
||||
set
|
||||
@@ -41,9 +41,9 @@ namespace Content.Server.Power.Components
|
||||
/// <summary>
|
||||
/// When true, causes this to never appear powered.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("powerDisabled")]
|
||||
public override bool PowerDisabled
|
||||
{
|
||||
public bool PowerDisabled {
|
||||
get => !NetworkLoad.Enabled;
|
||||
set => NetworkLoad.Enabled = !value;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -14,8 +20,9 @@ namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
private EntityQuery<ApcPowerReceiverComponent> _recQuery;
|
||||
private EntityQuery<ApcPowerProviderComponent> _provQuery;
|
||||
|
||||
@@ -56,10 +63,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
Text = Loc.GetString("verb-debug-toggle-need-power"),
|
||||
Category = VerbCategory.Debug,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")), // "smite" is a lightning bolt
|
||||
Act = () =>
|
||||
{
|
||||
SetNeedsPower(uid, !component.NeedsPower, component);
|
||||
}
|
||||
Act = () => component.NeedsPower = !component.NeedsPower
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,9 +143,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
args.State = new ApcPowerReceiverComponentState
|
||||
{
|
||||
Powered = component.Powered,
|
||||
NeedsPower = component.NeedsPower,
|
||||
PowerDisabled = component.PowerDisabled,
|
||||
Powered = component.Powered
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,6 +164,36 @@ namespace Content.Server.Power.EntitySystems
|
||||
return !_recQuery.Resolve(uid, ref receiver, false) || receiver.Powered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turn this machine on or off.
|
||||
/// Returns true if we turned it on, false if we turned it off.
|
||||
/// </summary>
|
||||
public bool TogglePower(EntityUid uid, bool playSwitchSound = true, ApcPowerReceiverComponent? receiver = null, EntityUid? user = null)
|
||||
{
|
||||
if (!_recQuery.Resolve(uid, ref receiver, false))
|
||||
return true;
|
||||
|
||||
// it'll save a lot of confusion if 'always powered' means 'always powered'
|
||||
if (!receiver.NeedsPower)
|
||||
{
|
||||
receiver.PowerDisabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
receiver.PowerDisabled = !receiver.PowerDisabled;
|
||||
|
||||
if (user != null)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value):player} hit power button on {ToPrettyString(uid)}, it's now {(!receiver.PowerDisabled ? "on" : "off")}");
|
||||
|
||||
if (playSwitchSound)
|
||||
{
|
||||
_audio.PlayPvs(new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"), uid,
|
||||
AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
return !receiver.PowerDisabled; // i.e. PowerEnabled
|
||||
}
|
||||
|
||||
public void SetLoad(ApcPowerReceiverComponent comp, float load)
|
||||
{
|
||||
comp.Load = load;
|
||||
|
||||
@@ -11,7 +11,6 @@ using Content.Shared.Atmos;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Power.Generation.Teg;
|
||||
using Content.Shared.Rounding;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -67,12 +66,11 @@ public sealed class TegSystem : EntitySystem
|
||||
/// </summary>
|
||||
public const string DeviceNetworkCommandSyncData = "teg_sync_data";
|
||||
|
||||
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
|
||||
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
|
||||
|
||||
private EntityQuery<NodeContainerComponent> _nodeContainerQuery;
|
||||
|
||||
@@ -243,7 +241,8 @@ public sealed class TegSystem : EntitySystem
|
||||
|
||||
var powerReceiver = Comp<ApcPowerReceiverComponent>(uid);
|
||||
|
||||
_receiver.SetPowerDisabled(uid, !group.IsFullyBuilt, powerReceiver);
|
||||
powerReceiver.PowerDisabled = !group.IsFullyBuilt;
|
||||
|
||||
UpdateAppearance(uid, component, powerReceiver, group);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Wires;
|
||||
@@ -62,17 +61,15 @@ public sealed partial class PowerWireAction : BaseWireAction
|
||||
return;
|
||||
}
|
||||
|
||||
var receiverSys = EntityManager.System<PowerReceiverSystem>();
|
||||
|
||||
if (pulsed)
|
||||
{
|
||||
receiverSys.SetPowerDisabled(owner, true, power);
|
||||
power.PowerDisabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (AllWiresCut(owner))
|
||||
{
|
||||
receiverSys.SetPowerDisabled(owner, true, power);
|
||||
power.PowerDisabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -82,7 +79,7 @@ public sealed partial class PowerWireAction : BaseWireAction
|
||||
return;
|
||||
}
|
||||
|
||||
receiverSys.SetPowerDisabled(owner, false, power);
|
||||
power.PowerDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user