Merge remote-tracking branch 'upstream/master' into upstream
# Conflicts: # Content.Client/Access/AccessOverlay.cs # Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs # Content.Client/Access/UI/IdCardConsoleWindow.xaml # Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs # Content.Client/Chemistry/UI/InjectorStatusControl.cs # Content.Client/StatusIcon/StatusIconOverlay.cs # Content.Client/Stylesheets/StyleNano.cs # Content.Client/UserInterface/Systems/Chat/ChatUIController.cs # Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml # Content.Server/Access/Systems/IdCardConsoleSystem.cs # Content.Server/Administration/Commands/AGhost.cs # Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs # Content.Server/Connection/ConnectionManager.cs # Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs # Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs # Content.Server/GameTicking/GameTicker.RoundFlow.cs # Content.Server/GameTicking/GameTicker.Spawning.cs # Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.cs # Content.Server/Resist/EscapeInventorySystem.cs # Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs # Content.Shared/Access/Components/IdCardConsoleComponent.cs # Content.Shared/Anomaly/SharedAnomalySystem.cs # Content.Shared/Bed/Sleep/SharedSleepingSystem.cs # Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs # Content.Shared/Lock/LockSystem.cs # Content.Shared/RCD/Systems/RCDSystem.cs # Content.Shared/Roles/JobPrototype.cs # Content.Shared/StatusIcon/StatusIconPrototype.cs # Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs # Resources/Audio/Machines/attributions.yml # Resources/Locale/en-US/rcd/components/rcd-component.ftl # Resources/Maps/reach.yml # Resources/Prototypes/Catalog/Cargo/cargo_vending.yml # Resources/Prototypes/Catalog/Fills/Lockers/heads.yml # Resources/Prototypes/Catalog/Fills/Lockers/security.yml # Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/cola.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/lawdrobe.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/pwrgame.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/shamblersjuice.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/soda.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/spaceup.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/starkist.yml # Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml # Resources/Prototypes/DeviceLinking/sink_ports.yml # Resources/Prototypes/Entities/Clothing/Back/duffel.yml # Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml # Resources/Prototypes/Entities/Clothing/Neck/misc.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml # Resources/Prototypes/Entities/Mobs/Customization/Markings/gauze.yml # Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml # Resources/Prototypes/Entities/Objects/Magic/books.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml # Resources/Prototypes/Entities/Objects/Misc/tiles.yml # Resources/Prototypes/Entities/Objects/Specific/Medical/morgue.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/shotgun.yml # Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_assembly.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml # Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml # Resources/Prototypes/Entities/Structures/Doors/Firelocks/frame.yml # Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml # Resources/Prototypes/Entities/Structures/Doors/SecretDoor/secret_door.yml # Resources/Prototypes/Entities/Structures/Doors/Windoors/assembly.yml # Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml # Resources/Prototypes/Entities/Structures/Machines/lathe.yml # Resources/Prototypes/Entities/Structures/Power/cable_terminal.yml # Resources/Prototypes/Entities/Structures/Storage/Tanks/base_structuretanks.yml # Resources/Prototypes/Entities/Structures/Walls/grille.yml # Resources/Prototypes/Recipes/Construction/Graphs/structures/shutter.yml # Resources/Prototypes/Recipes/Crafting/Graphs/improvised/flowercrown.yml # Resources/Prototypes/Recipes/Crafting/improvised.yml # Resources/Prototypes/Roles/Jobs/Security/detective.yml # Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml # Resources/Prototypes/Roles/Jobs/Security/security_officer.yml # Resources/Prototypes/Roles/Jobs/Security/warden.yml # Resources/Prototypes/StatusEffects/health.yml # Resources/Prototypes/Voice/speech_emotes.yml # Resources/Prototypes/lobbyscreens.yml # Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertchaplain.rsi/equipped-OUTERCLOTHING-body-slim.png # Resources/Textures/Decals/bricktile.rsi/white_box.png # Resources/Textures/Objects/Misc/books.rsi/meta.json # Resources/migration.yml
This commit is contained in:
@@ -9,20 +9,20 @@ namespace Content.Client.Access;
|
|||||||
|
|
||||||
public sealed class AccessOverlay : Overlay
|
public sealed class AccessOverlay : Overlay
|
||||||
{
|
{
|
||||||
|
private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
|
||||||
|
private const int TextFontSize = 12;
|
||||||
|
|
||||||
private readonly IEntityManager _entityManager;
|
private readonly IEntityManager _entityManager;
|
||||||
private readonly EntityLookupSystem _lookup;
|
private readonly SharedTransformSystem _transformSystem;
|
||||||
private readonly SharedTransformSystem _xform;
|
|
||||||
private readonly Font _font;
|
private readonly Font _font;
|
||||||
|
|
||||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||||
|
|
||||||
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
|
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
|
||||||
{
|
{
|
||||||
_entityManager = entManager;
|
_entityManager = entityManager;
|
||||||
_lookup = lookup;
|
_transformSystem = transformSystem;
|
||||||
_xform = xform;
|
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
|
||||||
|
|
||||||
_font = cache.GetFont("/Fonts/IBMPlexMono/IBMPlexMono-Regular.ttf", 12);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
@@ -30,52 +30,65 @@ public sealed class AccessOverlay : Overlay
|
|||||||
if (args.ViewportControl == null)
|
if (args.ViewportControl == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
|
var textBuffer = new StringBuilder();
|
||||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var accessReader, out var transform))
|
||||||
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
|
|
||||||
LookupFlags.Static | LookupFlags.Approximate))
|
|
||||||
{
|
{
|
||||||
if (!readerQuery.TryGetComponent(ent, out var reader) ||
|
textBuffer.Clear();
|
||||||
!xformQuery.TryGetComponent(ent, out var xform))
|
|
||||||
|
var entityName = _entityManager.ToPrettyString(uid);
|
||||||
|
textBuffer.AppendLine(entityName.Prototype);
|
||||||
|
textBuffer.Append("UID: ");
|
||||||
|
textBuffer.Append(entityName.Uid.Id);
|
||||||
|
textBuffer.Append(", NUID: ");
|
||||||
|
textBuffer.Append(entityName.Nuid.Id);
|
||||||
|
textBuffer.AppendLine();
|
||||||
|
|
||||||
|
if (!accessReader.Enabled)
|
||||||
{
|
{
|
||||||
|
textBuffer.AppendLine("-Disabled");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = new StringBuilder();
|
if (accessReader.AccessLists.Count > 0)
|
||||||
var index = 0;
|
|
||||||
var a = $"{_entityManager.ToPrettyString(ent)}";
|
|
||||||
text.Append(a);
|
|
||||||
|
|
||||||
foreach (var list in reader.AccessLists)
|
|
||||||
{
|
{
|
||||||
a = $"Tag {index}";
|
var groupNumber = 0;
|
||||||
text.AppendLine(a);
|
foreach (var accessList in accessReader.AccessLists)
|
||||||
|
|
||||||
foreach (var entry in list)
|
|
||||||
{
|
{
|
||||||
a = $"- {entry}";
|
groupNumber++;
|
||||||
text.AppendLine(a);
|
foreach (var entry in accessList)
|
||||||
|
{
|
||||||
|
textBuffer.Append("+Set ");
|
||||||
|
textBuffer.Append(groupNumber);
|
||||||
|
textBuffer.Append(": ");
|
||||||
|
textBuffer.Append(entry.Id);
|
||||||
|
textBuffer.AppendLine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
string textStr;
|
|
||||||
|
|
||||||
if (text.Length >= 2)
|
|
||||||
{
|
|
||||||
textStr = text.ToString();
|
|
||||||
textStr = textStr[..^2];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
textStr = "";
|
textBuffer.AppendLine("+Unrestricted");
|
||||||
}
|
}
|
||||||
|
|
||||||
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
|
foreach (var key in accessReader.AccessKeys)
|
||||||
|
{
|
||||||
|
textBuffer.Append("+Key ");
|
||||||
|
textBuffer.Append(key.OriginStation);
|
||||||
|
textBuffer.Append(": ");
|
||||||
|
textBuffer.Append(key.Id);
|
||||||
|
textBuffer.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
|
foreach (var tag in accessReader.DenyTags)
|
||||||
|
{
|
||||||
|
textBuffer.Append("-Tag ");
|
||||||
|
textBuffer.AppendLine(tag.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var accessInfoText = textBuffer.ToString();
|
||||||
|
var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
|
||||||
|
args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,16 @@ namespace Content.Client.Access.Commands;
|
|||||||
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||||
{
|
{
|
||||||
public string Command => "showaccessreaders";
|
public string Command => "showaccessreaders";
|
||||||
public string Description => "Shows all access readers in the viewport";
|
|
||||||
public string Help => $"{Command}";
|
public string Description => "Toggles showing access reader permissions on the map";
|
||||||
|
public string Help => """
|
||||||
|
Overlay Info:
|
||||||
|
-Disabled | The access reader is disabled
|
||||||
|
+Unrestricted | The access reader has no restrictions
|
||||||
|
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
|
||||||
|
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
|
||||||
|
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
|
||||||
|
""";
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
{
|
{
|
||||||
var collection = IoCManager.Instance;
|
var collection = IoCManager.Instance;
|
||||||
@@ -26,10 +34,9 @@ public sealed class ShowAccessReadersCommand : IConsoleCommand
|
|||||||
|
|
||||||
var entManager = collection.Resolve<IEntityManager>();
|
var entManager = collection.Resolve<IEntityManager>();
|
||||||
var cache = collection.Resolve<IResourceCache>();
|
var cache = collection.Resolve<IResourceCache>();
|
||||||
var lookup = entManager.System<EntityLookupSystem>();
|
|
||||||
var xform = entManager.System<SharedTransformSystem>();
|
var xform = entManager.System<SharedTransformSystem>();
|
||||||
|
|
||||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
|
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
|
||||||
shell.WriteLine($"Set access reader debug overlay to true");
|
shell.WriteLine($"Set access reader debug overlay to true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
Content.Client/Access/UI/AccessLevelControl.xaml
Normal file
4
Content.Client/Access/UI/AccessLevelControl.xaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<GridContainer xmlns="https://spacestation14.io"
|
||||||
|
Columns="5"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
</GridContainer>
|
||||||
52
Content.Client/Access/UI/AccessLevelControl.xaml.cs
Normal file
52
Content.Client/Access/UI/AccessLevelControl.xaml.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Access.UI;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AccessLevelControl : GridContainer
|
||||||
|
{
|
||||||
|
public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new();
|
||||||
|
|
||||||
|
public AccessLevelControl()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
|
||||||
|
{
|
||||||
|
foreach (var access in accessLevels)
|
||||||
|
{
|
||||||
|
if (!prototypeManager.TryIndex(access, out var accessLevel))
|
||||||
|
{
|
||||||
|
Logger.Error($"Unable to find accesslevel for {access}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newButton = new Button
|
||||||
|
{
|
||||||
|
Text = accessLevel.GetAccessLevelName(),
|
||||||
|
ToggleMode = true,
|
||||||
|
};
|
||||||
|
AddChild(newButton);
|
||||||
|
ButtonsList.Add(accessLevel.ID, newButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(
|
||||||
|
List<ProtoId<AccessLevelPrototype>> pressedList,
|
||||||
|
List<ProtoId<AccessLevelPrototype>>? enabledList = null)
|
||||||
|
{
|
||||||
|
foreach (var (accessName, button) in ButtonsList)
|
||||||
|
{
|
||||||
|
button.Pressed = pressedList.Contains(accessName);
|
||||||
|
button.Disabled = !(enabledList?.Contains(accessName) ?? true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ namespace Content.Client.Access.UI
|
|||||||
_window?.UpdateState(castState);
|
_window?.UpdateState(castState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubmitData(List<string> newAccessList)
|
public void SubmitData(List<ProtoId<AccessLevelPrototype>> newAccessList)
|
||||||
{
|
{
|
||||||
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
|
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ namespace Content.Client.Access.UI
|
|||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
private readonly ISawmill _logMill = default!;
|
|
||||||
private readonly AccessOverriderBoundUserInterface _owner;
|
private readonly AccessOverriderBoundUserInterface _owner;
|
||||||
private readonly Dictionary<string, Button> _accessButtons = new();
|
private readonly Dictionary<string, Button> _accessButtons = new();
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ namespace Content.Client.Access.UI
|
|||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
|
var logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
|
||||||
|
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
|
|
||||||
@@ -33,13 +32,13 @@ namespace Content.Client.Access.UI
|
|||||||
{
|
{
|
||||||
if (!prototypeManager.TryIndex(access, out var accessLevel))
|
if (!prototypeManager.TryIndex(access, out var accessLevel))
|
||||||
{
|
{
|
||||||
_logMill.Error($"Unable to find accesslevel for {access}");
|
logMill.Error($"Unable to find accesslevel for {access}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newButton = new Button
|
var newButton = new Button
|
||||||
{
|
{
|
||||||
Text = GetAccessLevelName(accessLevel),
|
Text = accessLevel.GetAccessLevelName(),
|
||||||
ToggleMode = true,
|
ToggleMode = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,14 +48,6 @@ namespace Content.Client.Access.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetAccessLevelName(AccessLevelPrototype prototype)
|
|
||||||
{
|
|
||||||
if (prototype.Name is { } name)
|
|
||||||
return Loc.GetString(name);
|
|
||||||
|
|
||||||
return prototype.ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
|
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
PrivilegedIdLabel.Text = state.PrivilegedIdName;
|
PrivilegedIdLabel.Text = state.PrivilegedIdName;
|
||||||
@@ -105,7 +96,7 @@ namespace Content.Client.Access.UI
|
|||||||
_owner.SubmitData(
|
_owner.SubmitData(
|
||||||
|
|
||||||
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
||||||
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
|
_accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId<AccessLevelPrototype>(x.Key)).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ namespace Content.Client.Access.UI
|
|||||||
if (EntMan.TryGetComponent<IdCardConsoleComponent>(Owner, out var idCard))
|
if (EntMan.TryGetComponent<IdCardConsoleComponent>(Owner, out var idCard))
|
||||||
{
|
{
|
||||||
accessLevels = idCard.AccessLevels;
|
accessLevels = idCard.AccessLevels;
|
||||||
accessLevels.Sort();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -70,7 +69,7 @@ namespace Content.Client.Access.UI
|
|||||||
public void SubmitData(
|
public void SubmitData(
|
||||||
string newFullName,
|
string newFullName,
|
||||||
string newJobTitle,
|
string newJobTitle,
|
||||||
List<string> newAccessList,
|
List<ProtoId<AccessLevelPrototype>> newAccessList,
|
||||||
string newJobPrototype,
|
string newJobPrototype,
|
||||||
string? newJobIcon)
|
string? newJobIcon)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,11 +30,7 @@
|
|||||||
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
|
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
|
||||||
<OptionButton Name="JobPresetOptionButton" />
|
<OptionButton Name="JobPresetOptionButton" />
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
|
<Control Name="AccessLevelControlContainer" />
|
||||||
|
|
||||||
<!-- Access level buttons are added here by the C# code -->
|
|
||||||
|
|
||||||
</GridContainer>
|
|
||||||
<!-- WD EDIT -->
|
<!-- WD EDIT -->
|
||||||
<GridContainer Name="CurrentJobIcon" Columns="2">
|
<GridContainer Name="CurrentJobIcon" Columns="2">
|
||||||
<Label Text="Текущая выбранная иконка для роли: " />
|
<Label Text="Текущая выбранная иконка для роли: " />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Shared.Access;
|
using Content.Shared.Access;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Access.Systems;
|
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
@@ -22,11 +21,10 @@ namespace Content.Client.Access.UI
|
|||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resource = default!; //WD-EDIT
|
[Dependency] private readonly IResourceCache _resource = default!; //WD-EDIT
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!; //WD-EDIT
|
[Dependency] private readonly IEntityManager _entityManager = default!; //WD-EDIT
|
||||||
private readonly ISawmill _logMill = default!;
|
|
||||||
|
|
||||||
private readonly IdCardConsoleBoundUserInterface _owner;
|
private readonly IdCardConsoleBoundUserInterface _owner;
|
||||||
|
|
||||||
private readonly Dictionary<string, Button> _accessButtons = new();
|
private AccessLevelControl _accessButtons = new();
|
||||||
private readonly Dictionary<string, TextureButton> _jobIconButtons = new(); //WD-EDIT
|
private readonly Dictionary<string, TextureButton> _jobIconButtons = new(); //WD-EDIT
|
||||||
private readonly List<string> _jobPrototypeIds = new();
|
private readonly List<string> _jobPrototypeIds = new();
|
||||||
|
|
||||||
@@ -42,7 +40,6 @@ namespace Content.Client.Access.UI
|
|||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_logMill = _logManager.GetSawmill(SharedIdCardConsoleSystem.Sawmill);
|
|
||||||
|
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
|
|
||||||
@@ -65,7 +62,6 @@ namespace Content.Client.Access.UI
|
|||||||
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>().ToList();
|
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>().ToList();
|
||||||
jobs.Sort((x, y) => string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCulture));
|
jobs.Sort((x, y) => string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCulture));
|
||||||
|
|
||||||
List<Button> buttonsToAdd = new();
|
|
||||||
foreach (var job in jobs)
|
foreach (var job in jobs)
|
||||||
{
|
{
|
||||||
if (!job.OverrideConsoleVisibility.GetValueOrDefault(job.SetPreference))
|
if (!job.OverrideConsoleVisibility.GetValueOrDefault(job.SetPreference))
|
||||||
@@ -79,30 +75,12 @@ namespace Content.Client.Access.UI
|
|||||||
|
|
||||||
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
|
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
|
||||||
|
|
||||||
foreach (var access in accessLevels)
|
_accessButtons.Populate(accessLevels, prototypeManager);
|
||||||
|
AccessLevelControlContainer.AddChild(_accessButtons);
|
||||||
|
|
||||||
|
foreach (var (id, button) in _accessButtons.ButtonsList)
|
||||||
{
|
{
|
||||||
if (!prototypeManager.TryIndex(access, out var accessLevel))
|
button.OnPressed += _ => SubmitData();
|
||||||
{
|
|
||||||
_logMill.Error($"Unable to find accesslevel for {access}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newButton = new Button
|
|
||||||
{
|
|
||||||
Text = GetAccessLevelName(accessLevel),
|
|
||||||
ToggleMode = true
|
|
||||||
};
|
|
||||||
|
|
||||||
_accessButtons.Add(accessLevel.ID, newButton);
|
|
||||||
newButton.OnPressed += _ => SubmitData();
|
|
||||||
buttonsToAdd.Add(newButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonsToAdd.Sort((x, y) => string.Compare(x.Text, y.Text, StringComparison.Ordinal));
|
|
||||||
|
|
||||||
foreach (var button in buttonsToAdd)
|
|
||||||
{
|
|
||||||
AccessLevelGrid.AddChild(button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
@@ -136,17 +114,9 @@ namespace Content.Client.Access.UI
|
|||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetAccessLevelName(AccessLevelPrototype prototype)
|
|
||||||
{
|
|
||||||
if (prototype.Name is { } name)
|
|
||||||
return Loc.GetString(name);
|
|
||||||
|
|
||||||
return prototype.ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearAllAccess()
|
private void ClearAllAccess()
|
||||||
{
|
{
|
||||||
foreach (var button in _accessButtons.Values)
|
foreach (var button in _accessButtons.ButtonsList.Values)
|
||||||
{
|
{
|
||||||
if (button.Pressed)
|
if (button.Pressed)
|
||||||
{
|
{
|
||||||
@@ -170,7 +140,7 @@ namespace Content.Client.Access.UI
|
|||||||
// this is a sussy way to do this
|
// this is a sussy way to do this
|
||||||
foreach (var access in job.Access)
|
foreach (var access in job.Access)
|
||||||
{
|
{
|
||||||
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
|
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
|
||||||
{
|
{
|
||||||
button.Pressed = true;
|
button.Pressed = true;
|
||||||
}
|
}
|
||||||
@@ -185,7 +155,7 @@ namespace Content.Client.Access.UI
|
|||||||
|
|
||||||
foreach (var access in groupPrototype.Tags)
|
foreach (var access in groupPrototype.Tags)
|
||||||
{
|
{
|
||||||
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
|
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
|
||||||
{
|
{
|
||||||
button.Pressed = true;
|
button.Pressed = true;
|
||||||
}
|
}
|
||||||
@@ -236,15 +206,10 @@ namespace Content.Client.Access.UI
|
|||||||
|
|
||||||
JobPresetOptionButton.Disabled = !interfaceEnabled;
|
JobPresetOptionButton.Disabled = !interfaceEnabled;
|
||||||
|
|
||||||
foreach (var (accessName, button) in _accessButtons)
|
_accessButtons.UpdateState(state.TargetIdAccessList?.ToList() ??
|
||||||
{
|
new List<ProtoId<AccessLevelPrototype>>(),
|
||||||
button.Disabled = !interfaceEnabled;
|
state.AllowedModifyAccessList?.ToList() ??
|
||||||
if (!interfaceEnabled)
|
new List<ProtoId<AccessLevelPrototype>>());
|
||||||
continue;
|
|
||||||
|
|
||||||
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
|
|
||||||
button.Disabled = !state.AllowedModifyAccessList?.Contains(accessName) ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||||
if (jobIndex >= 0)
|
if (jobIndex >= 0)
|
||||||
@@ -298,7 +263,7 @@ namespace Content.Client.Access.UI
|
|||||||
FullNameLineEdit.Text,
|
FullNameLineEdit.Text,
|
||||||
JobTitleLineEdit.Text,
|
JobTitleLineEdit.Text,
|
||||||
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
||||||
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
|
_accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
|
||||||
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty,
|
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty,
|
||||||
_lastJobIcon);
|
_lastJobIcon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<controls:FancyWindow
|
<controls:FancyWindow
|
||||||
xmlns="https://spacestation14.io"
|
xmlns="https://spacestation14.io"
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
Title="{Loc 'anomaly-scanner-ui-title'}"
|
Title="{Loc 'anomaly-scanner-ui-title'}"
|
||||||
MinSize="350 260"
|
MinSize="350 400"
|
||||||
SetSize="350 260">
|
SetSize="350 400">
|
||||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="10 0 10 10">
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="10 0 10 10">
|
||||||
<RichTextLabel Name="TextDisplay"></RichTextLabel>
|
<RichTextLabel Name="TextDisplay"></RichTextLabel>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
|||||||
SendMessage(new BountyPrintLabelMessage(id));
|
SendMessage(new BountyPrintLabelMessage(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_menu.OnSkipButtonPressed += id =>
|
||||||
|
{
|
||||||
|
SendMessage(new BountySkipMessage(id));
|
||||||
|
};
|
||||||
|
|
||||||
_menu.OpenCentered();
|
_menu.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +42,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
|||||||
if (message is not CargoBountyConsoleState state)
|
if (message is not CargoBountyConsoleState state)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_menu?.UpdateEntries(state.Bounties);
|
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -13,7 +13,18 @@
|
|||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<Control MinWidth="10"/>
|
<Control MinWidth="10"/>
|
||||||
<BoxContainer Orientation="Vertical" MinWidth="120">
|
<BoxContainer Orientation="Vertical" MinWidth="120">
|
||||||
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
|
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
||||||
|
<Button Name="PrintButton"
|
||||||
|
Text="{Loc 'bounty-console-label-button-text'}"
|
||||||
|
HorizontalExpand="False"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
StyleClasses="OpenRight"/>
|
||||||
|
<Button Name="SkipButton"
|
||||||
|
Text="{Loc 'bounty-console-skip-button-text'}"
|
||||||
|
HorizontalExpand="False"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
StyleClasses="OpenLeft"/>
|
||||||
|
</BoxContainer>
|
||||||
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
|
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Shared.Cargo;
|
using Content.Shared.Cargo;
|
||||||
using Content.Shared.Cargo.Prototypes;
|
using Content.Shared.Cargo.Prototypes;
|
||||||
|
using Content.Shared.Random;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Content.Client.Cargo.UI;
|
namespace Content.Client.Cargo.UI;
|
||||||
|
|
||||||
@@ -14,15 +16,19 @@ public sealed partial class BountyEntry : BoxContainer
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
|
||||||
public Action? OnButtonPressed;
|
public Action? OnLabelButtonPressed;
|
||||||
|
public Action? OnSkipButtonPressed;
|
||||||
|
|
||||||
public TimeSpan EndTime;
|
public TimeSpan EndTime;
|
||||||
|
public TimeSpan UntilNextSkip;
|
||||||
|
|
||||||
public BountyEntry(CargoBountyData bounty)
|
public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
UntilNextSkip = untilNextSkip;
|
||||||
|
|
||||||
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -38,6 +44,27 @@ public sealed partial class BountyEntry : BoxContainer
|
|||||||
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
|
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
|
||||||
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
|
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
|
||||||
|
|
||||||
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
|
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
|
||||||
|
SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSkipButton(float deltaSeconds)
|
||||||
|
{
|
||||||
|
UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
|
||||||
|
if (UntilNextSkip > TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
|
||||||
|
SkipButton.Disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
|
||||||
|
SkipButton.Disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
{
|
||||||
|
base.FrameUpdate(args);
|
||||||
|
UpdateSkipButton(args.DeltaSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,21 @@ namespace Content.Client.Cargo.UI;
|
|||||||
public sealed partial class CargoBountyMenu : FancyWindow
|
public sealed partial class CargoBountyMenu : FancyWindow
|
||||||
{
|
{
|
||||||
public Action<string>? OnLabelButtonPressed;
|
public Action<string>? OnLabelButtonPressed;
|
||||||
|
public Action<string>? OnSkipButtonPressed;
|
||||||
|
|
||||||
public CargoBountyMenu()
|
public CargoBountyMenu()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateEntries(List<CargoBountyData> bounties)
|
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||||
{
|
{
|
||||||
BountyEntriesContainer.Children.Clear();
|
BountyEntriesContainer.Children.Clear();
|
||||||
foreach (var b in bounties)
|
foreach (var b in bounties)
|
||||||
{
|
{
|
||||||
var entry = new BountyEntry(b);
|
var entry = new BountyEntry(b, untilNextSkip);
|
||||||
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||||
|
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
|
||||||
|
|
||||||
BountyEntriesContainer.AddChild(entry);
|
BountyEntriesContainer.AddChild(entry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ public sealed class InjectorStatusControl : Control
|
|||||||
private readonly SharedSolutionContainerSystem _solutionContainers;
|
private readonly SharedSolutionContainerSystem _solutionContainers;
|
||||||
private readonly RichTextLabel _label;
|
private readonly RichTextLabel _label;
|
||||||
|
|
||||||
private FixedPoint2 _prevVolume;
|
private FixedPoint2 PrevVolume;
|
||||||
private FixedPoint2 _prevMaxVolume;
|
private FixedPoint2 PrevMaxVolume;
|
||||||
private InjectorToggleMode _prevToggleState;
|
private FixedPoint2 PrevTransferAmount;
|
||||||
|
private InjectorToggleMode PrevToggleState;
|
||||||
|
|
||||||
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers)
|
public InjectorStatusControl(Entity<InjectorComponent> parent, SharedSolutionContainerSystem solutionContainers)
|
||||||
{
|
{
|
||||||
@@ -35,14 +36,16 @@ public sealed class InjectorStatusControl : Control
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// only updates the UI if any of the details are different than they previously were
|
// only updates the UI if any of the details are different than they previously were
|
||||||
if (_prevVolume == solution.Volume
|
if (PrevVolume == solution.Volume
|
||||||
&& _prevMaxVolume == solution.MaxVolume
|
&& PrevMaxVolume == solution.MaxVolume
|
||||||
&& _prevToggleState == _parent.Comp.ToggleState)
|
&& PrevTransferAmount == _parent.Comp.TransferAmount
|
||||||
|
&& PrevToggleState == _parent.Comp.ToggleState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_prevVolume = solution.Volume;
|
PrevVolume = solution.Volume;
|
||||||
_prevMaxVolume = solution.MaxVolume;
|
PrevMaxVolume = solution.MaxVolume;
|
||||||
_prevToggleState = _parent.Comp.ToggleState;
|
PrevTransferAmount = _parent.Comp.TransferAmount;
|
||||||
|
PrevToggleState = _parent.Comp.ToggleState;
|
||||||
|
|
||||||
// Update current volume and injector state
|
// Update current volume and injector state
|
||||||
var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
|
var modeStringLocalized = Loc.GetString(_parent.Comp.ToggleState switch
|
||||||
|
|||||||
@@ -96,24 +96,22 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
|||||||
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
|
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
|
||||||
{
|
{
|
||||||
if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state, appearance))
|
if (!_appearanceSystem.TryGetData<VisualState>(uid, Visuals.VisualState, out var state, appearance))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
|
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
|
||||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
|
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
|
||||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
|
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state is VisualState.OverlayFlushing or VisualState.OverlayCharging);
|
||||||
|
|
||||||
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
|
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
|
||||||
? sprite.LayerGetState(chargingLayer)
|
? sprite.LayerGetState(chargingLayer)
|
||||||
: new RSI.StateId(DefaultChargeState);
|
: new RSI.StateId(DefaultChargeState);
|
||||||
|
|
||||||
// This is a transient state so not too worried about replaying in range.
|
// This is a transient state so not too worried about replaying in range.
|
||||||
if (state == VisualState.Flushing)
|
if (state == VisualState.OverlayFlushing)
|
||||||
{
|
{
|
||||||
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
|
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
|
||||||
{
|
{
|
||||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayer)
|
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
|
||||||
? sprite.LayerGetState(flushLayer)
|
? sprite.LayerGetState(flushLayer)
|
||||||
: new RSI.StateId(DefaultFlushState);
|
: new RSI.StateId(DefaultFlushState);
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
|||||||
{
|
{
|
||||||
new AnimationTrackSpriteFlick
|
new AnimationTrackSpriteFlick
|
||||||
{
|
{
|
||||||
LayerKey = DisposalUnitVisualLayers.BaseFlush,
|
LayerKey = DisposalUnitVisualLayers.OverlayFlush,
|
||||||
KeyFrames =
|
KeyFrames =
|
||||||
{
|
{
|
||||||
// Play the flush animation
|
// Play the flush animation
|
||||||
@@ -154,26 +152,18 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
|||||||
_animationSystem.Play(uid, anim, AnimationKey);
|
_animationSystem.Play(uid, anim, AnimationKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (state == VisualState.Charging)
|
else if (state == VisualState.OverlayCharging)
|
||||||
{
|
sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, new RSI.StateId("disposal-charging"));
|
||||||
sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, chargingState);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
_animationSystem.Stop(uid, AnimationKey);
|
_animationSystem.Stop(uid, AnimationKey);
|
||||||
}
|
|
||||||
|
|
||||||
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
|
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
|
||||||
{
|
|
||||||
handleState = HandleState.Normal;
|
handleState = HandleState.Normal;
|
||||||
}
|
|
||||||
|
|
||||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
|
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
|
||||||
|
|
||||||
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
|
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
|
||||||
{
|
|
||||||
lightState = LightStates.Off;
|
lightState = LightStates.Off;
|
||||||
}
|
|
||||||
|
|
||||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
||||||
(lightState & LightStates.Charging) != 0);
|
(lightState & LightStates.Charging) != 0);
|
||||||
@@ -189,7 +179,7 @@ public enum DisposalUnitVisualLayers : byte
|
|||||||
Unanchored,
|
Unanchored,
|
||||||
Base,
|
Base,
|
||||||
BaseCharging,
|
BaseCharging,
|
||||||
BaseFlush,
|
OverlayFlush,
|
||||||
OverlayCharging,
|
OverlayCharging,
|
||||||
OverlayReady,
|
OverlayReady,
|
||||||
OverlayFull,
|
OverlayFull,
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Doors.Electronics;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.Doors.Electronics;
|
||||||
|
|
||||||
|
public sealed class DoorElectronicsBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
private DoorElectronicsConfigurationMenu? _window;
|
||||||
|
|
||||||
|
public DoorElectronicsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
List<ProtoId<AccessLevelPrototype>> accessLevels = new();
|
||||||
|
|
||||||
|
foreach (var accessLevel in _prototypeManager.EnumeratePrototypes<AccessLevelPrototype>())
|
||||||
|
{
|
||||||
|
if (accessLevel.Name != null)
|
||||||
|
{
|
||||||
|
accessLevels.Add(accessLevel.ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accessLevels.Sort();
|
||||||
|
|
||||||
|
_window = new DoorElectronicsConfigurationMenu(this, accessLevels, _prototypeManager);
|
||||||
|
_window.OnClose += Close;
|
||||||
|
_window.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
var castState = (DoorElectronicsConfigurationState) state;
|
||||||
|
|
||||||
|
_window?.UpdateState(castState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateConfiguration(List<ProtoId<AccessLevelPrototype>> newAccessList)
|
||||||
|
{
|
||||||
|
SendMessage(new DoorElectronicsUpdateConfigurationMessage(newAccessList));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.UserInterface"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||||
|
<Control Name="AccessLevelControlContainer" />
|
||||||
|
|
||||||
|
</controls:FancyWindow>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Client.Access.UI;
|
||||||
|
using Content.Client.Doors.Electronics;
|
||||||
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Doors.Electronics;
|
||||||
|
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||||
|
|
||||||
|
namespace Content.Client.Doors.Electronics;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class DoorElectronicsConfigurationMenu : FancyWindow
|
||||||
|
{
|
||||||
|
private readonly DoorElectronicsBoundUserInterface _owner;
|
||||||
|
private AccessLevelControl _buttonsList = new();
|
||||||
|
|
||||||
|
public DoorElectronicsConfigurationMenu(DoorElectronicsBoundUserInterface ui, List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
_owner = ui;
|
||||||
|
|
||||||
|
_buttonsList.Populate(accessLevels, prototypeManager);
|
||||||
|
AccessLevelControlContainer.AddChild(_buttonsList);
|
||||||
|
|
||||||
|
foreach (var (id, button) in _buttonsList.ButtonsList)
|
||||||
|
{
|
||||||
|
button.OnPressed += _ => _owner.UpdateConfiguration(
|
||||||
|
_buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(DoorElectronicsConfigurationState state)
|
||||||
|
{
|
||||||
|
_buttonsList.UpdateState(state.AccessList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -212,14 +212,16 @@ namespace Content.Client.Examine
|
|||||||
var vBox = new BoxContainer
|
var vBox = new BoxContainer
|
||||||
{
|
{
|
||||||
Name = "ExaminePopupVbox",
|
Name = "ExaminePopupVbox",
|
||||||
Orientation = LayoutOrientation.Vertical
|
Orientation = LayoutOrientation.Vertical,
|
||||||
|
MaxWidth = _examineTooltipOpen.MaxWidth
|
||||||
};
|
};
|
||||||
panel.AddChild(vBox);
|
panel.AddChild(vBox);
|
||||||
|
|
||||||
var hBox = new BoxContainer
|
var hBox = new BoxContainer
|
||||||
{
|
{
|
||||||
Orientation = LayoutOrientation.Horizontal,
|
Orientation = LayoutOrientation.Horizontal,
|
||||||
SeparationOverride = 5
|
SeparationOverride = 5,
|
||||||
|
Margin = new Thickness(6, 0, 6, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
vBox.AddChild(hBox);
|
vBox.AddChild(hBox);
|
||||||
@@ -229,8 +231,7 @@ namespace Content.Client.Examine
|
|||||||
var spriteView = new SpriteView
|
var spriteView = new SpriteView
|
||||||
{
|
{
|
||||||
OverrideDirection = Direction.South,
|
OverrideDirection = Direction.South,
|
||||||
SetSize = new Vector2(32, 32),
|
SetSize = new Vector2(32, 32)
|
||||||
Margin = new Thickness(2, 0, 2, 0),
|
|
||||||
};
|
};
|
||||||
spriteView.SetEntity(target);
|
spriteView.SetEntity(target);
|
||||||
hBox.AddChild(spriteView);
|
hBox.AddChild(spriteView);
|
||||||
@@ -238,19 +239,17 @@ namespace Content.Client.Examine
|
|||||||
|
|
||||||
if (knowTarget)
|
if (knowTarget)
|
||||||
{
|
{
|
||||||
hBox.AddChild(new Label
|
var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player));
|
||||||
{
|
var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]");
|
||||||
Text = Identity.Name(target, EntityManager, player),
|
var label = new RichTextLabel();
|
||||||
HorizontalExpand = true,
|
label.SetMessage(labelMessage);
|
||||||
});
|
hBox.AddChild(label);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hBox.AddChild(new Label
|
var label = new RichTextLabel();
|
||||||
{
|
label.SetMessage(FormattedMessage.FromMarkup("[bold]???[/bold]"));
|
||||||
Text = "???",
|
hBox.AddChild(label);
|
||||||
HorizontalExpand = true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panel.Measure(Vector2Helpers.Infinity);
|
panel.Measure(Vector2Helpers.Infinity);
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ namespace Content.Client.Input
|
|||||||
// Not in engine because the engine doesn't understand what a flipped object is
|
// Not in engine because the engine doesn't understand what a flipped object is
|
||||||
common.AddFunction(ContentKeyFunctions.EditorFlipObject);
|
common.AddFunction(ContentKeyFunctions.EditorFlipObject);
|
||||||
|
|
||||||
|
// Not in engine so that the RCD can rotate objects
|
||||||
|
common.AddFunction(EngineKeyFunctions.EditorRotateObject);
|
||||||
|
|
||||||
var human = contexts.GetContext("human");
|
var human = contexts.GetContext("human");
|
||||||
human.AddFunction(EngineKeyFunctions.MoveUp);
|
human.AddFunction(EngineKeyFunctions.MoveUp);
|
||||||
human.AddFunction(EngineKeyFunctions.MoveDown);
|
human.AddFunction(EngineKeyFunctions.MoveDown);
|
||||||
|
|||||||
@@ -124,9 +124,7 @@
|
|||||||
<BoxContainer
|
<BoxContainer
|
||||||
VerticalExpand="True"
|
VerticalExpand="True"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical">
|
||||||
MinHeight="225"
|
|
||||||
>
|
|
||||||
<Label Text="{Loc 'lathe-menu-materials-title'}" Margin="5 5 5 5" HorizontalAlignment="Center"/>
|
<Label Text="{Loc 'lathe-menu-materials-title'}" Margin="5 5 5 5" HorizontalAlignment="Center"/>
|
||||||
<BoxContainer
|
<BoxContainer
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
|
|||||||
@@ -104,41 +104,12 @@ public sealed partial class LatheMenu : DefaultWindow
|
|||||||
RecipeList.Children.Clear();
|
RecipeList.Children.Clear();
|
||||||
foreach (var prototype in sortedRecipesToShow)
|
foreach (var prototype in sortedRecipesToShow)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
|
||||||
var first = true;
|
|
||||||
foreach (var (id, amount) in prototype.RequiredMaterials)
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (first)
|
|
||||||
first = false;
|
|
||||||
else
|
|
||||||
sb.Append('\n');
|
|
||||||
|
|
||||||
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
|
|
||||||
var sheetVolume = _materialStorage.GetSheetVolume(proto);
|
|
||||||
|
|
||||||
var unit = Loc.GetString(proto.Unit);
|
|
||||||
// rounded in locale not here
|
|
||||||
var sheets = adjustedAmount / (float) sheetVolume;
|
|
||||||
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
|
|
||||||
var name = Loc.GetString(proto.Name);
|
|
||||||
sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(prototype.Description))
|
|
||||||
{
|
|
||||||
sb.Append('\n');
|
|
||||||
sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var icon = prototype.Icon == null
|
var icon = prototype.Icon == null
|
||||||
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
|
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
|
||||||
: _spriteSystem.Frame0(prototype.Icon);
|
: _spriteSystem.Frame0(prototype.Icon);
|
||||||
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
|
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
|
||||||
|
|
||||||
var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
|
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
|
||||||
control.OnButtonPressed += s =>
|
control.OnButtonPressed += s =>
|
||||||
{
|
{
|
||||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||||
@@ -149,6 +120,51 @@ public sealed partial class LatheMenu : DefaultWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GenerateTooltipText(LatheRecipePrototype prototype)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new();
|
||||||
|
|
||||||
|
foreach (var (id, amount) in prototype.RequiredMaterials)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, _entityManager.GetComponent<LatheComponent>(_owner).MaterialUseMultiplier);
|
||||||
|
var sheetVolume = _materialStorage.GetSheetVolume(proto);
|
||||||
|
|
||||||
|
var unit = Loc.GetString(proto.Unit);
|
||||||
|
var sheets = adjustedAmount / (float) sheetVolume;
|
||||||
|
|
||||||
|
var availableAmount = _materialStorage.GetMaterialAmount(_owner, id);
|
||||||
|
var missingAmount = Math.Max(0, adjustedAmount - availableAmount);
|
||||||
|
var missingSheets = missingAmount / (float) sheetVolume;
|
||||||
|
|
||||||
|
var name = Loc.GetString(proto.Name);
|
||||||
|
|
||||||
|
string tooltipText;
|
||||||
|
if (missingSheets > 0)
|
||||||
|
{
|
||||||
|
tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
|
||||||
|
tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(tooltipText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(prototype.Description))
|
||||||
|
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
|
||||||
|
|
||||||
|
// Remove last newline
|
||||||
|
if (sb.Length > 0)
|
||||||
|
sb.Remove(sb.Length - 1, 1);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateCategories()
|
public void UpdateCategories()
|
||||||
{
|
{
|
||||||
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();
|
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();
|
||||||
|
|||||||
@@ -11,17 +11,16 @@ namespace Content.Client.Lathe.UI;
|
|||||||
public sealed partial class RecipeControl : Control
|
public sealed partial class RecipeControl : Control
|
||||||
{
|
{
|
||||||
public Action<string>? OnButtonPressed;
|
public Action<string>? OnButtonPressed;
|
||||||
|
public Func<string> TooltipTextSupplier;
|
||||||
|
|
||||||
public string TooltipText;
|
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Texture? texture = null)
|
||||||
|
|
||||||
public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
|
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
RecipeName.Text = recipe.Name;
|
RecipeName.Text = recipe.Name;
|
||||||
RecipeTexture.Texture = texture;
|
RecipeTexture.Texture = texture;
|
||||||
Button.Disabled = !canProduce;
|
Button.Disabled = !canProduce;
|
||||||
TooltipText = tooltip;
|
TooltipTextSupplier = tooltipTextSupplier;
|
||||||
Button.TooltipSupplier = SupplyTooltip;
|
Button.TooltipSupplier = SupplyTooltip;
|
||||||
|
|
||||||
Button.OnPressed += (_) =>
|
Button.OnPressed += (_) =>
|
||||||
@@ -32,6 +31,6 @@ public sealed partial class RecipeControl : Control
|
|||||||
|
|
||||||
private Control? SupplyTooltip(Control sender)
|
private Control? SupplyTooltip(Control sender)
|
||||||
{
|
{
|
||||||
return new RecipeTooltip(TooltipText);
|
return new RecipeTooltip(TooltipTextSupplier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<BoxContainer xmlns="https://spacestation14.io"
|
<ScrollContainer xmlns="https://spacestation14.io"
|
||||||
Orientation="Vertical"
|
|
||||||
SizeFlagsStretchRatio="8"
|
SizeFlagsStretchRatio="8"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
VerticalExpand="True">
|
VerticalExpand="True">
|
||||||
<Label Name="NoMatsLabel" Text="{Loc 'lathe-menu-no-materials-message'}" Align="Center"/>
|
<BoxContainer Name="MaterialList" Orientation="Vertical">
|
||||||
</BoxContainer>
|
<Label Name="NoMatsLabel" Text="{Loc 'lathe-menu-no-materials-message'}" Align="Center"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</ScrollContainer>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Client.Materials.UI;
|
|||||||
/// This widget is one row in the lathe eject menu.
|
/// This widget is one row in the lathe eject menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class MaterialStorageControl : BoxContainer
|
public sealed partial class MaterialStorageControl : ScrollContainer
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ public sealed partial class MaterialStorageControl : BoxContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
var children = new List<MaterialDisplay>();
|
var children = new List<MaterialDisplay>();
|
||||||
children.AddRange(Children.OfType<MaterialDisplay>());
|
children.AddRange(MaterialList.Children.OfType<MaterialDisplay>());
|
||||||
|
|
||||||
foreach (var display in children)
|
foreach (var display in children)
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@ public sealed partial class MaterialStorageControl : BoxContainer
|
|||||||
|
|
||||||
if (extra.Contains(mat))
|
if (extra.Contains(mat))
|
||||||
{
|
{
|
||||||
RemoveChild(display);
|
MaterialList.RemoveChild(display);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ public sealed partial class MaterialStorageControl : BoxContainer
|
|||||||
foreach (var mat in missing)
|
foreach (var mat in missing)
|
||||||
{
|
{
|
||||||
var volume = mats[mat];
|
var volume = mats[mat];
|
||||||
AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
|
MaterialList.AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentMaterials = mats;
|
_currentMaterials = mats;
|
||||||
|
|||||||
@@ -210,9 +210,9 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
|
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (sensor.TotalDamage != null)
|
else if (sensor.DamagePercentage != null)
|
||||||
{
|
{
|
||||||
var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
|
var index = MathF.Round(4f * sensor.DamagePercentage.Value);
|
||||||
|
|
||||||
if (index >= 5)
|
if (index >= 5)
|
||||||
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
|
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
using Content.Shared.Nutrition.EntitySystems;
|
|
||||||
|
|
||||||
namespace Content.Client.Nutrition.EntitySystems;
|
|
||||||
|
|
||||||
public sealed class OpenableSystem : SharedOpenableSystem
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,14 @@
|
|||||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||||
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Text="{Loc 'ui-options-chat-window-opacity'}" Margin="8 0" />
|
||||||
|
<Slider Name="ChatWindowOpacitySlider"
|
||||||
|
MinValue="0"
|
||||||
|
MaxValue="1"
|
||||||
|
MinWidth="200" />
|
||||||
|
<Label Name="ChatWindowOpacityLabel" Margin="8 0" />
|
||||||
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
|
<Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
|
||||||
<Slider Name="ScreenShakeIntensitySlider"
|
<Slider Name="ScreenShakeIntensitySlider"
|
||||||
@@ -41,6 +49,7 @@
|
|||||||
<Label Text="{Loc 'ui-options-general-speech'}"
|
<Label Text="{Loc 'ui-options-general-speech'}"
|
||||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||||
StyleClasses="LabelKeyText"/>
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<CheckBox Name="ShowOocPatronColor" Text="{Loc 'ui-options-show-ooc-patron-color'}" />
|
||||||
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
||||||
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||||
@@ -66,6 +75,3 @@
|
|||||||
</controls:StripeBack>
|
</controls:StripeBack>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</tabs:MiscTab>
|
</tabs:MiscTab>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ using Content.Client.UserInterface.Screens;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.HUD;
|
using Content.Shared.HUD;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared;
|
using Robust.Shared;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class MiscTab : Control
|
public sealed partial class MiscTab : Control
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
@@ -55,8 +59,11 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
UpdateApplyButton();
|
UpdateApplyButton();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel.UserData.PatronTier is { } patron;
|
||||||
|
|
||||||
HudThemeOption.OnItemSelected += OnHudThemeChanged;
|
HudThemeOption.OnItemSelected += OnHudThemeChanged;
|
||||||
DiscordRich.OnToggled += OnCheckBoxToggled;
|
DiscordRich.OnToggled += OnCheckBoxToggled;
|
||||||
|
ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
|
||||||
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
|
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
|
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
|
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
@@ -66,12 +73,14 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
|
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
|
ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
|
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
|
||||||
|
ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
|
||||||
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
|
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
|
||||||
// ToggleWalk.OnToggled += OnCheckBoxToggled;
|
// ToggleWalk.OnToggled += OnCheckBoxToggled;
|
||||||
StaticStorageUI.OnToggled += OnCheckBoxToggled;
|
StaticStorageUI.OnToggled += OnCheckBoxToggled;
|
||||||
|
|
||||||
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
|
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
|
||||||
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
|
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
|
||||||
|
ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
|
||||||
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||||
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
|
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||||
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||||
@@ -81,6 +90,7 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||||
ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
||||||
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
|
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
|
||||||
|
ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
|
||||||
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
|
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
|
||||||
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
|
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
|
||||||
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
|
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
|
||||||
@@ -101,6 +111,13 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
UpdateApplyButton();
|
UpdateApplyButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnChatWindowOpacitySliderChanged(Range range)
|
||||||
|
{
|
||||||
|
ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
|
||||||
|
("opacity", range.Value));
|
||||||
|
UpdateApplyButton();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnScreenShakeIntensitySliderChanged(Range obj)
|
private void OnScreenShakeIntensitySliderChanged(Range obj)
|
||||||
{
|
{
|
||||||
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
|
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
|
||||||
@@ -121,12 +138,14 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
|
||||||
|
_cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
|
||||||
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
|
||||||
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
|
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
|
||||||
|
_cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
|
||||||
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
|
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
|
||||||
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
|
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
|
||||||
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
|
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
|
||||||
@@ -148,12 +167,14 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
|
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||||
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||||
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
|
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
|
||||||
|
var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
|
||||||
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||||
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||||
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||||
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
|
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||||
var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
|
||||||
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
|
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
|
||||||
|
var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
|
||||||
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
|
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
|
||||||
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
|
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
|
||||||
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
|
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
|
||||||
@@ -164,12 +185,14 @@ namespace Content.Client.Options.UI.Tabs
|
|||||||
isShowHeldItemSame &&
|
isShowHeldItemSame &&
|
||||||
isCombatModeIndicatorsSame &&
|
isCombatModeIndicatorsSame &&
|
||||||
isOpaqueStorageWindow &&
|
isOpaqueStorageWindow &&
|
||||||
|
isOocPatronColorShowSame &&
|
||||||
isLoocShowSame &&
|
isLoocShowSame &&
|
||||||
isFancyChatSame &&
|
isFancyChatSame &&
|
||||||
isFancyBackgroundSame &&
|
isFancyBackgroundSame &&
|
||||||
isEnableColorNameSame &&
|
isEnableColorNameSame &&
|
||||||
isColorblindFriendly &&
|
isColorblindFriendly &&
|
||||||
isReducedMotionSame &&
|
isReducedMotionSame &&
|
||||||
|
isChatWindowOpacitySame &&
|
||||||
isScreenShakeIntensitySame &&
|
isScreenShakeIntensitySame &&
|
||||||
// isToggleWalkSame &&
|
// isToggleWalkSame &&
|
||||||
isStaticStorageUISame;
|
isStaticStorageUISame;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace Content.Client.Overlays;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EntityHealthBarOverlay : Overlay
|
public sealed class EntityHealthBarOverlay : Overlay
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
|
||||||
private readonly IEntityManager _entManager;
|
private readonly IEntityManager _entManager;
|
||||||
private readonly SharedTransformSystem _transform;
|
private readonly SharedTransformSystem _transform;
|
||||||
private readonly MobStateSystem _mobStateSystem;
|
private readonly MobStateSystem _mobStateSystem;
|
||||||
@@ -27,17 +26,14 @@ public sealed class EntityHealthBarOverlay : Overlay
|
|||||||
private readonly ProgressColorSystem _progressColor;
|
private readonly ProgressColorSystem _progressColor;
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||||
public HashSet<string> DamageContainers = new();
|
public HashSet<string> DamageContainers = new();
|
||||||
private readonly ShaderInstance _shader;
|
|
||||||
|
|
||||||
public EntityHealthBarOverlay(IEntityManager entManager)
|
public EntityHealthBarOverlay(IEntityManager entManager)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
_entManager = entManager;
|
_entManager = entManager;
|
||||||
_transform = _entManager.System<SharedTransformSystem>();
|
_transform = _entManager.System<SharedTransformSystem>();
|
||||||
_mobStateSystem = _entManager.System<MobStateSystem>();
|
_mobStateSystem = _entManager.System<MobStateSystem>();
|
||||||
_mobThresholdSystem = _entManager.System<MobThresholdSystem>();
|
_mobThresholdSystem = _entManager.System<MobThresholdSystem>();
|
||||||
_progressColor = _entManager.System<ProgressColorSystem>();
|
_progressColor = _entManager.System<ProgressColorSystem>();
|
||||||
_shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
@@ -50,8 +46,6 @@ public sealed class EntityHealthBarOverlay : Overlay
|
|||||||
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
|
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
|
||||||
var rotationMatrix = Matrix3.CreateRotation(-rotation);
|
var rotationMatrix = Matrix3.CreateRotation(-rotation);
|
||||||
|
|
||||||
handle.UseShader(_shader);
|
|
||||||
|
|
||||||
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
|
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
|
||||||
while (query.MoveNext(out var uid,
|
while (query.MoveNext(out var uid,
|
||||||
out var mobThresholdsComponent,
|
out var mobThresholdsComponent,
|
||||||
@@ -122,7 +116,6 @@ public sealed class EntityHealthBarOverlay : Overlay
|
|||||||
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
|
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.UseShader(null);
|
|
||||||
handle.SetTransform(Matrix3.Identity);
|
handle.SetTransform(Matrix3.Identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using static Robust.Client.GameObjects.SpriteComponent;
|
|
||||||
using Content.Shared.Clothing;
|
|
||||||
using Content.Shared.Hands;
|
|
||||||
using Content.Shared.Paint;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Client.Paint;
|
|
||||||
|
|
||||||
public sealed class PaintedVisualizerSystem : VisualizerSystem<PaintedComponent>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Visualizer for Paint which applies a shader and colors the entity.
|
|
||||||
/// </summary>
|
|
||||||
|
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
||||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<PaintedComponent, HeldVisualsUpdatedEvent>(OnHeldVisualsUpdated);
|
|
||||||
SubscribeLocalEvent<PaintedComponent, ComponentShutdown>(OnShutdown);
|
|
||||||
SubscribeLocalEvent<PaintedComponent, EquipmentVisualsUpdatedEvent>(OnEquipmentVisualsUpdated);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAppearanceChange(EntityUid uid, PaintedComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
var shader = _protoMan.Index<ShaderPrototype>(component.ShaderName).Instance();
|
|
||||||
|
|
||||||
if (args.Sprite == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// What is this even doing? It's not even checking what the value is.
|
|
||||||
if (!_appearance.TryGetData(uid, PaintVisuals.Painted, out bool isPainted))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var sprite = args.Sprite;
|
|
||||||
|
|
||||||
foreach (var spriteLayer in sprite.AllLayers)
|
|
||||||
{
|
|
||||||
if (spriteLayer is not Layer layer)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (layer.Shader == null) // If shader isn't null we dont want to replace the original shader.
|
|
||||||
{
|
|
||||||
layer.Shader = shader;
|
|
||||||
layer.Color = component.Color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnHeldVisualsUpdated(EntityUid uid, PaintedComponent component, HeldVisualsUpdatedEvent args)
|
|
||||||
{
|
|
||||||
if (args.RevealedLayers.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!TryComp(args.User, out SpriteComponent? sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var revealed in args.RevealedLayers)
|
|
||||||
{
|
|
||||||
if (!sprite.LayerMapTryGet(revealed, out var layer))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
sprite.LayerSetShader(layer, component.ShaderName);
|
|
||||||
sprite.LayerSetColor(layer, component.Color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEquipmentVisualsUpdated(EntityUid uid, PaintedComponent component, EquipmentVisualsUpdatedEvent args)
|
|
||||||
{
|
|
||||||
if (args.RevealedLayers.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!TryComp(args.Equipee, out SpriteComponent? sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var revealed in args.RevealedLayers)
|
|
||||||
{
|
|
||||||
if (!sprite.LayerMapTryGet(revealed, out var layer))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
sprite.LayerSetShader(layer, component.ShaderName);
|
|
||||||
sprite.LayerSetColor(layer, component.Color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnShutdown(EntityUid uid, PaintedComponent component, ref ComponentShutdown args)
|
|
||||||
{
|
|
||||||
if (!TryComp(uid, out SpriteComponent? sprite))
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.BeforeColor = sprite.Color;
|
|
||||||
var shader = _protoMan.Index<ShaderPrototype>(component.ShaderName).Instance();
|
|
||||||
|
|
||||||
if (!Terminating(uid))
|
|
||||||
{
|
|
||||||
foreach (var spriteLayer in sprite.AllLayers)
|
|
||||||
{
|
|
||||||
if (spriteLayer is not Layer layer)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (layer.Shader == shader) // If shader isn't same as one in component we need to ignore it.
|
|
||||||
{
|
|
||||||
layer.Shader = null;
|
|
||||||
if (layer.Color == component.Color) // If color isn't the same as one in component we don't want to change it.
|
|
||||||
layer.Color = component.BeforeColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -114,9 +114,16 @@ public partial class NavMapControl : MapGridControl
|
|||||||
VerticalExpand = false,
|
VerticalExpand = false,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
_zoom,
|
new BoxContainer()
|
||||||
_beacons,
|
{
|
||||||
_recenter,
|
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
_zoom,
|
||||||
|
_beacons,
|
||||||
|
_recenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -188,10 +188,13 @@ namespace Content.Client.Popups
|
|||||||
PopupEntity(message, uid, type);
|
PopupEntity(message, uid, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
|
public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||||
{
|
{
|
||||||
|
if (recipient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (_timing.IsFirstTimePredicted)
|
if (_timing.IsFirstTimePredicted)
|
||||||
PopupEntity(message, uid, recipient, type);
|
PopupEntity(message, uid, recipient.Value, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)
|
public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)
|
||||||
|
|||||||
122
Content.Client/RCD/AlignRCDConstruction.cs
Normal file
122
Content.Client/RCD/AlignRCDConstruction.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Client.Gameplay;
|
||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.RCD.Components;
|
||||||
|
using Content.Shared.RCD.Systems;
|
||||||
|
using Robust.Client.Placement;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.State;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.RCD;
|
||||||
|
|
||||||
|
public sealed class AlignRCDConstruction : PlacementMode
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||||
|
[Dependency] private readonly RCDSystem _rcdSystem = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||||
|
|
||||||
|
private const float SearchBoxSize = 2f;
|
||||||
|
private const float PlaceColorBaseAlpha = 0.5f;
|
||||||
|
|
||||||
|
private EntityCoordinates _unalignedMouseCoords = default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This placement mode is not on the engine because it is content specific (i.e., for the RCD)
|
||||||
|
/// </summary>
|
||||||
|
public AlignRCDConstruction(PlacementManager pMan) : base(pMan)
|
||||||
|
{
|
||||||
|
var dependencies = IoCManager.Instance!;
|
||||||
|
_entityManager = dependencies.Resolve<IEntityManager>();
|
||||||
|
_mapManager = dependencies.Resolve<IMapManager>();
|
||||||
|
_playerManager = dependencies.Resolve<IPlayerManager>();
|
||||||
|
_stateManager = dependencies.Resolve<IStateManager>();
|
||||||
|
|
||||||
|
_mapSystem = _entityManager.System<SharedMapSystem>();
|
||||||
|
_rcdSystem = _entityManager.System<RCDSystem>();
|
||||||
|
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||||
|
|
||||||
|
ValidPlaceColor = ValidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||||
|
{
|
||||||
|
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
|
||||||
|
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
|
||||||
|
|
||||||
|
var gridId = MouseCoords.GetGridUid(_entityManager);
|
||||||
|
|
||||||
|
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
|
||||||
|
|
||||||
|
float tileSize = mapGrid.TileSize;
|
||||||
|
GridDistancing = tileSize;
|
||||||
|
|
||||||
|
if (pManager.CurrentPermission!.IsTile)
|
||||||
|
{
|
||||||
|
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2,
|
||||||
|
CurrentTile.Y + tileSize / 2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
|
||||||
|
CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsValidPosition(EntityCoordinates position)
|
||||||
|
{
|
||||||
|
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||||
|
|
||||||
|
// If the destination is out of interaction range, set the placer alpha to zero
|
||||||
|
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var xform))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!xform.Coordinates.InRange(_entityManager, _transformSystem, position, SharedInteractionSystem.InteractionRange))
|
||||||
|
{
|
||||||
|
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise restore the alpha value
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if player is carrying an RCD in their active hand
|
||||||
|
if (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||||
|
|
||||||
|
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Retrieve the map grid data for the position
|
||||||
|
if (!_rcdSystem.TryGetMapGridData(position, out var mapGridData))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Determine if the user is hovering over a target
|
||||||
|
var currentState = _stateManager.CurrentState;
|
||||||
|
|
||||||
|
if (currentState is not GameplayStateBase screen)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var target = screen.GetClickedEntity(_unalignedMouseCoords.ToMap(_entityManager, _transformSystem));
|
||||||
|
|
||||||
|
// Determine if the RCD operation is valid or not
|
||||||
|
if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Content.Client/RCD/RCDConstructionGhostSystem.cs
Normal file
78
Content.Client/RCD/RCDConstructionGhostSystem.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Content.Shared.Hands.Components;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Content.Shared.RCD;
|
||||||
|
using Content.Shared.RCD.Components;
|
||||||
|
using Content.Shared.RCD.Systems;
|
||||||
|
using Robust.Client.Placement;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Content.Client.RCD;
|
||||||
|
|
||||||
|
public sealed class RCDConstructionGhostSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly RCDSystem _rcdSystem = default!;
|
||||||
|
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||||
|
|
||||||
|
private string _placementMode = typeof(AlignRCDConstruction).Name;
|
||||||
|
private Direction _placementDirection = default;
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
// Get current placer data
|
||||||
|
var placerEntity = _placementManager.CurrentPermission?.MobUid;
|
||||||
|
var placerProto = _placementManager.CurrentPermission?.EntityType;
|
||||||
|
var placerIsRCD = HasComp<RCDComponent>(placerEntity);
|
||||||
|
|
||||||
|
// Exit if erasing or the current placer is not an RCD (build mode is active)
|
||||||
|
if (_placementManager.Eraser || (placerEntity != null && !placerIsRCD))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Determine if player is carrying an RCD in their active hand
|
||||||
|
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||||
|
|
||||||
|
if (!TryComp<HandsComponent>(player, out var hands))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||||
|
|
||||||
|
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
|
||||||
|
{
|
||||||
|
// If the player was holding an RCD, but is no longer, cancel placement
|
||||||
|
if (placerIsRCD)
|
||||||
|
_placementManager.Clear();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the direction the RCD prototype based on the placer direction
|
||||||
|
if (_placementDirection != _placementManager.Direction)
|
||||||
|
{
|
||||||
|
_placementDirection = _placementManager.Direction;
|
||||||
|
RaiseNetworkEvent(new RCDConstructionGhostRotationEvent(GetNetEntity(heldEntity.Value), _placementDirection));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the placer has not changed, exit
|
||||||
|
_rcdSystem.UpdateCachedPrototype(heldEntity.Value, rcd);
|
||||||
|
|
||||||
|
if (heldEntity == placerEntity && rcd.CachedPrototype.Prototype == placerProto)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Create a new placer
|
||||||
|
var newObjInfo = new PlacementInformation
|
||||||
|
{
|
||||||
|
MobUid = heldEntity.Value,
|
||||||
|
PlacementOption = _placementMode,
|
||||||
|
EntityType = rcd.CachedPrototype.Prototype,
|
||||||
|
Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange),
|
||||||
|
IsTile = (rcd.CachedPrototype.Mode == RcdMode.ConstructTile),
|
||||||
|
UseEditorContext = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
_placementManager.Clear();
|
||||||
|
_placementManager.BeginPlacing(newObjInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Content.Client/RCD/RCDMenu.xaml
Normal file
47
Content.Client/RCD/RCDMenu.xaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
xmlns:rcd="clr-namespace:Content.Client.RCD"
|
||||||
|
BackButtonStyleClass="RadialMenuBackButton"
|
||||||
|
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||||
|
VerticalExpand="True"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
MinSize="450 450">
|
||||||
|
|
||||||
|
<!-- Note: The min size of the window just determine how close to the edge of the screen the center of the radial menu can be placed -->
|
||||||
|
<!-- The radial menu will try to open so that its center is located where the player's cursor is currently -->
|
||||||
|
|
||||||
|
<!-- Entry layer (shows main categories) -->
|
||||||
|
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
|
||||||
|
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False">
|
||||||
|
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/walls_and_flooring.png"/>
|
||||||
|
</ui:RadialMenuTextureButton>
|
||||||
|
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False">
|
||||||
|
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/windows_and_grilles.png"/>
|
||||||
|
</ui:RadialMenuTextureButton>
|
||||||
|
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False">
|
||||||
|
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/airlocks.png"/>
|
||||||
|
</ui:RadialMenuTextureButton>
|
||||||
|
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False">
|
||||||
|
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/multicoil.png"/>
|
||||||
|
</ui:RadialMenuTextureButton>
|
||||||
|
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False">
|
||||||
|
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/lighting.png"/>
|
||||||
|
</ui:RadialMenuTextureButton>
|
||||||
|
</ui:RadialContainer>
|
||||||
|
|
||||||
|
<!-- Walls and flooring -->
|
||||||
|
<ui:RadialContainer Name="WallsAndFlooring" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||||
|
|
||||||
|
<!-- Windows and grilles -->
|
||||||
|
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||||
|
|
||||||
|
<!-- Airlocks -->
|
||||||
|
<ui:RadialContainer Name="Airlocks" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||||
|
|
||||||
|
<!-- Computer and machine frames -->
|
||||||
|
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||||
|
|
||||||
|
<!-- Lighting -->
|
||||||
|
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||||
|
|
||||||
|
</ui:RadialMenu>
|
||||||
174
Content.Client/RCD/RCDMenu.xaml.cs
Normal file
174
Content.Client/RCD/RCDMenu.xaml.cs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.RCD;
|
||||||
|
using Content.Shared.RCD.Components;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Content.Client.RCD;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class RCDMenu : RadialMenu
|
||||||
|
{
|
||||||
|
[Dependency] private readonly EntityManager _entManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
private readonly SharedPopupSystem _popup;
|
||||||
|
|
||||||
|
public event Action<ProtoId<RCDPrototype>>? SendRCDSystemMessageAction;
|
||||||
|
|
||||||
|
private EntityUid _owner;
|
||||||
|
|
||||||
|
public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||||
|
_popup = _entManager.System<SharedPopupSystem>();
|
||||||
|
|
||||||
|
_owner = owner;
|
||||||
|
|
||||||
|
// Find the main radial container
|
||||||
|
var main = FindControl<RadialContainer>("Main");
|
||||||
|
|
||||||
|
if (main == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Populate secondary radial containers
|
||||||
|
if (!_entManager.TryGetComponent<RCDComponent>(owner, out var rcd))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var protoId in rcd.AvailablePrototypes)
|
||||||
|
{
|
||||||
|
if (!_protoManager.TryIndex(protoId, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (proto.Mode == RcdMode.Invalid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var parent = FindControl<RadialContainer>(proto.Category);
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tooltip = Loc.GetString(proto.SetName);
|
||||||
|
|
||||||
|
if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) &&
|
||||||
|
proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto))
|
||||||
|
{
|
||||||
|
tooltip = Loc.GetString(entProto.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1);
|
||||||
|
|
||||||
|
var button = new RCDMenuButton()
|
||||||
|
{
|
||||||
|
StyleClasses = { "RadialMenuButton" },
|
||||||
|
SetSize = new Vector2(64f, 64f),
|
||||||
|
ToolTip = tooltip,
|
||||||
|
ProtoId = protoId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (proto.Sprite != null)
|
||||||
|
{
|
||||||
|
var tex = new TextureRect()
|
||||||
|
{
|
||||||
|
VerticalAlignment = VAlignment.Center,
|
||||||
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
Texture = _spriteSystem.Frame0(proto.Sprite),
|
||||||
|
TextureScale = new Vector2(2f, 2f),
|
||||||
|
};
|
||||||
|
|
||||||
|
button.AddChild(tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddChild(button);
|
||||||
|
|
||||||
|
// Ensure that the button that transitions the menu to the associated category layer
|
||||||
|
// is visible in the main radial container (as these all start with Visible = false)
|
||||||
|
foreach (var child in main.Children)
|
||||||
|
{
|
||||||
|
var castChild = child as RadialMenuTextureButton;
|
||||||
|
|
||||||
|
if (castChild is not RadialMenuTextureButton)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (castChild.TargetLayer == proto.Category)
|
||||||
|
{
|
||||||
|
castChild.Visible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up menu actions
|
||||||
|
foreach (var child in Children)
|
||||||
|
AddRCDMenuButtonOnClickActions(child);
|
||||||
|
|
||||||
|
OnChildAdded += AddRCDMenuButtonOnClickActions;
|
||||||
|
|
||||||
|
SendRCDSystemMessageAction += bui.SendRCDSystemMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddRCDMenuButtonOnClickActions(Control control)
|
||||||
|
{
|
||||||
|
var radialContainer = control as RadialContainer;
|
||||||
|
|
||||||
|
if (radialContainer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var child in radialContainer.Children)
|
||||||
|
{
|
||||||
|
var castChild = child as RCDMenuButton;
|
||||||
|
|
||||||
|
if (castChild == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
castChild.OnButtonUp += _ =>
|
||||||
|
{
|
||||||
|
SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
|
||||||
|
|
||||||
|
if (_playerManager.LocalSession?.AttachedEntity != null &&
|
||||||
|
_protoManager.TryIndex(castChild.ProtoId, out var proto))
|
||||||
|
{
|
||||||
|
var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
|
||||||
|
|
||||||
|
if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject)
|
||||||
|
{
|
||||||
|
var name = Loc.GetString(proto.SetName);
|
||||||
|
|
||||||
|
if (proto.Prototype != null &&
|
||||||
|
_protoManager.TryIndex(proto.Prototype, out var entProto))
|
||||||
|
name = entProto.Name;
|
||||||
|
|
||||||
|
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popup message
|
||||||
|
_popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RCDMenuButton : RadialMenuTextureButton
|
||||||
|
{
|
||||||
|
public ProtoId<RCDPrototype> ProtoId { get; set; }
|
||||||
|
|
||||||
|
public RCDMenuButton()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Content.Client/RCD/RCDMenuBoundUserInterface.cs
Normal file
49
Content.Client/RCD/RCDMenuBoundUserInterface.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Shared.RCD;
|
||||||
|
using Content.Shared.RCD.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Input;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.RCD;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IClyde _displayManager = default!;
|
||||||
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||||
|
|
||||||
|
private RCDMenu? _menu;
|
||||||
|
|
||||||
|
public RCDMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_menu = new(Owner, this);
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
|
||||||
|
// Open the menu, centered on the mouse
|
||||||
|
var vpSize = _displayManager.ScreenSize;
|
||||||
|
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendRCDSystemMessage(ProtoId<RCDPrototype> protoId)
|
||||||
|
{
|
||||||
|
// A predicted message cannot be used here as the RCD UI is closed immediately
|
||||||
|
// after this message is sent, which will stop the server from receiving it
|
||||||
|
SendMessage(new RCDSystemMessage(protoId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing) return;
|
||||||
|
|
||||||
|
_menu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -263,9 +263,10 @@ public sealed partial class MapScreen : BoxContainer
|
|||||||
|
|
||||||
while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata))
|
while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata))
|
||||||
{
|
{
|
||||||
if (!_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId))
|
if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
|
||||||
continue;
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var mapName = mapMetadata.EntityName;
|
var mapName = mapMetadata.EntityName;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(mapName))
|
if (string.IsNullOrEmpty(mapName))
|
||||||
@@ -310,7 +311,6 @@ public sealed partial class MapScreen : BoxContainer
|
|||||||
};
|
};
|
||||||
|
|
||||||
_mapHeadings.Add(mapComp.MapId, gridContents);
|
_mapHeadings.Add(mapComp.MapId, gridContents);
|
||||||
|
|
||||||
foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
|
foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
|
||||||
{
|
{
|
||||||
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
|
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
|
||||||
@@ -327,8 +327,8 @@ public sealed partial class MapScreen : BoxContainer
|
|||||||
{
|
{
|
||||||
AddMapObject(mapComp.MapId, gridObj);
|
AddMapObject(mapComp.MapId, gridObj);
|
||||||
}
|
}
|
||||||
else if (iffComp == null ||
|
else if (!_shuttles.IsBeaconMap(_mapManager.GetMapEntityId(mapComp.MapId)) && (iffComp == null ||
|
||||||
(iffComp.Flags & IFFFlags.Hide) == 0x0)
|
(iffComp.Flags & IFFFlags.Hide) == 0x0))
|
||||||
{
|
{
|
||||||
_pendingMapObjects.Add((mapComp.MapId, gridObj));
|
_pendingMapObjects.Add((mapComp.MapId, gridObj));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Robust.Client.GameObjects;
|
|||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Client.StatusIcon;
|
namespace Content.Client.StatusIcon;
|
||||||
@@ -11,11 +12,13 @@ namespace Content.Client.StatusIcon;
|
|||||||
public sealed class StatusIconOverlay : Overlay
|
public sealed class StatusIconOverlay : Overlay
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entity = default!;
|
[Dependency] private readonly IEntityManager _entity = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
|
||||||
private readonly SpriteSystem _sprite;
|
private readonly SpriteSystem _sprite;
|
||||||
private readonly TransformSystem _transform;
|
private readonly TransformSystem _transform;
|
||||||
private readonly StatusIconSystem _statusIcon;
|
private readonly StatusIconSystem _statusIcon;
|
||||||
|
private readonly ShaderInstance _unshadedShader;
|
||||||
|
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ public sealed class StatusIconOverlay : Overlay
|
|||||||
_sprite = _entity.System<SpriteSystem>();
|
_sprite = _entity.System<SpriteSystem>();
|
||||||
_transform = _entity.System<TransformSystem>();
|
_transform = _entity.System<TransformSystem>();
|
||||||
_statusIcon = _entity.System<StatusIconSystem>();
|
_statusIcon = _entity.System<StatusIconSystem>();
|
||||||
|
_unshadedShader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
@@ -103,9 +107,16 @@ public sealed class StatusIconOverlay : Overlay
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proto.IsShaded)
|
||||||
|
handle.UseShader(null);
|
||||||
|
else
|
||||||
|
handle.UseShader(_unshadedShader);
|
||||||
|
|
||||||
var position = new Vector2(xOffset, yOffset);
|
var position = new Vector2(xOffset, yOffset);
|
||||||
handle.DrawTexture(texture, position);
|
handle.DrawTexture(texture, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle.UseShader(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
using Content.Shared.Store;
|
using Content.Shared.Store;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using Robust.Shared.Prototypes;
|
||||||
using Serilog;
|
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
|
||||||
|
|
||||||
namespace Content.Client.Store.Ui;
|
namespace Content.Client.Store.Ui;
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class StoreBoundUserInterface : BoundUserInterface
|
public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
|
private IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private StoreMenu? _menu;
|
private StoreMenu? _menu;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private string _windowName = Loc.GetString("store-ui-default-title");
|
private string _windowName = Loc.GetString("store-ui-default-title");
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private string _search = "";
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
private HashSet<ListingData> _listings = new();
|
||||||
|
|
||||||
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -49,6 +54,12 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_menu.SearchTextUpdated += (_, search) =>
|
||||||
|
{
|
||||||
|
_search = search.Trim().ToLowerInvariant();
|
||||||
|
UpdateListingsWithSearchFilter();
|
||||||
|
};
|
||||||
|
|
||||||
_menu.OnRefundAttempt += (_) =>
|
_menu.OnRefundAttempt += (_) =>
|
||||||
{
|
{
|
||||||
SendMessage(new StoreRequestRefundMessage());
|
SendMessage(new StoreRequestRefundMessage());
|
||||||
@@ -64,10 +75,10 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case StoreUpdateState msg:
|
case StoreUpdateState msg:
|
||||||
_menu.UpdateBalance(msg.Balance);
|
_listings = msg.Listings;
|
||||||
_menu.PopulateStoreCategoryButtons(msg.Listings);
|
|
||||||
|
|
||||||
_menu.UpdateListing(msg.Listings.ToList());
|
_menu.UpdateBalance(msg.Balance);
|
||||||
|
UpdateListingsWithSearchFilter();
|
||||||
_menu.SetFooterVisibility(msg.ShowFooter);
|
_menu.SetFooterVisibility(msg.ShowFooter);
|
||||||
_menu.UpdateRefund(msg.AllowRefund);
|
_menu.UpdateRefund(msg.AllowRefund);
|
||||||
break;
|
break;
|
||||||
@@ -89,4 +100,19 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||||||
_menu?.Close();
|
_menu?.Close();
|
||||||
_menu?.Dispose();
|
_menu?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateListingsWithSearchFilter()
|
||||||
|
{
|
||||||
|
if (_menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var filteredListings = new HashSet<ListingData>(_listings);
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
|
||||||
|
!ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search));
|
||||||
|
}
|
||||||
|
_menu.PopulateStoreCategoryButtons(filteredListings);
|
||||||
|
_menu.UpdateListing(filteredListings.ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Text="Refund" />
|
Text="Refund" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<PanelContainer VerticalExpand="True">
|
<LineEdit Name="SearchBar" Margin="4" PlaceHolder="Search" HorizontalExpand="True"/>
|
||||||
|
<PanelContainer VerticalExpand="True">
|
||||||
<PanelContainer.PanelOverride>
|
<PanelContainer.PanelOverride>
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using Content.Client.Actions;
|
using Content.Client.Actions;
|
||||||
using Content.Client.GameTicking.Managers;
|
using Content.Client.GameTicking.Managers;
|
||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
@@ -27,6 +26,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
|
|
||||||
private StoreWithdrawWindow? _withdrawWindow;
|
private StoreWithdrawWindow? _withdrawWindow;
|
||||||
|
|
||||||
|
public event EventHandler<string>? SearchTextUpdated;
|
||||||
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||||
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||||
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||||
@@ -46,6 +46,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
||||||
RefreshButton.OnButtonDown += OnRefreshButtonDown;
|
RefreshButton.OnButtonDown += OnRefreshButtonDown;
|
||||||
RefundButton.OnButtonDown += OnRefundButtonDown;
|
RefundButton.OnButtonDown += OnRefundButtonDown;
|
||||||
|
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
|
||||||
|
|
||||||
if (Window != null)
|
if (Window != null)
|
||||||
Window.Title = name;
|
Window.Title = name;
|
||||||
@@ -59,7 +60,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
||||||
|
|
||||||
var balanceStr = string.Empty;
|
var balanceStr = string.Empty;
|
||||||
foreach (var ((type, amount),proto) in currency)
|
foreach (var ((_, amount), proto) in currency)
|
||||||
{
|
{
|
||||||
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
|
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
|
||||||
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
|
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
|
||||||
@@ -81,7 +82,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
{
|
{
|
||||||
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||||
|
|
||||||
|
|
||||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||||
// maybe read clients prototypes instead?
|
// maybe read clients prototypes instead?
|
||||||
ClearListings();
|
ClearListings();
|
||||||
@@ -129,8 +129,8 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
if (!listing.Categories.Contains(CurrentCategory))
|
if (!listing.Categories.Contains(CurrentCategory))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var listingName = Loc.GetString(listing.Name);
|
var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
|
||||||
var listingDesc = Loc.GetString(listing.Description);
|
var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
|
||||||
var listingPrice = listing.Cost;
|
var listingPrice = listing.Cost;
|
||||||
var canBuy = CanBuyListing(Balance, listingPrice);
|
var canBuy = CanBuyListing(Balance, listingPrice);
|
||||||
|
|
||||||
@@ -144,12 +144,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
{
|
{
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
|
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
|
||||||
|
|
||||||
var proto = _prototypeManager.Index<EntityPrototype>(listing.ProductEntity);
|
|
||||||
if (listingName == string.Empty)
|
|
||||||
listingName = proto.Name;
|
|
||||||
if (listingDesc == string.Empty)
|
|
||||||
listingDesc = proto.Description;
|
|
||||||
}
|
}
|
||||||
else if (listing.ProductAction != null)
|
else if (listing.ProductAction != null)
|
||||||
{
|
{
|
||||||
@@ -254,13 +248,16 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||||||
|
|
||||||
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
|
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
|
||||||
|
|
||||||
|
// This will reset the Current Category selection if nothing matches the search.
|
||||||
|
if (allCategories.All(category => category.ID != CurrentCategory))
|
||||||
|
CurrentCategory = string.Empty;
|
||||||
|
|
||||||
if (CurrentCategory == string.Empty && allCategories.Count > 0)
|
if (CurrentCategory == string.Empty && allCategories.Count > 0)
|
||||||
CurrentCategory = allCategories.First().ID;
|
CurrentCategory = allCategories.First().ID;
|
||||||
|
|
||||||
if (allCategories.Count <= 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CategoryListContainer.Children.Clear();
|
CategoryListContainer.Children.Clear();
|
||||||
|
if (allCategories.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (var proto in allCategories)
|
foreach (var proto in allCategories)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ namespace Content.Client.Stylesheets
|
|||||||
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
|
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
|
||||||
public const string StyleClassInventorySlotBackground = "InventorySlotBackground";
|
public const string StyleClassInventorySlotBackground = "InventorySlotBackground";
|
||||||
public const string StyleClassHandSlotHighlight = "HandSlotHighlight";
|
public const string StyleClassHandSlotHighlight = "HandSlotHighlight";
|
||||||
|
public const string StyleClassChatPanel = "ChatPanel";
|
||||||
public const string StyleClassChatSubPanel = "ChatSubPanel";
|
public const string StyleClassChatSubPanel = "ChatSubPanel";
|
||||||
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
|
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
|
||||||
public const string StyleClassHotbarPanel = "HotbarPanel";
|
public const string StyleClassHotbarPanel = "HotbarPanel";
|
||||||
@@ -158,6 +159,8 @@ namespace Content.Client.Stylesheets
|
|||||||
public const string StyleClassButtonColorGreen = "ButtonColorGreen";
|
public const string StyleClassButtonColorGreen = "ButtonColorGreen";
|
||||||
public const string StyleClassButtonColorPurple = "ButtonColorPurple";
|
public const string StyleClassButtonColorPurple = "ButtonColorPurple";
|
||||||
|
|
||||||
|
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
|
||||||
|
|
||||||
public override Stylesheet Stylesheet { get; }
|
public override Stylesheet Stylesheet { get; }
|
||||||
|
|
||||||
public StyleNano(IResourceCache resCache) : base(resCache)
|
public StyleNano(IResourceCache resCache) : base(resCache)
|
||||||
@@ -354,7 +357,7 @@ namespace Content.Client.Stylesheets
|
|||||||
var buttonTex = resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
var buttonTex = resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||||
var topButtonBase = new StyleBoxTexture
|
var topButtonBase = new StyleBoxTexture
|
||||||
{
|
{
|
||||||
Texture = buttonTex,
|
Texture = buttonTex,
|
||||||
};
|
};
|
||||||
topButtonBase.SetPatchMargin(StyleBox.Margin.All, 10);
|
topButtonBase.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||||
topButtonBase.SetPadding(StyleBox.Margin.All, 0);
|
topButtonBase.SetPadding(StyleBox.Margin.All, 0);
|
||||||
@@ -362,19 +365,19 @@ namespace Content.Client.Stylesheets
|
|||||||
|
|
||||||
var topButtonOpenRight = new StyleBoxTexture(topButtonBase)
|
var topButtonOpenRight = new StyleBoxTexture(topButtonBase)
|
||||||
{
|
{
|
||||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(0, 0), new Vector2(14, 24))),
|
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(0, 0), new Vector2(14, 24))),
|
||||||
};
|
};
|
||||||
topButtonOpenRight.SetPatchMargin(StyleBox.Margin.Right, 0);
|
topButtonOpenRight.SetPatchMargin(StyleBox.Margin.Right, 0);
|
||||||
|
|
||||||
var topButtonOpenLeft = new StyleBoxTexture(topButtonBase)
|
var topButtonOpenLeft = new StyleBoxTexture(topButtonBase)
|
||||||
{
|
{
|
||||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(14, 24))),
|
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(14, 24))),
|
||||||
};
|
};
|
||||||
topButtonOpenLeft.SetPatchMargin(StyleBox.Margin.Left, 0);
|
topButtonOpenLeft.SetPatchMargin(StyleBox.Margin.Left, 0);
|
||||||
|
|
||||||
var topButtonSquare = new StyleBoxTexture(topButtonBase)
|
var topButtonSquare = new StyleBoxTexture(topButtonBase)
|
||||||
{
|
{
|
||||||
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(3, 24))),
|
Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(3, 24))),
|
||||||
};
|
};
|
||||||
topButtonSquare.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
|
topButtonSquare.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
|
||||||
|
|
||||||
@@ -410,12 +413,16 @@ namespace Content.Client.Stylesheets
|
|||||||
lineEdit.SetPatchMargin(StyleBox.Margin.All, 3);
|
lineEdit.SetPatchMargin(StyleBox.Margin.All, 3);
|
||||||
lineEdit.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
|
lineEdit.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
|
||||||
|
|
||||||
var chatSubBGTex = resCache.GetTexture("/Textures/Interface/Nano/chat_sub_background.png");
|
var chatBg = new StyleBoxFlat
|
||||||
var chatSubBG = new StyleBoxTexture
|
|
||||||
{
|
{
|
||||||
Texture = chatSubBGTex,
|
BackgroundColor = ChatBackgroundColor
|
||||||
};
|
};
|
||||||
chatSubBG.SetPatchMargin(StyleBox.Margin.All, 3);
|
|
||||||
|
var chatSubBg = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BackgroundColor = ChatBackgroundColor,
|
||||||
|
};
|
||||||
|
chatSubBg.SetContentMarginOverride(StyleBox.Margin.All, 3);
|
||||||
|
|
||||||
var actionSearchBoxTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_dark_thin_border.png");
|
var actionSearchBoxTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_dark_thin_border.png");
|
||||||
var actionSearchBox = new StyleBoxTexture
|
var actionSearchBox = new StyleBoxTexture
|
||||||
@@ -494,7 +501,7 @@ namespace Content.Client.Stylesheets
|
|||||||
|
|
||||||
// Placeholder
|
// Placeholder
|
||||||
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
|
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
|
||||||
var placeholder = new StyleBoxTexture {Texture = placeholderTexture};
|
var placeholder = new StyleBoxTexture { Texture = placeholderTexture };
|
||||||
placeholder.SetPatchMargin(StyleBox.Margin.All, 19);
|
placeholder.SetPatchMargin(StyleBox.Margin.All, 19);
|
||||||
placeholder.SetExpandMargin(StyleBox.Margin.All, -5);
|
placeholder.SetExpandMargin(StyleBox.Margin.All, -5);
|
||||||
placeholder.Mode = StyleBoxTexture.StretchMode.Tile;
|
placeholder.Mode = StyleBoxTexture.StretchMode.Tile;
|
||||||
@@ -508,7 +515,7 @@ namespace Content.Client.Stylesheets
|
|||||||
var itemListItemBackground = new StyleBoxFlat {BackgroundColor = new Color(15, 15, 15)};
|
var itemListItemBackground = new StyleBoxFlat {BackgroundColor = new Color(15, 15, 15)};
|
||||||
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||||
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||||
var itemListItemBackgroundTransparent = new StyleBoxFlat {BackgroundColor = Color.Transparent};
|
var itemListItemBackgroundTransparent = new StyleBoxFlat { BackgroundColor = Color.Transparent };
|
||||||
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||||
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||||
|
|
||||||
@@ -578,9 +585,9 @@ namespace Content.Client.Stylesheets
|
|||||||
sliderForeBox.SetPatchMargin(StyleBox.Margin.All, 13);
|
sliderForeBox.SetPatchMargin(StyleBox.Margin.All, 13);
|
||||||
sliderGrabBox.SetPatchMargin(StyleBox.Margin.All, 13);
|
sliderGrabBox.SetPatchMargin(StyleBox.Margin.All, 13);
|
||||||
|
|
||||||
var sliderFillGreen = new StyleBoxTexture(sliderFillBox) {Modulate = Color.LimeGreen};
|
var sliderFillGreen = new StyleBoxTexture(sliderFillBox) { Modulate = Color.LimeGreen };
|
||||||
var sliderFillRed = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Red};
|
var sliderFillRed = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Red };
|
||||||
var sliderFillBlue = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Blue};
|
var sliderFillBlue = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Blue };
|
||||||
var sliderFillWhite = new StyleBoxTexture(sliderFillBox) { Modulate = Color.White };
|
var sliderFillWhite = new StyleBoxTexture(sliderFillBox) { Modulate = Color.White };
|
||||||
|
|
||||||
var boxFont13 = resCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
var boxFont13 = resCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||||
@@ -967,6 +974,13 @@ namespace Content.Client.Stylesheets
|
|||||||
Element<TextEdit>().Pseudo(TextEdit.StylePseudoClassPlaceholder)
|
Element<TextEdit>().Pseudo(TextEdit.StylePseudoClassPlaceholder)
|
||||||
.Prop("font-color", Color.Gray),
|
.Prop("font-color", Color.Gray),
|
||||||
|
|
||||||
|
// chat subpanels (chat lineedit backing, popup backings)
|
||||||
|
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassChatPanel}, null, null),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new StyleProperty(PanelContainer.StylePropertyPanel, chatBg),
|
||||||
|
}),
|
||||||
|
|
||||||
// Chat lineedit - we don't actually draw a stylebox around the lineedit itself, we put it around the
|
// Chat lineedit - we don't actually draw a stylebox around the lineedit itself, we put it around the
|
||||||
// input + other buttons, so we must clear the default stylebox
|
// input + other buttons, so we must clear the default stylebox
|
||||||
new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassChatLineEdit}, null, null),
|
new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassChatLineEdit}, null, null),
|
||||||
@@ -975,13 +989,6 @@ namespace Content.Client.Stylesheets
|
|||||||
new StyleProperty(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
|
new StyleProperty(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// chat subpanels (chat lineedit backing, popup backings)
|
|
||||||
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassChatSubPanel}, null, null),
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new StyleProperty(PanelContainer.StylePropertyPanel, chatSubBG),
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Action searchbox lineedit
|
// Action searchbox lineedit
|
||||||
new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassActionSearchBox}, null, null),
|
new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassActionSearchBox}, null, null),
|
||||||
new[]
|
new[]
|
||||||
@@ -1622,6 +1629,25 @@ namespace Content.Client.Stylesheets
|
|||||||
Element<Label>().Class("Disabled")
|
Element<Label>().Class("Disabled")
|
||||||
.Prop("font-color", DisabledFore),
|
.Prop("font-color", DisabledFore),
|
||||||
|
|
||||||
|
// Radial menu buttons
|
||||||
|
Element<TextureButton>().Class("RadialMenuButton")
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/button_normal.png")),
|
||||||
|
Element<TextureButton>().Class("RadialMenuButton")
|
||||||
|
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/button_hover.png")),
|
||||||
|
|
||||||
|
Element<TextureButton>().Class("RadialMenuCloseButton")
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/close_normal.png")),
|
||||||
|
Element<TextureButton>().Class("RadialMenuCloseButton")
|
||||||
|
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/close_hover.png")),
|
||||||
|
|
||||||
|
Element<TextureButton>().Class("RadialMenuBackButton")
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/back_normal.png")),
|
||||||
|
Element<TextureButton>().Class("RadialMenuBackButton")
|
||||||
|
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||||
|
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/back_hover.png")),
|
||||||
|
|
||||||
//PDA - Backgrounds
|
//PDA - Backgrounds
|
||||||
Element<PanelContainer>().Class("PdaContentBackground")
|
Element<PanelContainer>().Class("PdaContentBackground")
|
||||||
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenBoth)
|
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenBoth)
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
|
|||||||
|
|
||||||
SubscribeLocalEvent<TextScreenVisualsComponent, ComponentInit>(OnInit);
|
SubscribeLocalEvent<TextScreenVisualsComponent, ComponentInit>(OnInit);
|
||||||
SubscribeLocalEvent<TextScreenTimerComponent, ComponentInit>(OnTimerInit);
|
SubscribeLocalEvent<TextScreenTimerComponent, ComponentInit>(OnTimerInit);
|
||||||
|
|
||||||
|
UpdatesOutsidePrediction = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInit(EntityUid uid, TextScreenVisualsComponent component, ComponentInit args)
|
private void OnInit(EntityUid uid, TextScreenVisualsComponent component, ComponentInit args)
|
||||||
@@ -110,17 +112,11 @@ public sealed class TextScreenSystem : VisualizerSystem<TextScreenVisualsCompone
|
|||||||
if (args.AppearanceData.TryGetValue(TextScreenVisuals.Color, out var color) && color is Color)
|
if (args.AppearanceData.TryGetValue(TextScreenVisuals.Color, out var color) && color is Color)
|
||||||
component.Color = (Color) color;
|
component.Color = (Color) color;
|
||||||
|
|
||||||
// DefaultText: broadcast updates from comms consoles
|
// DefaultText: fallback text e.g. broadcast updates from comms consoles
|
||||||
// ScreenText: the text accompanying shuttle timers e.g. "ETA"
|
|
||||||
if (args.AppearanceData.TryGetValue(TextScreenVisuals.DefaultText, out var newDefault) && newDefault is string)
|
if (args.AppearanceData.TryGetValue(TextScreenVisuals.DefaultText, out var newDefault) && newDefault is string)
|
||||||
{
|
component.Text = SegmentText((string) newDefault, component);
|
||||||
string?[] defaultText = SegmentText((string) newDefault, component);
|
|
||||||
component.Text = defaultText;
|
// ScreenText: currently rendered text e.g. the "ETA" accompanying shuttle timers
|
||||||
component.TextToDraw = defaultText;
|
|
||||||
ResetText(uid, component);
|
|
||||||
BuildTextLayers(uid, component, args.Sprite);
|
|
||||||
DrawLayers(uid, component.LayerStatesToDraw);
|
|
||||||
}
|
|
||||||
if (args.AppearanceData.TryGetValue(TextScreenVisuals.ScreenText, out var text) && text is string)
|
if (args.AppearanceData.TryGetValue(TextScreenVisuals.ScreenText, out var text) && text is string)
|
||||||
{
|
{
|
||||||
component.TextToDraw = SegmentText((string) text, component);
|
component.TextToDraw = SegmentText((string) text, component);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
using Content.Shared.Toilet;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Toilet;
|
|
||||||
|
|
||||||
public sealed class ToiletVisualsSystem : VisualizerSystem<ToiletComponent>
|
|
||||||
{
|
|
||||||
protected override void OnAppearanceChange(EntityUid uid, ToiletComponent component, ref AppearanceChangeEvent args)
|
|
||||||
{
|
|
||||||
if (args.Sprite == null) return;
|
|
||||||
|
|
||||||
AppearanceSystem.TryGetData<bool>(uid, ToiletVisuals.LidOpen, out var lidOpen, args.Component);
|
|
||||||
AppearanceSystem.TryGetData<bool>(uid, ToiletVisuals.SeatUp, out var seatUp, args.Component);
|
|
||||||
|
|
||||||
var state = (lidOpen, seatUp) switch
|
|
||||||
{
|
|
||||||
(false, false) => "closed_toilet_seat_down",
|
|
||||||
(false, true) => "closed_toilet_seat_up",
|
|
||||||
(true, false) => "open_toilet_seat_down",
|
|
||||||
(true, true) => "open_toilet_seat_up"
|
|
||||||
};
|
|
||||||
|
|
||||||
args.Sprite.LayerSetState(0, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
105
Content.Client/UserInterface/Controls/RadialContainer.cs
Normal file
105
Content.Client/UserInterface/Controls/RadialContainer.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Content.Client.UserInterface.Controls;
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
public class RadialContainer : LayoutContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the anglular range, in radians, in which child elements will be placed.
|
||||||
|
/// The first value denotes the angle at which the first element is to be placed, and
|
||||||
|
/// the second value denotes the angle at which the last element is to be placed.
|
||||||
|
/// Both values must be between 0 and 2 PI radians
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The top of the screen is at 0 radians, and the bottom of the screen is at PI radians
|
||||||
|
/// </remarks>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public Vector2 AngularRange
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _angularRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var x = value.X;
|
||||||
|
var y = value.Y;
|
||||||
|
|
||||||
|
x = x > MathF.Tau ? x % MathF.Tau : x;
|
||||||
|
y = y > MathF.Tau ? y % MathF.Tau : y;
|
||||||
|
|
||||||
|
x = x < 0 ? MathF.Tau + x : x;
|
||||||
|
y = y < 0 ? MathF.Tau + y : y;
|
||||||
|
|
||||||
|
_angularRange = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 _angularRange = new Vector2(0f, MathF.Tau - float.Epsilon);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the direction in which child elements will be arranged
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines how far from the radial container's center that its child elements will be placed
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float Radius { get; set; } = 100f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether the container should reserve a space on the layout for child which are not currently visible
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool ReserveSpaceForHiddenChildren { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This container arranges its children, evenly separated, in a radial pattern
|
||||||
|
/// </summary>
|
||||||
|
public RadialContainer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
|
||||||
|
var childCount = children.Count();
|
||||||
|
|
||||||
|
// Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements
|
||||||
|
var arc = AngularRange.Y - AngularRange.X;
|
||||||
|
arc = (arc < 0) ? MathF.Tau + arc : arc;
|
||||||
|
arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc;
|
||||||
|
|
||||||
|
// Account for both circular arrangements and arc-based arrangements
|
||||||
|
var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f) ? 0 : 1;
|
||||||
|
|
||||||
|
// Determine the separation between child elements
|
||||||
|
var sepAngle = arc / (childCount - childMod);
|
||||||
|
sepAngle *= (RadialAlignment == RAlignment.AntiClockwise) ? -1f : 1f;
|
||||||
|
|
||||||
|
// Adjust the positions of all the child elements
|
||||||
|
foreach (var (i, child) in children.Select((x, i) => (i, x)))
|
||||||
|
{
|
||||||
|
var position = new Vector2(Radius * MathF.Sin(AngularRange.X + sepAngle * i) + Width / 2f - child.Width / 2f, -Radius * MathF.Cos(AngularRange.X + sepAngle * i) + Height / 2f - child.Height / 2f);
|
||||||
|
SetPosition(child, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the different radial alignment modes
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="RadialAlignment"/>
|
||||||
|
public enum RAlignment : byte
|
||||||
|
{
|
||||||
|
Clockwise,
|
||||||
|
AntiClockwise,
|
||||||
|
}
|
||||||
|
}
|
||||||
255
Content.Client/UserInterface/Controls/RadialMenu.cs
Normal file
255
Content.Client/UserInterface/Controls/RadialMenu.cs
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Content.Client.UserInterface.Controls;
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
public class RadialMenu : BaseWindow
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contextual button used to traverse through previous layers of the radial menu
|
||||||
|
/// </summary>
|
||||||
|
public TextureButton? ContextualButton { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a style class to be applied to the contextual button when it is set to move the user back through previous layers of the radial menu
|
||||||
|
/// </summary>
|
||||||
|
public string? BackButtonStyleClass
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _backButtonStyleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_backButtonStyleClass = value;
|
||||||
|
|
||||||
|
if (_path.Count > 0 && ContextualButton != null && _backButtonStyleClass != null)
|
||||||
|
ContextualButton.SetOnlyStyleClass(_backButtonStyleClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a style class to be applied to the contextual button when it will close the radial menu
|
||||||
|
/// </summary>
|
||||||
|
public string? CloseButtonStyleClass
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _closeButtonStyleClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_closeButtonStyleClass = value;
|
||||||
|
|
||||||
|
if (_path.Count == 0 && ContextualButton != null && _closeButtonStyleClass != null)
|
||||||
|
ContextualButton.SetOnlyStyleClass(_closeButtonStyleClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Control> _path = new();
|
||||||
|
private string? _backButtonStyleClass;
|
||||||
|
private string? _closeButtonStyleClass;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A free floating menu which enables the quick display of one or more radial containers
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only one radial container is visible at a time (each container forming a separate 'layer' within
|
||||||
|
/// the menu), along with a contextual button at the menu center, which will either return the user
|
||||||
|
/// to the previous layer or close the menu if there are no previous layers left to traverse.
|
||||||
|
/// To create a functional radial menu, simply parent one or more named radial containers to it,
|
||||||
|
/// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these
|
||||||
|
/// buttons to the name of a radial conatiner will display the container in question to the user
|
||||||
|
/// whenever it is clicked in additon to any other actions assigned to the button
|
||||||
|
/// </remarks>
|
||||||
|
public RadialMenu()
|
||||||
|
{
|
||||||
|
// Hide all starting children (if any) except the first (this is the active layer)
|
||||||
|
if (ChildCount > 1)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < ChildCount; i++)
|
||||||
|
GetChild(i).Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto generate a contextual button for moving back through visited layers
|
||||||
|
ContextualButton = new TextureButton()
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HAlignment.Center,
|
||||||
|
VerticalAlignment = VAlignment.Center,
|
||||||
|
SetSize = new Vector2(64f, 64f),
|
||||||
|
};
|
||||||
|
|
||||||
|
ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer();
|
||||||
|
AddChild(ContextualButton);
|
||||||
|
|
||||||
|
// Hide any further add children, unless its promoted to the active layer
|
||||||
|
OnChildAdded += child => child.Visible = (GetCurrentActiveLayer() == child);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Control? GetCurrentActiveLayer()
|
||||||
|
{
|
||||||
|
var children = Children.Where(x => x != ContextualButton);
|
||||||
|
|
||||||
|
if (!children.Any())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return children.First(x => x.Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryToMoveToNewLayer(string newLayer)
|
||||||
|
{
|
||||||
|
if (newLayer == string.Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var currentLayer = GetCurrentActiveLayer();
|
||||||
|
|
||||||
|
if (currentLayer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
if (child == ContextualButton)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Hide layers which are not of interest
|
||||||
|
if (result == true || child.Name != newLayer)
|
||||||
|
{
|
||||||
|
child.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the layer of interest
|
||||||
|
else
|
||||||
|
{
|
||||||
|
child.Visible = true;
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the traversal path
|
||||||
|
if (result)
|
||||||
|
_path.Add(currentLayer);
|
||||||
|
|
||||||
|
// Set the style class of the button
|
||||||
|
if (_path.Count > 0 && ContextualButton != null && BackButtonStyleClass != null)
|
||||||
|
ContextualButton.SetOnlyStyleClass(BackButtonStyleClass);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReturnToPreviousLayer()
|
||||||
|
{
|
||||||
|
// Close the menu if the traversal path is empty
|
||||||
|
if (_path.Count == 0)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastChild = _path[^1];
|
||||||
|
|
||||||
|
// Hide all children except the contextual button
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
if (child != ContextualButton)
|
||||||
|
child.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the last visited layer visible, update the path list
|
||||||
|
lastChild.Visible = true;
|
||||||
|
_path.RemoveAt(_path.Count - 1);
|
||||||
|
|
||||||
|
// Set the style class of the button
|
||||||
|
if (_path.Count == 0 && ContextualButton != null && CloseButtonStyleClass != null)
|
||||||
|
ContextualButton.SetOnlyStyleClass(CloseButtonStyleClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
public class RadialMenuButton : Button
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Upon clicking this button the radial menu will transition to the named layer
|
||||||
|
/// </summary>
|
||||||
|
public string? TargetLayer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A simple button that can move the user to a different layer within a radial menu
|
||||||
|
/// </summary>
|
||||||
|
public RadialMenuButton()
|
||||||
|
{
|
||||||
|
OnButtonUp += OnClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClicked(ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (TargetLayer == null || TargetLayer == string.Empty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parent = FindParentMultiLayerContainer(this);
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
parent.TryToMoveToNewLayer(TargetLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RadialMenu? FindParentMultiLayerContainer(Control control)
|
||||||
|
{
|
||||||
|
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||||
|
{
|
||||||
|
if (ancestor is RadialMenu)
|
||||||
|
return ancestor as RadialMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Virtual]
|
||||||
|
public class RadialMenuTextureButton : TextureButton
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Upon clicking this button the radial menu will be moved to the named layer
|
||||||
|
/// </summary>
|
||||||
|
public string TargetLayer { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A simple texture button that can move the user to a different layer within a radial menu
|
||||||
|
/// </summary>
|
||||||
|
public RadialMenuTextureButton()
|
||||||
|
{
|
||||||
|
OnButtonUp += OnClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClicked(ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (TargetLayer == string.Empty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var parent = FindParentMultiLayerContainer(this);
|
||||||
|
|
||||||
|
if (parent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
parent.TryToMoveToNewLayer(TargetLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RadialMenu? FindParentMultiLayerContainer(Control control)
|
||||||
|
{
|
||||||
|
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||||
|
{
|
||||||
|
if (ancestor is RadialMenu)
|
||||||
|
return ancestor as RadialMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ using Content.Client.Chat.UI;
|
|||||||
using Content.Client.Examine;
|
using Content.Client.Examine;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
using Content.Client.Ghost;
|
using Content.Client.Ghost;
|
||||||
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.Screens;
|
using Content.Client.UserInterface.Screens;
|
||||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||||
using Content.Client.UserInterface.Systems.Gameplay;
|
using Content.Client.UserInterface.Systems.Gameplay;
|
||||||
@@ -56,7 +57,6 @@ public sealed class ChatUIController : UIController
|
|||||||
[Dependency] private readonly IStateManager _state = default!;
|
[Dependency] private readonly IStateManager _state = default!;
|
||||||
[Dependency] private readonly IGameTiming _timing = default!;
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
[Dependency] private readonly IEntityManager _entities = default!;
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
|
|
||||||
[UISystemDependency] private readonly ExamineSystem? _examine = default;
|
[UISystemDependency] private readonly ExamineSystem? _examine = default;
|
||||||
@@ -187,8 +187,8 @@ public sealed class ChatUIController : UIController
|
|||||||
_net.RegisterNetMessage<MsgChatMessage>(OnChatMessage);
|
_net.RegisterNetMessage<MsgChatMessage>(OnChatMessage);
|
||||||
_net.RegisterNetMessage<MsgDeleteChatMessagesBy>(OnDeleteChatMessagesBy);
|
_net.RegisterNetMessage<MsgDeleteChatMessagesBy>(OnDeleteChatMessagesBy);
|
||||||
SubscribeNetworkEvent<DamageForceSayEvent>(OnDamageForceSay);
|
SubscribeNetworkEvent<DamageForceSayEvent>(OnDamageForceSay);
|
||||||
_cfg.OnValueChanged(CCVars.ChatEnableColorName, (value) => { _chatNameColorsEnabled = value; });
|
_config.OnValueChanged(CCVars.ChatEnableColorName, (value) => { _chatNameColorsEnabled = value; });
|
||||||
_chatNameColorsEnabled = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
_chatNameColorsEnabled = _config.GetCVar(CCVars.ChatEnableColorName);
|
||||||
|
|
||||||
_speechBubbleRoot = new LayoutContainer();
|
_speechBubbleRoot = new LayoutContainer();
|
||||||
|
|
||||||
@@ -251,6 +251,9 @@ public sealed class ChatUIController : UIController
|
|||||||
{
|
{
|
||||||
_chatNameColors[i] = nameColors[i].ToHex();
|
_chatNameColors[i] = nameColors[i].ToHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_config.OnValueChanged(CCVars.ChatWindowOpacity, OnChatWindowOpacityChanged);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUpdateChangelingChat(ChangelingUserStart ev)
|
private void OnUpdateChangelingChat(ChangelingUserStart ev)
|
||||||
@@ -271,6 +274,8 @@ public sealed class ChatUIController : UIController
|
|||||||
|
|
||||||
var viewportContainer = UIManager.ActiveScreen!.FindControl<LayoutContainer>("ViewportContainer");
|
var viewportContainer = UIManager.ActiveScreen!.FindControl<LayoutContainer>("ViewportContainer");
|
||||||
SetSpeechBubbleRoot(viewportContainer);
|
SetSpeechBubbleRoot(viewportContainer);
|
||||||
|
|
||||||
|
SetChatWindowOpacity(_config.GetCVar(CCVars.ChatWindowOpacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnScreenUnload()
|
public void OnScreenUnload()
|
||||||
@@ -278,6 +283,34 @@ public sealed class ChatUIController : UIController
|
|||||||
SetMainChat(false);
|
SetMainChat(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnChatWindowOpacityChanged(float opacity)
|
||||||
|
{
|
||||||
|
SetChatWindowOpacity(opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetChatWindowOpacity(float opacity)
|
||||||
|
{
|
||||||
|
var chatBox = UIManager.ActiveScreen?.GetWidget<ChatBox>() ?? UIManager.ActiveScreen?.GetWidget<ResizableChatBox>();
|
||||||
|
|
||||||
|
var panel = chatBox?.ChatWindowPanel;
|
||||||
|
if (panel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Color color;
|
||||||
|
if (panel.PanelOverride is StyleBoxFlat styleBoxFlat)
|
||||||
|
color = styleBoxFlat.BackgroundColor;
|
||||||
|
else if (panel.TryGetStyleProperty<StyleBox>(PanelContainer.StylePropertyPanel, out var style)
|
||||||
|
&& style is StyleBoxFlat propStyleBoxFlat)
|
||||||
|
color = propStyleBoxFlat.BackgroundColor;
|
||||||
|
else
|
||||||
|
color = StyleNano.ChatBackgroundColor;
|
||||||
|
|
||||||
|
panel.PanelOverride = new StyleBoxFlat
|
||||||
|
{
|
||||||
|
BackgroundColor = color.WithAlpha(opacity)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public void SetMainChat(bool setting)
|
public void SetMainChat(bool setting)
|
||||||
{
|
{
|
||||||
if (UIManager.ActiveScreen == null)
|
if (UIManager.ActiveScreen == null)
|
||||||
@@ -823,7 +856,7 @@ public sealed class ChatUIController : UIController
|
|||||||
ProcessChatMessage(msg);
|
ProcessChatMessage(msg);
|
||||||
|
|
||||||
if ((msg.Channel & ChatChannel.AdminRelated) == 0 ||
|
if ((msg.Channel & ChatChannel.AdminRelated) == 0 ||
|
||||||
_cfg.GetCVar(CCVars.ReplayRecordAdminChat))
|
_config.GetCVar(CCVars.ReplayRecordAdminChat))
|
||||||
{
|
{
|
||||||
_replayRecording.RecordClientMessage(msg);
|
_replayRecording.RecordClientMessage(msg);
|
||||||
}
|
}
|
||||||
@@ -882,7 +915,7 @@ public sealed class ChatUIController : UIController
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ChatChannel.LOOC:
|
case ChatChannel.LOOC:
|
||||||
if (_cfg.GetCVar(CCVars.LoocAboveHeadShow))
|
if (_config.GetCVar(CCVars.LoocAboveHeadShow))
|
||||||
AddSpeechBubble(msg, SpeechBubble.SpeechType.Looc);
|
AddSpeechBubble(msg, SpeechBubble.SpeechType.Looc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.Chat;
|
using Content.Client.Stylesheets;
|
||||||
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ public class ChatInputBox : PanelContainer
|
|||||||
StyleClasses = {"chatFilterOptionButton"}
|
StyleClasses = {"chatFilterOptionButton"}
|
||||||
};
|
};
|
||||||
Container.AddChild(FilterButton);
|
Container.AddChild(FilterButton);
|
||||||
|
AddStyleClass(StyleNano.StyleClassChatSubPanel);
|
||||||
ChannelSelector.OnChannelSelect += UpdateActiveChannel;
|
ChannelSelector.OnChannelSelect += UpdateActiveChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
VerticalExpand="True"
|
VerticalExpand="True"
|
||||||
MinSize="465 225">
|
MinSize="465 225">
|
||||||
<PanelContainer HorizontalExpand="True" VerticalExpand="True" StyleClasses="FuckyWuckyBackground" >
|
<PanelContainer Name="ChatWindowPanel" Access="Public" HorizontalExpand="True" VerticalExpand="True"
|
||||||
|
StyleClasses="StyleNano.StyleClassChatPanel">
|
||||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True" VerticalExpand="True">
|
<BoxContainer Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True" VerticalExpand="True">
|
||||||
<OutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" Margin="2 2 2 2" >
|
<OutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" Margin="2 2 2 2" >
|
||||||
<OutputPanel.StyleBoxOverride>
|
<OutputPanel.StyleBoxOverride>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<controls:ItemStatusPanel
|
<controls:ItemStatusPanel
|
||||||
xmlns="https://spacestation14.io"
|
xmlns="https://spacestation14.io"
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
|
||||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer Orientation="Vertical" SeparationOverride="0">
|
<BoxContainer Orientation="Vertical" SeparationOverride="0">
|
||||||
<BoxContainer Name="StatusContents" Orientation="Vertical"/>
|
<BoxContainer Name="StatusContents" Orientation="Vertical"/>
|
||||||
<Label Name="ItemNameLabel" ClipText="True" StyleClasses="ItemStatus"/>
|
<Label Name="ItemNameLabel" StyleClasses="ItemStatus"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
</controls:ItemStatusPanel>
|
</controls:ItemStatusPanel>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public sealed class ZombieSystem : EntitySystem
|
|||||||
args.Cancelled = true;
|
args.Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCanDisplayStatusIcons(EntityUid uid, InitialInfectedComponent component, CanDisplayStatusIconsEvent args)
|
private void OnCanDisplayStatusIcons(EntityUid uid, InitialInfectedComponent component, ref CanDisplayStatusIconsEvent args)
|
||||||
{
|
{
|
||||||
if (HasComp<InitialInfectedComponent>(args.User) && !HasComp<ZombieComponent>(args.User))
|
if (HasComp<InitialInfectedComponent>(args.User) && !HasComp<ZombieComponent>(args.User))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Access
|
|||||||
var reader = new AccessReaderComponent();
|
var reader = new AccessReaderComponent();
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "Bar" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Bar" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.True);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test deny
|
// test deny
|
||||||
@@ -67,58 +67,58 @@ namespace Content.IntegrationTests.Tests.Access
|
|||||||
reader.DenyTags.Add("A");
|
reader.DenyTags.Add("A");
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "Foo" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "Foo" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.True);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test one list
|
// test one list
|
||||||
reader = new AccessReaderComponent();
|
reader = new AccessReaderComponent();
|
||||||
reader.AccessLists.Add(new HashSet<string> { "A" });
|
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test one list - two items
|
// test one list - two items
|
||||||
reader = new AccessReaderComponent();
|
reader = new AccessReaderComponent();
|
||||||
reader.AccessLists.Add(new HashSet<string> { "A", "B" });
|
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A", "B" });
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test two list
|
// test two list
|
||||||
reader = new AccessReaderComponent();
|
reader = new AccessReaderComponent();
|
||||||
reader.AccessLists.Add(new HashSet<string> { "A" });
|
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||||
reader.AccessLists.Add(new HashSet<string> { "B", "C" });
|
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "B", "C" });
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "C", "B" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B", "A" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "C", "B", "A" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||||
});
|
});
|
||||||
|
|
||||||
// test deny list
|
// test deny list
|
||||||
reader = new AccessReaderComponent();
|
reader = new AccessReaderComponent();
|
||||||
reader.AccessLists.Add(new HashSet<string> { "A" });
|
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||||
reader.DenyTags.Add("B");
|
reader.DenyTags.Add("B");
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.False);
|
||||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
|
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await pair.CleanReturnAsync();
|
await pair.CleanReturnAsync();
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public sealed class CraftingTests : InteractionTest
|
|||||||
|
|
||||||
// Player's hands should be full of the remaining rods, except those dropped during the failed crafting attempt.
|
// 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.
|
// Spear and left over stacks should be on the floor.
|
||||||
await AssertEntityLookup((Rod, 2), (Cable, 8), (ShardGlass, 2), (Spear, 1));
|
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
|
// The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
|
||||||
@@ -100,7 +100,7 @@ public sealed class CraftingTests : InteractionTest
|
|||||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||||
Assert.That(rodStack, Has.Count.EqualTo(8));
|
Assert.That(rodStack, Has.Count.EqualTo(8));
|
||||||
Assert.That(wireStack, Has.Count.EqualTo(8));
|
Assert.That(wireStack, Has.Count.EqualTo(7));
|
||||||
|
|
||||||
await FindEntity(Spear, shouldSucceed: false);
|
await FindEntity(Spear, shouldSucceed: false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
|
using Content.Shared.Access;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Access.Systems;
|
using Content.Shared.Access.Systems;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
@@ -12,6 +13,7 @@ using Robust.Shared.Audio;
|
|||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using static Content.Shared.Access.Components.AccessOverriderComponent;
|
using static Content.Shared.Access.Components.AccessOverriderComponent;
|
||||||
|
|
||||||
namespace Content.Server.Access.Systems;
|
namespace Content.Server.Access.Systems;
|
||||||
@@ -26,6 +28,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -108,17 +111,20 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
var targetLabel = Loc.GetString("access-overrider-window-no-target");
|
var targetLabel = Loc.GetString("access-overrider-window-no-target");
|
||||||
var targetLabelColor = Color.Red;
|
var targetLabelColor = Color.Red;
|
||||||
|
|
||||||
string[]? possibleAccess = null;
|
ProtoId<AccessLevelPrototype>[]? possibleAccess = null;
|
||||||
string[]? currentAccess = null;
|
ProtoId<AccessLevelPrototype>[]? currentAccess = null;
|
||||||
string[]? missingAccess = null;
|
ProtoId<AccessLevelPrototype>[]? missingAccess = null;
|
||||||
|
|
||||||
if (component.TargetAccessReaderId is { Valid: true } accessReader)
|
if (component.TargetAccessReaderId is { Valid: true } accessReader)
|
||||||
{
|
{
|
||||||
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
|
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
|
||||||
targetLabelColor = Color.White;
|
targetLabelColor = Color.White;
|
||||||
|
|
||||||
List<HashSet<string>> currentAccessHashsets = EntityManager.GetComponent<AccessReaderComponent>(accessReader).AccessLists;
|
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderComponent))
|
||||||
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets)?.ToArray();
|
return;
|
||||||
|
|
||||||
|
var currentAccessHashsets = accessReaderComponent.AccessLists;
|
||||||
|
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.PrivilegedIdSlot.Item is { Valid: true } idCard)
|
if (component.PrivilegedIdSlot.Item is { Valid: true } idCard)
|
||||||
@@ -151,15 +157,15 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
_userInterface.TrySetUiState(uid, AccessOverriderUiKey.Key, newState);
|
_userInterface.TrySetUiState(uid, AccessOverriderUiKey.Key, newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> ConvertAccessHashSetsToList(List<HashSet<string>> accessHashsets)
|
private List<ProtoId<AccessLevelPrototype>> ConvertAccessHashSetsToList(List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets)
|
||||||
{
|
{
|
||||||
List<string> accessList = new List<string>();
|
List<ProtoId<AccessLevelPrototype>> accessList = new List<ProtoId<AccessLevelPrototype>>();
|
||||||
|
|
||||||
if (accessHashsets != null && accessHashsets.Any())
|
if (accessHashsets != null && accessHashsets.Any())
|
||||||
{
|
{
|
||||||
foreach (HashSet<string> hashSet in accessHashsets)
|
foreach (HashSet<ProtoId<AccessLevelPrototype>> hashSet in accessHashsets)
|
||||||
{
|
{
|
||||||
foreach (string hash in hashSet.ToArray())
|
foreach (ProtoId<AccessLevelPrototype> hash in hashSet.ToArray())
|
||||||
{
|
{
|
||||||
accessList.Add(hash);
|
accessList.Add(hash);
|
||||||
}
|
}
|
||||||
@@ -169,15 +175,15 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
return accessList;
|
return accessList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HashSet<string>> ConvertAccessListToHashSet(List<string> accessList)
|
private List<HashSet<ProtoId<AccessLevelPrototype>>> ConvertAccessListToHashSet(List<ProtoId<AccessLevelPrototype>> accessList)
|
||||||
{
|
{
|
||||||
List<HashSet<string>> accessHashsets = new List<HashSet<string>>();
|
List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets = new List<HashSet<ProtoId<AccessLevelPrototype>>>();
|
||||||
|
|
||||||
if (accessList != null && accessList.Any())
|
if (accessList != null && accessList.Any())
|
||||||
{
|
{
|
||||||
foreach (string access in accessList)
|
foreach (ProtoId<AccessLevelPrototype> access in accessList)
|
||||||
{
|
{
|
||||||
accessHashsets.Add(new HashSet<string>() { access });
|
accessHashsets.Add(new HashSet<ProtoId<AccessLevelPrototype>>() { access });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
/// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader.
|
/// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void TryWriteToTargetAccessReaderId(EntityUid uid,
|
private void TryWriteToTargetAccessReaderId(EntityUid uid,
|
||||||
List<string> newAccessList,
|
List<ProtoId<AccessLevelPrototype>> newAccessList,
|
||||||
EntityUid player,
|
EntityUid player,
|
||||||
AccessOverriderComponent? component = null)
|
AccessOverriderComponent? component = null)
|
||||||
{
|
{
|
||||||
@@ -211,9 +217,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TryComp(component.TargetAccessReaderId, out AccessReaderComponent? accessReader);
|
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReader))
|
||||||
|
|
||||||
if (accessReader == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldTags = ConvertAccessHashSetsToList(accessReader.AccessLists);
|
var oldTags = ConvertAccessHashSetsToList(accessReader.AccessLists);
|
||||||
@@ -262,10 +266,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
|||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent<AccessReaderComponent>(uid, out var reader))
|
if (_accessReader.GetMainAccessReader(uid, out var accessReader))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var privilegedId = component.PrivilegedIdSlot.Item;
|
var privilegedId = component.PrivilegedIdSlot.Item;
|
||||||
return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, reader);
|
return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, accessReader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Robust.Shared.Containers;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
using static Content.Shared.Access.Components.IdCardConsoleComponent;
|
||||||
|
using Content.Shared.Access;
|
||||||
|
|
||||||
namespace Content.Server.Access.Systems;
|
namespace Content.Server.Access.Systems;
|
||||||
|
|
||||||
@@ -55,11 +56,11 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var privilegedIdName = string.Empty;
|
var privilegedIdName = string.Empty;
|
||||||
string[]? possibleAccess = null;
|
List<ProtoId<AccessLevelPrototype>>? possibleAccess = null;
|
||||||
if (component.PrivilegedIdSlot.Item is { Valid: true } item)
|
if (component.PrivilegedIdSlot.Item is { Valid: true } item)
|
||||||
{
|
{
|
||||||
privilegedIdName = EntityManager.GetComponent<MetaDataComponent>(item).EntityName;
|
privilegedIdName = EntityManager.GetComponent<MetaDataComponent>(item).EntityName;
|
||||||
possibleAccess = _accessReader.FindAccessTags(item).ToArray();
|
possibleAccess = _accessReader.FindAccessTags(item).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
IdCardConsoleBoundUserInterfaceState newState;
|
IdCardConsoleBoundUserInterfaceState newState;
|
||||||
@@ -84,7 +85,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
var targetIdComponent = EntityManager.GetComponent<IdCardComponent>(targetId);
|
var targetIdComponent = EntityManager.GetComponent<IdCardComponent>(targetId);
|
||||||
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
|
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
|
||||||
|
|
||||||
var jobProto = string.Empty;
|
var jobProto = new ProtoId<AccessLevelPrototype>(string.Empty);
|
||||||
var jobIcon = targetIdComponent.JobIcon; //WD-EDIT
|
var jobIcon = targetIdComponent.JobIcon; //WD-EDIT
|
||||||
|
|
||||||
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|
||||||
@@ -101,7 +102,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
true,
|
true,
|
||||||
targetIdComponent.FullName,
|
targetIdComponent.FullName,
|
||||||
targetIdComponent.JobTitle,
|
targetIdComponent.JobTitle,
|
||||||
targetAccessComponent.Tags.ToArray(),
|
targetAccessComponent.Tags.ToList(),
|
||||||
possibleAccess,
|
possibleAccess,
|
||||||
jobProto,
|
jobProto,
|
||||||
privilegedIdName,
|
privilegedIdName,
|
||||||
@@ -122,8 +123,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
EntityUid uid,
|
EntityUid uid,
|
||||||
string newFullName,
|
string newFullName,
|
||||||
string newJobTitle,
|
string newJobTitle,
|
||||||
List<string> newAccessList,
|
List<ProtoId<AccessLevelPrototype>> newAccessList,
|
||||||
string newJobProto,
|
ProtoId<AccessLevelPrototype> newJobProto,
|
||||||
string? newJobIcon,
|
string? newJobIcon,
|
||||||
EntityUid player,
|
EntityUid player,
|
||||||
IdCardConsoleComponent? component = null)
|
IdCardConsoleComponent? component = null)
|
||||||
@@ -153,7 +154,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldTags = _access.TryGetTags(targetId) ?? new List<string>();
|
var oldTags = _access.TryGetTags(targetId) ?? new List<ProtoId<AccessLevelPrototype>>();
|
||||||
oldTags = oldTags.ToList();
|
oldTags = oldTags.ToList();
|
||||||
|
|
||||||
var privilegedId = component.PrivilegedIdSlot.Item;
|
var privilegedId = component.PrivilegedIdSlot.Item;
|
||||||
@@ -209,7 +210,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
|||||||
EntityUid uid,
|
EntityUid uid,
|
||||||
EntityUid targetId,
|
EntityUid targetId,
|
||||||
string newFullName,
|
string newFullName,
|
||||||
string newJobTitle,
|
ProtoId<AccessLevelPrototype> newJobTitle,
|
||||||
JobPrototype? newJobProto,
|
JobPrototype? newJobProto,
|
||||||
string? newJobIcon) // WD EDIT
|
string? newJobIcon) // WD EDIT
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,77 +1,123 @@
|
|||||||
using Content.Server.GameTicking;
|
using System.Linq;
|
||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Ghost;
|
||||||
|
using Content.Server.Mind;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Commands
|
namespace Content.Server.Administration.Commands;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Admin)]
|
||||||
|
public sealed class AGhost : LocalizedCommands
|
||||||
{
|
{
|
||||||
[AdminCommand(AdminFlags.Admin)]
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
public sealed class AGhost : IConsoleCommand
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
|
||||||
|
public override string Command => "aghost";
|
||||||
|
public override string Description => LocalizationManager.GetString("aghost-description");
|
||||||
|
public override string Help => "aghost";
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IEntityManager _entities = default!;
|
if (args.Length == 1)
|
||||||
|
|
||||||
public string Command => "aghost";
|
|
||||||
public string Description => "Makes you an admin ghost.";
|
|
||||||
public string Help => "aghost";
|
|
||||||
|
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
{
|
||||||
var player = shell.Player;
|
var names = _playerManager.Sessions.OrderBy(c => c.Name).Select(c => c.Name).ToArray();
|
||||||
if (player == null)
|
return CompletionResult.FromHintOptions(names, LocalizationManager.GetString("shell-argument-username-optional-hint"));
|
||||||
{
|
|
||||||
shell.WriteLine("Nah");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mindSystem = _entities.System<SharedMindSystem>();
|
|
||||||
if (!mindSystem.TryGetMind(player, out var mindId, out var mind))
|
|
||||||
{
|
|
||||||
shell.WriteLine("You can't ghost here!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var metaDataSystem = _entities.System<MetaDataSystem>();
|
|
||||||
|
|
||||||
EntityCoordinates? coordinates = null;
|
|
||||||
if (player.AttachedEntity != null)
|
|
||||||
coordinates = _entities.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates;
|
|
||||||
|
|
||||||
if (mind.VisitingEntity != default && _entities.TryGetComponent<GhostComponent>(mind.VisitingEntity, out var oldGhostComponent))
|
|
||||||
{
|
|
||||||
mindSystem.UnVisit(mindId, mind);
|
|
||||||
// If already an admin ghost, then return to body.
|
|
||||||
if (oldGhostComponent.CanGhostInteract)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var canReturn = mind.CurrentEntity != null
|
|
||||||
&& !_entities.HasComponent<GhostComponent>(mind.CurrentEntity);
|
|
||||||
coordinates ??= player.AttachedEntity != null
|
|
||||||
? _entities.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
|
|
||||||
: EntitySystem.Get<GameTicker>().GetObserverSpawnPoint();
|
|
||||||
var ghost = _entities.SpawnEntity(GameTicker.AdminObserverPrototypeName, coordinates.Value);
|
|
||||||
_entities.GetComponent<TransformComponent>(ghost).AttachToGridOrMap();
|
|
||||||
|
|
||||||
if (canReturn)
|
|
||||||
{
|
|
||||||
// TODO: Remove duplication between all this and "GamePreset.OnGhostAttempt()"...
|
|
||||||
if (!string.IsNullOrWhiteSpace(mind.CharacterName))
|
|
||||||
metaDataSystem.SetEntityName(ghost, mind.CharacterName);
|
|
||||||
else if (!string.IsNullOrWhiteSpace(mind.Session?.Name))
|
|
||||||
metaDataSystem.SetEntityName(ghost, mind.Session.Name);
|
|
||||||
|
|
||||||
mindSystem.Visit(mindId, ghost, mind);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
metaDataSystem.SetEntityName(ghost, player.Name);
|
|
||||||
mindSystem.TransferTo(mindId, ghost, mind: mind);
|
|
||||||
}
|
|
||||||
|
|
||||||
var comp = _entities.GetComponent<GhostComponent>(ghost);
|
|
||||||
EntitySystem.Get<SharedGhostSystem>().SetCanReturnToBody(comp, canReturn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
shell.WriteError(LocalizationManager.GetString("shell-wrong-arguments-number"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = shell.Player;
|
||||||
|
var self = player != null;
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
// If you are not a player, you require a player argument.
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
shell.WriteError(LocalizationManager.GetString("shell-need-exactly-one-argument"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var didFind = _playerManager.TryGetSessionByUsername(args[0], out player);
|
||||||
|
if (!didFind)
|
||||||
|
{
|
||||||
|
shell.WriteError(LocalizationManager.GetString("shell-target-player-does-not-exist"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you are a player and a username is provided, a lookup is done to find the target player.
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
var didFind = _playerManager.TryGetSessionByUsername(args[0], out player);
|
||||||
|
if (!didFind)
|
||||||
|
{
|
||||||
|
shell.WriteError(LocalizationManager.GetString("shell-target-player-does-not-exist"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mindSystem = _entities.System<SharedMindSystem>();
|
||||||
|
var metaDataSystem = _entities.System<MetaDataSystem>();
|
||||||
|
var ghostSystem = _entities.System<SharedGhostSystem>();
|
||||||
|
var transformSystem = _entities.System<TransformSystem>();
|
||||||
|
var gameTicker = _entities.System<GameTicker>();
|
||||||
|
|
||||||
|
if (!mindSystem.TryGetMind(player, out var mindId, out var mind))
|
||||||
|
{
|
||||||
|
shell.WriteError(self
|
||||||
|
? LocalizationManager.GetString("aghost-no-mind-self")
|
||||||
|
: LocalizationManager.GetString("aghost-no-mind-other"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mind.VisitingEntity != default && _entities.TryGetComponent<GhostComponent>(mind.VisitingEntity, out var oldGhostComponent))
|
||||||
|
{
|
||||||
|
mindSystem.UnVisit(mindId, mind);
|
||||||
|
// If already an admin ghost, then return to body.
|
||||||
|
if (oldGhostComponent.CanGhostInteract)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var canReturn = mind.CurrentEntity != null
|
||||||
|
&& !_entities.HasComponent<GhostComponent>(mind.CurrentEntity);
|
||||||
|
var coordinates = player!.AttachedEntity != null
|
||||||
|
? _entities.GetComponent<TransformComponent>(player.AttachedEntity.Value).Coordinates
|
||||||
|
: gameTicker.GetObserverSpawnPoint();
|
||||||
|
var ghost = _entities.SpawnEntity(GameTicker.AdminObserverPrototypeName, coordinates);
|
||||||
|
transformSystem.AttachToGridOrMap(ghost, _entities.GetComponent<TransformComponent>(ghost));
|
||||||
|
|
||||||
|
if (canReturn)
|
||||||
|
{
|
||||||
|
// TODO: Remove duplication between all this and "GamePreset.OnGhostAttempt()"...
|
||||||
|
if (!string.IsNullOrWhiteSpace(mind.CharacterName))
|
||||||
|
metaDataSystem.SetEntityName(ghost, mind.CharacterName);
|
||||||
|
else if (!string.IsNullOrWhiteSpace(mind.Session?.Name))
|
||||||
|
metaDataSystem.SetEntityName(ghost, mind.Session.Name);
|
||||||
|
|
||||||
|
mindSystem.Visit(mindId, ghost, mind);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
metaDataSystem.SetEntityName(ghost, player.Name);
|
||||||
|
mindSystem.TransferTo(mindId, ghost, mind: mind);
|
||||||
|
}
|
||||||
|
|
||||||
|
var comp = _entities.GetComponent<GhostComponent>(ghost);
|
||||||
|
ghostSystem.SetCanReturnToBody(comp, canReturn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ using Robust.Shared.Map.Components;
|
|||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Systems;
|
namespace Content.Server.Administration.Systems;
|
||||||
@@ -844,14 +845,14 @@ public sealed partial class AdminVerbSystem
|
|||||||
{
|
{
|
||||||
var allAccess = _prototypeManager
|
var allAccess = _prototypeManager
|
||||||
.EnumeratePrototypes<AccessLevelPrototype>()
|
.EnumeratePrototypes<AccessLevelPrototype>()
|
||||||
.Select(p => p.ID).ToArray();
|
.Select(p => new ProtoId<AccessLevelPrototype>(p.ID)).ToArray();
|
||||||
|
|
||||||
_accessSystem.TrySetTags(entity, allAccess);
|
_accessSystem.TrySetTags(entity, allAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RevokeAllAccess(EntityUid entity)
|
private void RevokeAllAccess(EntityUid entity)
|
||||||
{
|
{
|
||||||
_accessSystem.TrySetTags(entity, Array.Empty<string>());
|
_accessSystem.TrySetTags(entity, new List<ProtoId<AccessLevelPrototype>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TricksVerbPriorities
|
public enum TricksVerbPriorities
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Content.Server.Advertise.EntitySystems;
|
using Content.Server.Advertise.EntitySystems;
|
||||||
using Content.Shared.Advertise;
|
using Content.Shared.Advertise;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
|
|
||||||
namespace Content.Server.Advertise.Components;
|
namespace Content.Server.Advertise.Components;
|
||||||
|
|
||||||
@@ -37,9 +36,4 @@ public sealed partial class AdvertiseComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
|
public TimeSpan NextAdvertisementTime { get; set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the entity will say advertisements or not.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public bool Enabled { get; set; } = true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,115 +23,83 @@ public sealed class AdvertiseSystem : EntitySystem
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next time the game will check if advertisements should be displayed
|
/// The next time the game will check if advertisements should be displayed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private TimeSpan _nextCheckTime = TimeSpan.MaxValue;
|
private TimeSpan _nextCheckTime = TimeSpan.MinValue;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<AdvertiseComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<AdvertiseComponent, MapInitEvent>(OnMapInit);
|
||||||
SubscribeLocalEvent<AdvertiseComponent, PowerChangedEvent>(OnPowerChanged);
|
|
||||||
|
|
||||||
SubscribeLocalEvent<ApcPowerReceiverComponent, AdvertiseEnableChangeAttemptEvent>(OnPowerReceiverEnableChangeAttempt);
|
SubscribeLocalEvent<ApcPowerReceiverComponent, AttemptAdvertiseEvent>(OnPowerReceiverAttemptAdvertiseEvent);
|
||||||
SubscribeLocalEvent<VendingMachineComponent, AdvertiseEnableChangeAttemptEvent>(OnVendingEnableChangeAttempt);
|
SubscribeLocalEvent<VendingMachineComponent, AttemptAdvertiseEvent>(OnVendingAttemptAdvertiseEvent);
|
||||||
|
|
||||||
// The component inits will lower this.
|
_nextCheckTime = TimeSpan.MinValue;
|
||||||
_nextCheckTime = TimeSpan.MaxValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, AdvertiseComponent advertise, MapInitEvent args)
|
private void OnMapInit(EntityUid uid, AdvertiseComponent advert, MapInitEvent args)
|
||||||
{
|
{
|
||||||
RefreshTimer(uid, advertise);
|
RandomizeNextAdvertTime(advert);
|
||||||
|
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPowerChanged(EntityUid uid, AdvertiseComponent advertise, ref PowerChangedEvent args)
|
private void RandomizeNextAdvertTime(AdvertiseComponent advert)
|
||||||
{
|
{
|
||||||
SetEnabled(uid, args.Powered, advertise);
|
var minDuration = Math.Max(1, advert.MinimumWait);
|
||||||
}
|
var maxDuration = Math.Max(minDuration, advert.MaximumWait);
|
||||||
|
|
||||||
public void RefreshTimer(EntityUid uid, AdvertiseComponent? advertise = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref advertise))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!advertise.Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var minDuration = Math.Max(1, advertise.MinimumWait);
|
|
||||||
var maxDuration = Math.Max(minDuration, advertise.MaximumWait);
|
|
||||||
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
|
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
|
||||||
var nextTime = _gameTiming.CurTime + waitDuration;
|
|
||||||
|
|
||||||
advertise.NextAdvertisementTime = nextTime;
|
advert.NextAdvertisementTime = _gameTiming.CurTime + waitDuration;
|
||||||
|
|
||||||
_nextCheckTime = MathHelper.Min(nextTime, _nextCheckTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advertise = null)
|
public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advert = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref advertise))
|
if (!Resolve(uid, ref advert))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_prototypeManager.TryIndex(advertise.Pack, out var advertisements))
|
var attemptEvent = new AttemptAdvertiseEvent(uid);
|
||||||
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true);
|
RaiseLocalEvent(uid, ref attemptEvent);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEnabled(EntityUid uid, bool enable, AdvertiseComponent? advertise = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref advertise))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (advertise.Enabled == enable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var attemptEvent = new AdvertiseEnableChangeAttemptEvent(enable);
|
|
||||||
RaiseLocalEvent(uid, attemptEvent);
|
|
||||||
|
|
||||||
if (attemptEvent.Cancelled)
|
if (attemptEvent.Cancelled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
advertise.Enabled = enable;
|
if (_prototypeManager.TryIndex(advert.Pack, out var advertisements))
|
||||||
RefreshTimer(uid, advertise);
|
_chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Messages)), InGameICChatType.Speak, hideChat: true);
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnPowerReceiverEnableChangeAttempt(EntityUid uid, ApcPowerReceiverComponent component, AdvertiseEnableChangeAttemptEvent args)
|
|
||||||
{
|
|
||||||
if (args.Enabling && !component.Powered)
|
|
||||||
args.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnVendingEnableChangeAttempt(EntityUid uid, VendingMachineComponent component, AdvertiseEnableChangeAttemptEvent args)
|
|
||||||
{
|
|
||||||
if (args.Enabling && component.Broken)
|
|
||||||
args.Cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
var curTime = _gameTiming.CurTime;
|
var currentGameTime = _gameTiming.CurTime;
|
||||||
if (_nextCheckTime > curTime)
|
if (_nextCheckTime > currentGameTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_nextCheckTime = curTime + _maximumNextCheckDuration;
|
// _nextCheckTime starts at TimeSpan.MinValue, so this has to SET the value, not just increment it.
|
||||||
|
_nextCheckTime = currentGameTime + _maximumNextCheckDuration;
|
||||||
|
|
||||||
var query = EntityQueryEnumerator<AdvertiseComponent>();
|
var query = EntityQueryEnumerator<AdvertiseComponent>();
|
||||||
while (query.MoveNext(out var uid, out var advert))
|
while (query.MoveNext(out var uid, out var advert))
|
||||||
{
|
{
|
||||||
if (!advert.Enabled)
|
if (currentGameTime > advert.NextAdvertisementTime)
|
||||||
continue;
|
|
||||||
|
|
||||||
// If this isn't advertising yet
|
|
||||||
if (advert.NextAdvertisementTime > curTime)
|
|
||||||
{
|
{
|
||||||
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
|
SayAdvertisement(uid, advert);
|
||||||
continue;
|
// The timer is always refreshed when it expires, to prevent mass advertising (ex: all the vending machines have no power, and get it back at the same time).
|
||||||
|
RandomizeNextAdvertTime(advert);
|
||||||
}
|
}
|
||||||
|
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
|
||||||
SayAdvertisement(uid, advert);
|
|
||||||
RefreshTimer(uid, advert);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void OnPowerReceiverAttemptAdvertiseEvent(EntityUid uid, ApcPowerReceiverComponent powerReceiver, ref AttemptAdvertiseEvent args)
|
||||||
|
{
|
||||||
|
args.Cancelled |= !powerReceiver.Powered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnVendingAttemptAdvertiseEvent(EntityUid uid, VendingMachineComponent machine, ref AttemptAdvertiseEvent args)
|
||||||
|
{
|
||||||
|
args.Cancelled |= machine.Broken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class AdvertiseEnableChangeAttemptEvent(bool enabling) : CancellableEntityEventArgs
|
[ByRefEvent]
|
||||||
|
public record struct AttemptAdvertiseEvent(EntityUid? Advertiser)
|
||||||
{
|
{
|
||||||
public bool Enabling { get; } = enabling;
|
public bool Cancelled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Anomaly.Components;
|
using Content.Server.Anomaly.Components;
|
||||||
using Content.Shared.Anomaly;
|
using Content.Shared.Anomaly;
|
||||||
using Content.Shared.Anomaly.Components;
|
using Content.Shared.Anomaly.Components;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
@@ -21,6 +21,7 @@ public sealed partial class AnomalySystem
|
|||||||
|
|
||||||
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
|
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnScannerAnomalySeverityChanged);
|
||||||
SubscribeLocalEvent<AnomalyHealthChangedEvent>(OnScannerAnomalyHealthChanged);
|
SubscribeLocalEvent<AnomalyHealthChangedEvent>(OnScannerAnomalyHealthChanged);
|
||||||
|
SubscribeLocalEvent<AnomalyBehaviorChangedEvent>(OnScannerAnomalyBehaviorChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnScannerAnomalyShutdown(ref AnomalyShutdownEvent args)
|
private void OnScannerAnomalyShutdown(ref AnomalyShutdownEvent args)
|
||||||
@@ -67,6 +68,17 @@ public sealed partial class AnomalySystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnScannerAnomalyBehaviorChanged(ref AnomalyBehaviorChangedEvent args)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<AnomalyScannerComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var component))
|
||||||
|
{
|
||||||
|
if (component.ScannedAnomaly != args.Anomaly)
|
||||||
|
continue;
|
||||||
|
UpdateScannerUi(uid, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnScannerUiOpened(EntityUid uid, AnomalyScannerComponent component, BoundUIOpenedEvent args)
|
private void OnScannerUiOpened(EntityUid uid, AnomalyScannerComponent component, BoundUIOpenedEvent args)
|
||||||
{
|
{
|
||||||
UpdateScannerUi(uid, component);
|
UpdateScannerUi(uid, component);
|
||||||
@@ -132,29 +144,95 @@ public sealed partial class AnomalySystem
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-severity-percentage", ("percent", anomalyComp.Severity.ToString("P"))));
|
TryComp<SecretDataAnomalyComponent>(anomaly, out var secret);
|
||||||
msg.PushNewline();
|
|
||||||
string stateLoc;
|
//Severity
|
||||||
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.Severity))
|
||||||
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-severity-percentage-unknown"));
|
||||||
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
|
||||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
|
||||||
else
|
else
|
||||||
stateLoc = Loc.GetString("anomaly-scanner-stability-medium");
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-severity-percentage", ("percent", anomalyComp.Severity.ToString("P"))));
|
||||||
msg.AddMarkup(stateLoc);
|
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
|
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output", ("point", GetAnomalyPointValue(anomaly, anomalyComp))));
|
//Stability
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.Stability))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-stability-unknown"));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string stateLoc;
|
||||||
|
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
||||||
|
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||||
|
else
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-medium");
|
||||||
|
msg.AddMarkup(stateLoc);
|
||||||
|
}
|
||||||
|
msg.PushNewline();
|
||||||
|
|
||||||
|
//Point output
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.OutputPoint))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output-unknown"));
|
||||||
|
else
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-point-output", ("point", GetAnomalyPointValue(anomaly, anomalyComp))));
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
|
|
||||||
|
//Particles title
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-readout"));
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-readout"));
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-danger", ("type", GetParticleLocale(anomalyComp.SeverityParticleType))));
|
|
||||||
|
//Danger
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.ParticleDanger))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-danger-unknown"));
|
||||||
|
else
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-danger", ("type", GetParticleLocale(anomalyComp.SeverityParticleType))));
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-unstable", ("type", GetParticleLocale(anomalyComp.DestabilizingParticleType))));
|
|
||||||
|
//Unstable
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.ParticleUnstable))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-unstable-unknown"));
|
||||||
|
else
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-unstable", ("type", GetParticleLocale(anomalyComp.DestabilizingParticleType))));
|
||||||
msg.PushNewline();
|
msg.PushNewline();
|
||||||
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-containment", ("type", GetParticleLocale(anomalyComp.WeakeningParticleType))));
|
|
||||||
|
//Containment
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.ParticleContainment))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-containment-unknown"));
|
||||||
|
else
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-containment", ("type", GetParticleLocale(anomalyComp.WeakeningParticleType))));
|
||||||
|
msg.PushNewline();
|
||||||
|
|
||||||
|
//Transformation
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.ParticleTransformation))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-transformation-unknown"));
|
||||||
|
else
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-scanner-particle-transformation", ("type", GetParticleLocale(anomalyComp.TransformationParticleType))));
|
||||||
|
|
||||||
|
|
||||||
|
//Behavior
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.PushNewline();
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-behavior-title"));
|
||||||
|
msg.PushNewline();
|
||||||
|
|
||||||
|
if (secret != null && secret.Secret.Contains(AnomalySecretData.Behavior))
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-behavior-unknown"));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (anomalyComp.CurrentBehavior != null)
|
||||||
|
{
|
||||||
|
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
|
||||||
|
|
||||||
|
msg.AddMarkup("- " + Loc.GetString(behavior.Description));
|
||||||
|
msg.PushNewline();
|
||||||
|
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
|
||||||
|
msg.AddMarkup("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg.AddMarkup(Loc.GetString("anomaly-behavior-balanced"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//The timer at the end here is actually added in the ui itself.
|
//The timer at the end here is actually added in the ui itself.
|
||||||
return msg;
|
return msg;
|
||||||
|
|||||||
@@ -8,13 +8,18 @@ using Content.Server.Radio.EntitySystems;
|
|||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Shared.Anomaly;
|
using Content.Shared.Anomaly;
|
||||||
using Content.Shared.Anomaly.Components;
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Prototypes;
|
||||||
using Content.Shared.DoAfter;
|
using Content.Shared.DoAfter;
|
||||||
|
using Content.Shared.Random;
|
||||||
|
using Content.Shared.Random.Helpers;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Physics.Events;
|
using Robust.Shared.Physics.Events;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Content.Server.Anomaly;
|
namespace Content.Server.Anomaly;
|
||||||
|
|
||||||
@@ -33,13 +38,20 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
||||||
[Dependency] private readonly StationSystem _station = default!;
|
[Dependency] private readonly StationSystem _station = default!;
|
||||||
[Dependency] private readonly RadioSystem _radio = default!;
|
[Dependency] private readonly RadioSystem _radio = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly RadiationSystem _radiation = default!;
|
[Dependency] private readonly RadiationSystem _radiation = default!;
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||||
|
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||||
|
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entity = default!;
|
||||||
|
|
||||||
public const float MinParticleVariation = 0.8f;
|
public const float MinParticleVariation = 0.8f;
|
||||||
public const float MaxParticleVariation = 1.2f;
|
public const float MaxParticleVariation = 1.2f;
|
||||||
|
|
||||||
|
[ValidatePrototypeId<WeightedRandomPrototype>]
|
||||||
|
const string WeightListProto = "AnomalyBehaviorList";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -54,25 +66,34 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
InitializeCommands();
|
InitializeCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, AnomalyComponent component, MapInitEvent args)
|
private void OnMapInit(Entity<AnomalyComponent> anomaly, ref MapInitEvent args)
|
||||||
{
|
{
|
||||||
component.NextPulseTime = Timing.CurTime + GetPulseLength(component) * 3; // longer the first time
|
anomaly.Comp.NextPulseTime = Timing.CurTime + GetPulseLength(anomaly.Comp) * 3; // longer the first time
|
||||||
ChangeAnomalyStability(uid, Random.NextFloat(component.InitialStabilityRange.Item1 , component.InitialStabilityRange.Item2), component);
|
ChangeAnomalyStability(anomaly, Random.NextFloat(anomaly.Comp.InitialStabilityRange.Item1 , anomaly.Comp.InitialStabilityRange.Item2), anomaly.Comp);
|
||||||
ChangeAnomalySeverity(uid, Random.NextFloat(component.InitialSeverityRange.Item1, component.InitialSeverityRange.Item2), component);
|
ChangeAnomalySeverity(anomaly, Random.NextFloat(anomaly.Comp.InitialSeverityRange.Item1, anomaly.Comp.InitialSeverityRange.Item2), anomaly.Comp);
|
||||||
|
|
||||||
|
ShuffleParticlesEffect(anomaly.Comp);
|
||||||
|
anomaly.Comp.Continuity = _random.NextFloat(anomaly.Comp.MinContituty, anomaly.Comp.MaxContituty);
|
||||||
|
SetBehavior(anomaly, GetRandomBehavior());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShuffleParticlesEffect(AnomalyComponent anomaly)
|
||||||
|
{
|
||||||
var particles = new List<AnomalousParticleType>
|
var particles = new List<AnomalousParticleType>
|
||||||
{ AnomalousParticleType.Delta, AnomalousParticleType.Epsilon, AnomalousParticleType.Zeta };
|
{ AnomalousParticleType.Delta, AnomalousParticleType.Epsilon, AnomalousParticleType.Zeta, AnomalousParticleType.Sigma };
|
||||||
component.SeverityParticleType = Random.PickAndTake(particles);
|
|
||||||
component.DestabilizingParticleType = Random.PickAndTake(particles);
|
anomaly.SeverityParticleType = Random.PickAndTake(particles);
|
||||||
component.WeakeningParticleType = Random.PickAndTake(particles);
|
anomaly.DestabilizingParticleType = Random.PickAndTake(particles);
|
||||||
|
anomaly.WeakeningParticleType = Random.PickAndTake(particles);
|
||||||
|
anomaly.TransformationParticleType = Random.PickAndTake(particles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnShutdown(EntityUid uid, AnomalyComponent component, ComponentShutdown args)
|
private void OnShutdown(Entity<AnomalyComponent> anomaly, ref ComponentShutdown args)
|
||||||
{
|
{
|
||||||
EndAnomaly(uid, component);
|
EndAnomaly(anomaly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartCollide(EntityUid uid, AnomalyComponent component, ref StartCollideEvent args)
|
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||||
{
|
{
|
||||||
if (!TryComp<AnomalousParticleComponent>(args.OtherEntity, out var particle))
|
if (!TryComp<AnomalousParticleComponent>(args.OtherEntity, out var particle))
|
||||||
return;
|
return;
|
||||||
@@ -80,21 +101,33 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
if (args.OtherFixtureId != particle.FixtureId)
|
if (args.OtherFixtureId != particle.FixtureId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var behaviorMod = 1f;
|
||||||
|
if (anomaly.Comp.CurrentBehavior != null)
|
||||||
|
{
|
||||||
|
var b = _prototype.Index(anomaly.Comp.CurrentBehavior.Value);
|
||||||
|
behaviorMod = b.ParticleSensivity;
|
||||||
|
}
|
||||||
// small function to randomize because it's easier to read like this
|
// small function to randomize because it's easier to read like this
|
||||||
float VaryValue(float v) => v * Random.NextFloat(MinParticleVariation, MaxParticleVariation);
|
float VaryValue(float v) => v * behaviorMod * Random.NextFloat(MinParticleVariation, MaxParticleVariation);
|
||||||
|
|
||||||
if (particle.ParticleType == component.DestabilizingParticleType || particle.DestabilzingOverride)
|
if (particle.ParticleType == anomaly.Comp.DestabilizingParticleType || particle.DestabilzingOverride)
|
||||||
{
|
{
|
||||||
ChangeAnomalyStability(uid, VaryValue(particle.StabilityPerDestabilizingHit), component);
|
ChangeAnomalyStability(anomaly, VaryValue(particle.StabilityPerDestabilizingHit), anomaly.Comp);
|
||||||
}
|
}
|
||||||
if (particle.ParticleType == component.SeverityParticleType || particle.SeverityOverride)
|
if (particle.ParticleType == anomaly.Comp.SeverityParticleType || particle.SeverityOverride)
|
||||||
{
|
{
|
||||||
ChangeAnomalySeverity(uid, VaryValue(particle.SeverityPerSeverityHit), component);
|
ChangeAnomalySeverity(anomaly, VaryValue(particle.SeverityPerSeverityHit), anomaly.Comp);
|
||||||
}
|
}
|
||||||
if (particle.ParticleType == component.WeakeningParticleType || particle.WeakeningOverride)
|
if (particle.ParticleType == anomaly.Comp.WeakeningParticleType || particle.WeakeningOverride)
|
||||||
{
|
{
|
||||||
ChangeAnomalyHealth(uid, VaryValue(particle.HealthPerWeakeningeHit), component);
|
ChangeAnomalyHealth(anomaly, VaryValue(particle.HealthPerWeakeningeHit), anomaly.Comp);
|
||||||
ChangeAnomalyStability(uid, VaryValue(particle.StabilityPerWeakeningeHit), component);
|
ChangeAnomalyStability(anomaly, VaryValue(particle.StabilityPerWeakeningeHit), anomaly.Comp);
|
||||||
|
}
|
||||||
|
if (particle.ParticleType == anomaly.Comp.TransformationParticleType || particle.TransmutationOverride)
|
||||||
|
{
|
||||||
|
ChangeAnomalySeverity(anomaly, VaryValue(particle.SeverityPerSeverityHit), anomaly.Comp);
|
||||||
|
if (_random.Prob(anomaly.Comp.Continuity))
|
||||||
|
SetBehavior(anomaly, GetRandomBehavior());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +149,13 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
//penalty of up to 50% based on health
|
//penalty of up to 50% based on health
|
||||||
multiplier *= MathF.Pow(1.5f, component.Health) - 0.5f;
|
multiplier *= MathF.Pow(1.5f, component.Health) - 0.5f;
|
||||||
|
|
||||||
|
//Apply behavior modifier
|
||||||
|
if (component.CurrentBehavior != null)
|
||||||
|
{
|
||||||
|
var behavior = _prototype.Index(component.CurrentBehavior.Value);
|
||||||
|
multiplier *= behavior.EarnPointModifier;
|
||||||
|
}
|
||||||
|
|
||||||
var severityValue = 1 / (1 + MathF.Pow(MathF.E, -7 * (component.Severity - 0.5f)));
|
var severityValue = 1 / (1 + MathF.Pow(MathF.E, -7 * (component.Severity - 0.5f)));
|
||||||
|
|
||||||
return (int) ((component.MaxPointsPerSecond - component.MinPointsPerSecond) * severityValue * multiplier) + component.MinPointsPerSecond;
|
return (int) ((component.MaxPointsPerSecond - component.MinPointsPerSecond) * severityValue * multiplier) + component.MinPointsPerSecond;
|
||||||
@@ -133,6 +173,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
AnomalousParticleType.Delta => Loc.GetString("anomaly-particles-delta"),
|
AnomalousParticleType.Delta => Loc.GetString("anomaly-particles-delta"),
|
||||||
AnomalousParticleType.Epsilon => Loc.GetString("anomaly-particles-epsilon"),
|
AnomalousParticleType.Epsilon => Loc.GetString("anomaly-particles-epsilon"),
|
||||||
AnomalousParticleType.Zeta => Loc.GetString("anomaly-particles-zeta"),
|
AnomalousParticleType.Zeta => Loc.GetString("anomaly-particles-zeta"),
|
||||||
|
AnomalousParticleType.Sigma => Loc.GetString("anomaly-particles-sigma"),
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -144,4 +185,40 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
UpdateGenerator();
|
UpdateGenerator();
|
||||||
UpdateVessels();
|
UpdateVessels();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Behavior
|
||||||
|
private string GetRandomBehavior()
|
||||||
|
{
|
||||||
|
var weightList = _prototype.Index<WeightedRandomPrototype>(WeightListProto);
|
||||||
|
return weightList.Pick(_random);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBehavior(Entity<AnomalyComponent> anomaly, ProtoId<AnomalyBehaviorPrototype> behaviorProto)
|
||||||
|
{
|
||||||
|
if (anomaly.Comp.CurrentBehavior == behaviorProto)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (anomaly.Comp.CurrentBehavior != null)
|
||||||
|
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||||
|
|
||||||
|
//event broadcast
|
||||||
|
var ev = new AnomalyBehaviorChangedEvent(anomaly, anomaly.Comp.CurrentBehavior, behaviorProto);
|
||||||
|
anomaly.Comp.CurrentBehavior = behaviorProto;
|
||||||
|
RaiseLocalEvent(anomaly, ref ev, true);
|
||||||
|
|
||||||
|
var behavior = _prototype.Index(behaviorProto);
|
||||||
|
|
||||||
|
EntityManager.AddComponents(anomaly, behavior.Components);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveBehavior(Entity<AnomalyComponent> anomaly, ProtoId<AnomalyBehaviorPrototype> behaviorProto)
|
||||||
|
{
|
||||||
|
if (anomaly.Comp.CurrentBehavior == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var behavior = _prototype.Index(anomaly.Comp.CurrentBehavior.Value);
|
||||||
|
|
||||||
|
EntityManager.RemoveComponents(anomaly, behavior.Components);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,58 +13,64 @@ public sealed partial class AnomalousParticleComponent : Component
|
|||||||
/// The type of particle that the projectile
|
/// The type of particle that the projectile
|
||||||
/// imbues onto the anomaly on contact.
|
/// imbues onto the anomaly on contact.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("particleType", required: true)]
|
[DataField(required: true)]
|
||||||
public AnomalousParticleType ParticleType;
|
public AnomalousParticleType ParticleType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The fixture that's checked on collision.
|
/// The fixture that's checked on collision.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("fixtureId")]
|
[DataField]
|
||||||
public string FixtureId = "projectile";
|
public string FixtureId = "projectile";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount that the <see cref="AnomalyComponent.Severity"/> increases by when hit
|
/// The amount that the <see cref="AnomalyComponent.Severity"/> increases by when hit
|
||||||
/// of an anomalous particle of <seealso cref="AnomalyComponent.SeverityParticleType"/>.
|
/// of an anomalous particle of <seealso cref="AnomalyComponent.SeverityParticleType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("severityPerSeverityHit")]
|
[DataField]
|
||||||
public float SeverityPerSeverityHit = 0.025f;
|
public float SeverityPerSeverityHit = 0.025f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
||||||
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("stabilityPerDestabilizingHit")]
|
[DataField]
|
||||||
public float StabilityPerDestabilizingHit = 0.04f;
|
public float StabilityPerDestabilizingHit = 0.04f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
||||||
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("healthPerWeakeningeHit")]
|
[DataField]
|
||||||
public float HealthPerWeakeningeHit = -0.05f;
|
public float HealthPerWeakeningeHit = -0.05f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
/// The amount that the <see cref="AnomalyComponent.Stability"/> increases by when hit
|
||||||
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
/// of an anomalous particle of <seealso cref="AnomalyComponent.DestabilizingParticleType"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("stabilityPerWeakeningeHit")]
|
[DataField]
|
||||||
public float StabilityPerWeakeningeHit = -0.1f;
|
public float StabilityPerWeakeningeHit = -0.1f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If this is true then the particle will always affect the stability of the anomaly.
|
/// If this is true then the particle will always affect the stability of the anomaly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("destabilzingOverride")]
|
[DataField]
|
||||||
public bool DestabilzingOverride = false;
|
public bool DestabilzingOverride = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If this is true then the particle will always affect the weakeness of the anomaly.
|
/// If this is true then the particle will always affect the weakeness of the anomaly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("weakeningOverride")]
|
[DataField]
|
||||||
public bool WeakeningOverride = false;
|
public bool WeakeningOverride = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If this is true then the particle will always affect the severity of the anomaly.
|
/// If this is true then the particle will always affect the severity of the anomaly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("severityOverride")]
|
[DataField]
|
||||||
public bool SeverityOverride = false;
|
public bool SeverityOverride = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If this is true then the particle will always affect the behaviour.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool TransmutationOverride = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hides some information about the anomaly when scanning it
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(SecretDataAnomalySystem), typeof(AnomalySystem))]
|
||||||
|
public sealed partial class SecretDataAnomalyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum hidden data elements on MapInit
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int RandomStartSecretMin = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum hidden data elements on MapInit
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int RandomStartSecretMax = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current secret data
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public List<AnomalySecretData> Secret = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum with secret data field variants
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public enum AnomalySecretData : byte
|
||||||
|
{
|
||||||
|
Severity,
|
||||||
|
Stability,
|
||||||
|
OutputPoint,
|
||||||
|
ParticleDanger,
|
||||||
|
ParticleUnstable,
|
||||||
|
ParticleContainment,
|
||||||
|
ParticleTransformation,
|
||||||
|
Behavior,
|
||||||
|
Default
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shuffle Particle types in some situations
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(ShuffleParticlesAnomalySystem))]
|
||||||
|
public sealed partial class ShuffleParticlesAnomalyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prob() chance to randomize particle types after Anomaly pulation
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool ShuffleOnPulse = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prob() chance to randomize particle types after APE or CHIMP projectile
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool ShuffleOnParticleHit = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chance to random particles
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Prob = 0.5f;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Server.Anomaly.Components;
|
using Content.Server.Anomaly.Components;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
@@ -33,7 +33,7 @@ public sealed class BluespaceAnomalySystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
var xform = xformQuery.GetComponent(uid);
|
var xform = xformQuery.GetComponent(uid);
|
||||||
var range = component.MaxShuffleRadius * args.Severity;
|
var range = component.MaxShuffleRadius * args.Severity * args.PowerModifier;
|
||||||
var mobs = new HashSet<Entity<MobStateComponent>>();
|
var mobs = new HashSet<Entity<MobStateComponent>>();
|
||||||
_lookup.GetEntitiesInRange(xform.Coordinates, range, mobs);
|
_lookup.GetEntitiesInRange(xform.Coordinates, range, mobs);
|
||||||
var allEnts = new ValueList<EntityUid>(mobs.Select(m => m.Owner)) { uid };
|
var allEnts = new ValueList<EntityUid>(mobs.Select(m => m.Owner)) { uid };
|
||||||
@@ -56,7 +56,7 @@ public sealed class BluespaceAnomalySystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
var mapPos = _xform.GetWorldPosition(xform);
|
var mapPos = _xform.GetWorldPosition(xform);
|
||||||
var radius = component.SupercriticalTeleportRadius;
|
var radius = component.SupercriticalTeleportRadius * args.PowerModifier;
|
||||||
var gridBounds = new Box2(mapPos - new Vector2(radius, radius), mapPos + new Vector2(radius, radius));
|
var gridBounds = new Box2(mapPos - new Vector2(radius, radius), mapPos + new Vector2(radius, radius));
|
||||||
var mobs = new HashSet<Entity<MobStateComponent>>();
|
var mobs = new HashSet<Entity<MobStateComponent>>();
|
||||||
_lookup.GetEntitiesInRange(xform.Coordinates, component.MaxShuffleRadius, mobs);
|
_lookup.GetEntitiesInRange(xform.Coordinates, component.MaxShuffleRadius, mobs);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public sealed class ElectricityAnomalySystem : EntitySystem
|
|||||||
|
|
||||||
private void OnPulse(Entity<ElectricityAnomalyComponent> anomaly, ref AnomalyPulseEvent args)
|
private void OnPulse(Entity<ElectricityAnomalyComponent> anomaly, ref AnomalyPulseEvent args)
|
||||||
{
|
{
|
||||||
var range = anomaly.Comp.MaxElectrocuteRange * args.Stability;
|
var range = anomaly.Comp.MaxElectrocuteRange * args.Stability * args.PowerModifier;
|
||||||
|
|
||||||
int boltCount = (int)MathF.Floor(MathHelper.Lerp((float)anomaly.Comp.MinBoltCount, (float)anomaly.Comp.MaxBoltCount, args.Severity));
|
int boltCount = (int)MathF.Floor(MathHelper.Lerp((float)anomaly.Comp.MinBoltCount, (float)anomaly.Comp.MaxBoltCount, args.Severity));
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ public sealed class ElectricityAnomalySystem : EntitySystem
|
|||||||
|
|
||||||
private void OnSupercritical(Entity<ElectricityAnomalyComponent> anomaly, ref AnomalySupercriticalEvent args)
|
private void OnSupercritical(Entity<ElectricityAnomalyComponent> anomaly, ref AnomalySupercriticalEvent args)
|
||||||
{
|
{
|
||||||
var range = anomaly.Comp.MaxElectrocuteRange * 3;
|
var range = anomaly.Comp.MaxElectrocuteRange * 3 * args.PowerModifier;
|
||||||
|
|
||||||
_emp.EmpPulse(_transform.GetMapCoordinates(anomaly), range, anomaly.Comp.EmpEnergyConsumption, anomaly.Comp.EmpDisabledDuration);
|
_emp.EmpPulse(_transform.GetMapCoordinates(anomaly), range, anomaly.Comp.EmpEnergyConsumption, anomaly.Comp.EmpDisabledDuration);
|
||||||
_lightning.ShootRandomLightnings(anomaly, range, anomaly.Comp.MaxBoltCount * 3, arcDepth: 3);
|
_lightning.ShootRandomLightnings(anomaly, range, anomaly.Comp.MaxBoltCount * 3, arcDepth: 3);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnPulse)
|
if (!entry.Settings.SpawnOnPulse)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnEntities(component, entry, args.Stability, args.Severity);
|
SpawnEntities(component, entry, args.Stability, args.Severity, args.PowerModifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnSuperCritical)
|
if (!entry.Settings.SpawnOnSuperCritical)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnEntities(component, entry, 1, 1);
|
SpawnEntities(component, entry, 1, 1, args.PowerModifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnShutdown || args.Supercritical)
|
if (!entry.Settings.SpawnOnShutdown || args.Supercritical)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnEntities(component, entry, 1, 1);
|
SpawnEntities(component, entry, 1, 1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnStabilityChanged)
|
if (!entry.Settings.SpawnOnStabilityChanged)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnEntities(component, entry, args.Stability, args.Severity);
|
SpawnEntities(component, entry, args.Stability, args.Severity, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,17 +79,17 @@ public sealed class EntityAnomalySystem : SharedEntityAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnSeverityChanged)
|
if (!entry.Settings.SpawnOnSeverityChanged)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnEntities(component, entry, args.Stability, args.Severity);
|
SpawnEntities(component, entry, args.Stability, args.Severity, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpawnEntities(Entity<EntitySpawnAnomalyComponent> anomaly, EntitySpawnSettingsEntry entry, float stability, float severity)
|
private void SpawnEntities(Entity<EntitySpawnAnomalyComponent> anomaly, EntitySpawnSettingsEntry entry, float stability, float severity, float powerMod)
|
||||||
{
|
{
|
||||||
var xform = Transform(anomaly);
|
var xform = Transform(anomaly);
|
||||||
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
|
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var tiles = _anomaly.GetSpawningPoints(anomaly, stability, severity, entry.Settings);
|
var tiles = _anomaly.GetSpawningPoints(anomaly, stability, severity, entry.Settings, powerMod);
|
||||||
if (tiles == null)
|
if (tiles == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public sealed class InjectionAnomalySystem : EntitySystem
|
|||||||
|
|
||||||
private void OnPulse(Entity<InjectionAnomalyComponent> entity, ref AnomalyPulseEvent args)
|
private void OnPulse(Entity<InjectionAnomalyComponent> entity, ref AnomalyPulseEvent args)
|
||||||
{
|
{
|
||||||
PulseScalableEffect(entity, entity.Comp.InjectRadius, entity.Comp.MaxSolutionInjection * args.Severity);
|
PulseScalableEffect(entity, entity.Comp.InjectRadius * args.PowerModifier, entity.Comp.MaxSolutionInjection * args.Severity * args.PowerModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSupercritical(Entity<InjectionAnomalyComponent> entity, ref AnomalySupercriticalEvent args)
|
private void OnSupercritical(Entity<InjectionAnomalyComponent> entity, ref AnomalySupercriticalEvent args)
|
||||||
{
|
{
|
||||||
PulseScalableEffect(entity, entity.Comp.SuperCriticalInjectRadius, entity.Comp.SuperCriticalSolutionInjection);
|
PulseScalableEffect(entity, entity.Comp.SuperCriticalInjectRadius * args.PowerModifier, entity.Comp.SuperCriticalSolutionInjection * args.PowerModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PulseScalableEffect(Entity<InjectionAnomalyComponent> entity, float injectRadius, float maxInject)
|
private void PulseScalableEffect(Entity<InjectionAnomalyComponent> entity, float injectRadius, float maxInject)
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ public sealed class ProjectileAnomalySystem : EntitySystem
|
|||||||
|
|
||||||
private void OnPulse(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalyPulseEvent args)
|
private void OnPulse(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||||
{
|
{
|
||||||
ShootProjectilesAtEntities(uid, component, args.Severity);
|
ShootProjectilesAtEntities(uid, component, args.Severity * args.PowerModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSupercritical(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
private void OnSupercritical(EntityUid uid, ProjectileAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||||
{
|
{
|
||||||
ShootProjectilesAtEntities(uid, component, 1.0f);
|
ShootProjectilesAtEntities(uid, component, args.PowerModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShootProjectilesAtEntities(EntityUid uid, ProjectileAnomalyComponent component, float severity)
|
private void ShootProjectilesAtEntities(EntityUid uid, ProjectileAnomalyComponent component, float severity)
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ public sealed class PuddleCreateAnomalySystem : EntitySystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var xform = Transform(entity.Owner);
|
var xform = Transform(entity.Owner);
|
||||||
var puddleSol = _solutionContainer.SplitSolution(sol.Value, entity.Comp.MaxPuddleSize * args.Severity);
|
var puddleSol = _solutionContainer.SplitSolution(sol.Value, entity.Comp.MaxPuddleSize * args.Severity * args.PowerModifier);
|
||||||
_puddle.TrySplashSpillAt(entity.Owner, xform.Coordinates, puddleSol, out _);
|
_puddle.TrySplashSpillAt(entity.Owner, xform.Coordinates, puddleSol, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSupercritical(Entity<PuddleCreateAnomalyComponent> entity, ref AnomalySupercriticalEvent args)
|
private void OnSupercritical(Entity<PuddleCreateAnomalyComponent> entity, ref AnomalySupercriticalEvent args)
|
||||||
{
|
{
|
||||||
if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var sol))
|
if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var sol))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Shared.Anomaly.Components;
|
using Content.Shared.Anomaly.Components;
|
||||||
using Content.Shared.Anomaly.Effects.Components;
|
using Content.Shared.Anomaly.Effects.Components;
|
||||||
@@ -24,14 +24,14 @@ public sealed class PyroclasticAnomalySystem : EntitySystem
|
|||||||
private void OnPulse(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalyPulseEvent args)
|
private void OnPulse(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalyPulseEvent args)
|
||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
var ignitionRadius = component.MaximumIgnitionRadius * args.Stability;
|
var ignitionRadius = component.MaximumIgnitionRadius * args.Stability * args.PowerModifier;
|
||||||
IgniteNearby(uid, xform.Coordinates, args.Severity, ignitionRadius);
|
IgniteNearby(uid, xform.Coordinates, args.Severity, ignitionRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSupercritical(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
private void OnSupercritical(EntityUid uid, PyroclasticAnomalyComponent component, ref AnomalySupercriticalEvent args)
|
||||||
{
|
{
|
||||||
var xform = Transform(uid);
|
var xform = Transform(uid);
|
||||||
IgniteNearby(uid, xform.Coordinates, 1, component.MaximumIgnitionRadius * 2);
|
IgniteNearby(uid, xform.Coordinates, 1, component.MaximumIgnitionRadius * 2 * args.PowerModifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void IgniteNearby(EntityUid uid, EntityCoordinates coordinates, float severity, float radius)
|
public void IgniteNearby(EntityUid uid, EntityCoordinates coordinates, float severity, float radius)
|
||||||
|
|||||||
40
Content.Server/Anomaly/Effects/SecretDataAnomalySystem.cs
Normal file
40
Content.Server/Anomaly/Effects/SecretDataAnomalySystem.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Effects;
|
||||||
|
|
||||||
|
public sealed class SecretDataAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
private readonly List<AnomalySecretData> _deita = new();
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<SecretDataAnomalyComponent, MapInitEvent>(OnMapInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMapInit(EntityUid uid, SecretDataAnomalyComponent anomaly, MapInitEvent args)
|
||||||
|
{
|
||||||
|
RandomizeSecret(uid,_random.Next(anomaly.RandomStartSecretMin, anomaly.RandomStartSecretMax), anomaly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RandomizeSecret(EntityUid uid, int count, SecretDataAnomalyComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Secret.Clear();
|
||||||
|
|
||||||
|
// I also considered just adding all the enum values and pruning but that seems more wasteful.
|
||||||
|
_deita.Clear();
|
||||||
|
_deita.AddRange(Enum.GetValues<AnomalySecretData>());
|
||||||
|
var actualCount = Math.Min(count, _deita.Count);
|
||||||
|
|
||||||
|
for (int i = 0; i < actualCount; i++)
|
||||||
|
{
|
||||||
|
component.Secret.Add(_random.PickAndTake(_deita));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Content.Server.Anomaly.Components;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
|
using Robust.Shared.Physics.Events;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Anomaly.Effects;
|
||||||
|
public sealed class ShuffleParticlesAnomalySystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AnomalySystem _anomaly = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, AnomalyPulseEvent>(OnPulse);
|
||||||
|
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStartCollide(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, StartCollideEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<AnomalyComponent>(uid, out var anomaly))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (shuffle.ShuffleOnParticleHit && _random.Prob(shuffle.Prob))
|
||||||
|
_anomaly.ShuffleParticlesEffect(anomaly);
|
||||||
|
|
||||||
|
if (!TryComp<AnomalousParticleComponent>(args.OtherEntity, out var particle))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPulse(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, AnomalyPulseEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<AnomalyComponent>(uid, out var anomaly))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (shuffle.ShuffleOnPulse && _random.Prob(shuffle.Prob))
|
||||||
|
{
|
||||||
|
_anomaly.ShuffleParticlesEffect(anomaly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public sealed class TileAnomalySystem : SharedTileAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnPulse)
|
if (!entry.Settings.SpawnOnPulse)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnTiles(component, entry, args.Stability, args.Severity);
|
SpawnTiles(component, entry, args.Stability, args.Severity, args.PowerModifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public sealed class TileAnomalySystem : SharedTileAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnSuperCritical)
|
if (!entry.Settings.SpawnOnSuperCritical)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnTiles(component, entry, 1, 1);
|
SpawnTiles(component, entry, 1, 1, args.PowerModifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public sealed class TileAnomalySystem : SharedTileAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnShutdown || args.Supercritical)
|
if (!entry.Settings.SpawnOnShutdown || args.Supercritical)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnTiles(component, entry, 1, 1);
|
SpawnTiles(component, entry, 1, 1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public sealed class TileAnomalySystem : SharedTileAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnStabilityChanged)
|
if (!entry.Settings.SpawnOnStabilityChanged)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnTiles(component, entry, args.Stability, args.Severity);
|
SpawnTiles(component, entry, args.Stability, args.Severity, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,17 +78,17 @@ public sealed class TileAnomalySystem : SharedTileAnomalySystem
|
|||||||
if (!entry.Settings.SpawnOnSeverityChanged)
|
if (!entry.Settings.SpawnOnSeverityChanged)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SpawnTiles(component, entry, args.Stability, args.Severity);
|
SpawnTiles(component, entry, args.Stability, args.Severity, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpawnTiles(Entity<TileSpawnAnomalyComponent> anomaly, TileSpawnSettingsEntry entry, float stability, float severity)
|
private void SpawnTiles(Entity<TileSpawnAnomalyComponent> anomaly, TileSpawnSettingsEntry entry, float stability, float severity, float powerMod)
|
||||||
{
|
{
|
||||||
var xform = Transform(anomaly);
|
var xform = Transform(anomaly);
|
||||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var tiles = _anomaly.GetSpawningPoints(anomaly, stability, severity, entry.Settings);
|
var tiles = _anomaly.GetSpawningPoints(anomaly, stability, severity, entry.Settings, powerMod);
|
||||||
if (tiles == null)
|
if (tiles == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -432,14 +432,16 @@ namespace Content.Server.Atmos.EntitySystems
|
|||||||
if (!overlay.Chunks.TryGetValue(gIndex, out var value))
|
if (!overlay.Chunks.TryGetValue(gIndex, out var value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (previousChunks != null &&
|
// If the chunk was updated since we last sent it, send it again
|
||||||
previousChunks.Contains(gIndex) &&
|
if (value.LastUpdate > LastSessionUpdate)
|
||||||
value.LastUpdate > LastSessionUpdate)
|
|
||||||
{
|
{
|
||||||
|
dataToSend.Add(value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dataToSend.Add(value);
|
// Always send it if we didn't previously send it
|
||||||
|
if (previousChunks == null || !previousChunks.Contains(gIndex))
|
||||||
|
dataToSend.Add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
previouslySent[netGrid] = gridChunks;
|
previouslySent[netGrid] = gridChunks;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
|||||||
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, ref AtmosDeviceUpdateEvent args)
|
private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, ref AtmosDeviceUpdateEvent args)
|
||||||
{
|
{
|
||||||
if (!filter.Enabled
|
if (!filter.Enabled
|
||||||
|| !_nodeContainer.TryGetNodes(uid, filter.InletName, filter.OutletName, filter.FilterName, out PipeNode? inletNode, out PipeNode? filterNode, out PipeNode? outletNode)
|
|| !_nodeContainer.TryGetNodes(uid, filter.InletName, filter.FilterName, filter.OutletName, out PipeNode? inletNode, out PipeNode? filterNode, out PipeNode? outletNode)
|
||||||
|| outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full.
|
|| outletNode.Air.Pressure >= Atmospherics.MaxOutputPressure) // No need to transfer if target is full.
|
||||||
{
|
{
|
||||||
_ambientSoundSystem.SetAmbience(uid, false);
|
_ambientSoundSystem.SetAmbience(uid, false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Shared.Cargo;
|
using Content.Shared.Cargo;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
namespace Content.Server.Cargo.Components;
|
namespace Content.Server.Cargo.Components;
|
||||||
|
|
||||||
@@ -32,4 +33,16 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public HashSet<string> CheckedBounties = new();
|
public HashSet<string> CheckedBounties = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which players will be able to skip the next bounty.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextSkipTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time between skipping bounties.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public TimeSpan SkipDelay = TimeSpan.FromMinutes(15);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Content.Server.Cargo.Components;
|
|||||||
using Content.Server.Labels;
|
using Content.Server.Labels;
|
||||||
using Content.Server.NameIdentifier;
|
using Content.Server.NameIdentifier;
|
||||||
using Content.Server.Paper;
|
using Content.Server.Paper;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.Cargo;
|
using Content.Shared.Cargo;
|
||||||
using Content.Shared.Cargo.Components;
|
using Content.Shared.Cargo.Components;
|
||||||
using Content.Shared.Cargo.Prototypes;
|
using Content.Shared.Cargo.Prototypes;
|
||||||
@@ -35,6 +35,7 @@ public sealed partial class CargoSystem
|
|||||||
{
|
{
|
||||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
|
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
|
||||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
|
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
|
||||||
|
SubscribeLocalEvent<CargoBountyConsoleComponent, BountySkipMessage>(OnSkipBountyMessage);
|
||||||
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
|
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
|
||||||
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
|
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
|
||||||
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
|
||||||
@@ -50,7 +51,8 @@ public sealed partial class CargoSystem
|
|||||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
|
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties));
|
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||||
|
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||||
@@ -70,6 +72,37 @@ public sealed partial class CargoSystem
|
|||||||
_audio.PlayPvs(component.PrintSound, uid);
|
_audio.PlayPvs(component.PrintSound, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent component, BountySkipMessage args)
|
||||||
|
{
|
||||||
|
if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_timing.CurTime < db.NextSkipTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Session.AttachedEntity is not { Valid: true } mob)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TryComp<AccessReaderComponent>(uid, out var accessReaderComponent) &&
|
||||||
|
!_accessReaderSystem.IsAllowed(mob, uid, accessReaderComponent))
|
||||||
|
{
|
||||||
|
_audio.PlayPvs(component.DenySound, uid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryRemoveBounty(station, bounty.Value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FillBountyDatabase(station);
|
||||||
|
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
||||||
|
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||||
|
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip));
|
||||||
|
_audio.PlayPvs(component.SkipSound, uid);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
|
public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
|
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
|
||||||
@@ -431,7 +464,8 @@ public sealed partial class CargoSystem
|
|||||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
!TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui);
|
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||||
|
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,8 +312,7 @@ namespace Content.Server.Chat.Managers
|
|||||||
var prefs = _preferencesManager.GetPreferences(player.UserId);
|
var prefs = _preferencesManager.GetPreferences(player.UserId);
|
||||||
colorOverride = prefs.AdminOOCColor;
|
colorOverride = prefs.AdminOOCColor;
|
||||||
}
|
}
|
||||||
if (player.Channel.UserData.PatronTier is { } patron &&
|
if ( _netConfigManager.GetClientCVar(player.Channel, CCVars.ShowOocPatronColor) && player.Channel.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor))
|
||||||
PatronOocColors.TryGetValue(patron, out var patronColor))
|
|
||||||
{
|
{
|
||||||
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)), ("rep", reputation));
|
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)), ("rep", reputation));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for components that inject a solution into a target's bloodstream in response to an event.
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class BaseSolutionInjectOnEventComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How much solution to remove from this entity per target when transferring.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note that this amount is per target, so the total amount removed will be
|
||||||
|
/// multiplied by the number of targets hit.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 TransferAmount = FixedPoint2.New(1);
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public float TransferEfficiency { get => _transferEfficiency; set => _transferEfficiency = Math.Clamp(value, 0, 1); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Proportion of the <see cref="TransferAmount"/> that will actually be injected
|
||||||
|
/// into the target's bloodstream. The rest is lost.
|
||||||
|
/// 0 means none of the transferred solution will enter the bloodstream.
|
||||||
|
/// 1 means the entire amount will enter the bloodstream.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("transferEfficiency")]
|
||||||
|
private float _transferEfficiency = 1f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Solution to inject from.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public string Solution = "default";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this will inject through hardsuits or not.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool PierceArmor = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contents of popup message to display to the attacker when injection
|
||||||
|
/// fails due to the target wearing a hardsuit.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Passed values: $weapon and $target
|
||||||
|
/// </remarks>
|
||||||
|
[DataField]
|
||||||
|
public LocId BlockedByHardsuitPopupMessage = "melee-inject-failed-hardsuit";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If anything covers any of these slots then the injection fails.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public SlotFlags BlockSlots = SlotFlags.NONE;
|
||||||
|
}
|
||||||
@@ -1,31 +1,8 @@
|
|||||||
using Content.Shared.FixedPoint;
|
namespace Content.Server.Chemistry.Components;
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.Components
|
/// <summary>
|
||||||
{
|
/// Used for melee weapon entities that should try to inject a
|
||||||
[RegisterComponent]
|
/// contained solution into a target when used to hit it.
|
||||||
public sealed partial class MeleeChemicalInjectorComponent : Component
|
/// </summary>
|
||||||
{
|
[RegisterComponent]
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
public sealed partial class MeleeChemicalInjectorComponent : BaseSolutionInjectOnEventComponent { }
|
||||||
[DataField("transferAmount")]
|
|
||||||
public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(1);
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float TransferEfficiency { get => _transferEfficiency; set => _transferEfficiency = Math.Clamp(value, 0, 1); }
|
|
||||||
|
|
||||||
[DataField("transferEfficiency")]
|
|
||||||
private float _transferEfficiency = 1f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this will inject through hardsuits or not.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("pierceArmor"), ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool PierceArmor = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Solution to inject from.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("solution")]
|
|
||||||
public string Solution { get; set; } = "default";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Content.Shared.Inventory;
|
|
||||||
using Content.Shared.Projectiles;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.Components;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// On colliding with an entity that has a bloodstream will dump its solution onto them.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed partial class SolutionInjectOnCollideComponent : Component
|
|
||||||
{
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("transferAmount")]
|
|
||||||
public FixedPoint2 TransferAmount = FixedPoint2.New(1);
|
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float TransferEfficiency { get => _transferEfficiency; set => _transferEfficiency = Math.Clamp(value, 0, 1); }
|
|
||||||
|
|
||||||
[DataField("transferEfficiency")]
|
|
||||||
private float _transferEfficiency = 1f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If anything covers any of these slots then the injection fails.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("blockSlots"), ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public SlotFlags BlockSlots = SlotFlags.MASK;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Content.Server.Chemistry.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for embeddable entities that should try to inject a
|
||||||
|
/// contained solution into a target when they become embedded in it.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class SolutionInjectOnEmbedComponent : BaseSolutionInjectOnEventComponent { }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Content.Server.Chemistry.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for projectile entities that should try to inject a
|
||||||
|
/// contained solution into a target when they hit it.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class SolutionInjectOnProjectileHitComponent : BaseSolutionInjectOnEventComponent { }
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using Content.Server.Body.Components;
|
|
||||||
using Content.Server.Body.Systems;
|
|
||||||
using Content.Server.Chemistry.Components;
|
|
||||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
|
||||||
using Content.Shared.Inventory;
|
|
||||||
using Content.Shared.Projectiles;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.EntitySystems;
|
|
||||||
|
|
||||||
public sealed class SolutionInjectOnCollideSystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionContainersSystem = default!;
|
|
||||||
[Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
|
|
||||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
SubscribeLocalEvent<SolutionInjectOnCollideComponent, ProjectileHitEvent>(HandleInjection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleInjection(Entity<SolutionInjectOnCollideComponent> ent, ref ProjectileHitEvent args)
|
|
||||||
{
|
|
||||||
var component = ent.Comp;
|
|
||||||
var target = args.Target;
|
|
||||||
|
|
||||||
if (!TryComp<BloodstreamComponent>(target, out var bloodstream) ||
|
|
||||||
!_solutionContainersSystem.TryGetInjectableSolution(ent.Owner, out var solution, out _))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.BlockSlots != 0x0)
|
|
||||||
{
|
|
||||||
var containerEnumerator = _inventorySystem.GetSlotEnumerator(target, component.BlockSlots);
|
|
||||||
|
|
||||||
// TODO add a helper method for this?
|
|
||||||
if (containerEnumerator.MoveNext(out _))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var solRemoved = _solutionContainersSystem.SplitSolution(solution.Value, component.TransferAmount);
|
|
||||||
var solRemovedVol = solRemoved.Volume;
|
|
||||||
|
|
||||||
var solToInject = solRemoved.SplitSolution(solRemovedVol * component.TransferEfficiency);
|
|
||||||
|
|
||||||
_bloodstreamSystem.TryAddToChemicals(target, solToInject, bloodstream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using Content.Server.Body.Components;
|
||||||
|
using Content.Server.Body.Systems;
|
||||||
|
using Content.Server.Chemistry.Components;
|
||||||
|
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||||
|
using Content.Shared.Inventory;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.Projectiles;
|
||||||
|
using Content.Shared.Tag;
|
||||||
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
|
||||||
|
namespace Content.Server.Chemistry.EntitySystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// System for handling the different inheritors of <see cref="BaseSolutionInjectOnEventComponent"/>.
|
||||||
|
/// Subscribes to relevent events and performs solution injections when they are raised.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SolutionInjectOnCollideSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||||
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||||
|
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<SolutionInjectOnProjectileHitComponent, ProjectileHitEvent>(HandleProjectileHit);
|
||||||
|
SubscribeLocalEvent<SolutionInjectOnEmbedComponent, EmbedEvent>(HandleEmbed);
|
||||||
|
SubscribeLocalEvent<MeleeChemicalInjectorComponent, MeleeHitEvent>(HandleMeleeHit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleProjectileHit(Entity<SolutionInjectOnProjectileHitComponent> entity, ref ProjectileHitEvent args)
|
||||||
|
{
|
||||||
|
DoInjection((entity.Owner, entity.Comp), args.Target, args.Shooter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEmbed(Entity<SolutionInjectOnEmbedComponent> entity, ref EmbedEvent args)
|
||||||
|
{
|
||||||
|
DoInjection((entity.Owner, entity.Comp), args.Embedded, args.Shooter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleMeleeHit(Entity<MeleeChemicalInjectorComponent> entity, ref MeleeHitEvent args)
|
||||||
|
{
|
||||||
|
// MeleeHitEvent is weird, so we have to filter to make sure we actually
|
||||||
|
// hit something and aren't just examining the weapon.
|
||||||
|
if (args.IsHit)
|
||||||
|
TryInjectTargets((entity.Owner, entity.Comp), args.HitEntities, args.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoInjection(Entity<BaseSolutionInjectOnEventComponent> injectorEntity, EntityUid target, EntityUid? source = null)
|
||||||
|
{
|
||||||
|
TryInjectTargets(injectorEntity, [target], source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters <paramref name="targets"/> for valid targets and tries to inject a portion of <see cref="BaseSolutionInjectOnEventComponent.Solution"/> into
|
||||||
|
/// each valid target's bloodstream.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Targets are invalid if any of the following are true:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>The target does not have a bloodstream.</item>
|
||||||
|
/// <item><see cref="BaseSolutionInjectOnEventComponent.PierceArmor"/> is false and the target is wearing a hardsuit.</item>
|
||||||
|
/// <item><see cref="BaseSolutionInjectOnEventComponent.BlockSlots"/> is not NONE and the target has an item equipped in any of the specified slots.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>true if at least one target was successfully injected, otherwise false</returns>
|
||||||
|
private bool TryInjectTargets(Entity<BaseSolutionInjectOnEventComponent> injector, IReadOnlyList<EntityUid> targets, EntityUid? source = null)
|
||||||
|
{
|
||||||
|
// Make sure we have at least one target
|
||||||
|
if (targets.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Get the solution to inject
|
||||||
|
if (!_solutionContainer.TryGetSolution(injector.Owner, injector.Comp.Solution, out var injectorSolution))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Build a list of bloodstreams to inject into
|
||||||
|
var targetBloodstreams = new ValueList<Entity<BloodstreamComponent>>();
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
if (Deleted(target))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Yuck, this is way to hardcodey for my tastes
|
||||||
|
// TODO blocking injection with a hardsuit should probably done with a cancellable event or something
|
||||||
|
if (!injector.Comp.PierceArmor && _inventory.TryGetSlotEntity(target, "outerClothing", out var suit) && _tag.HasTag(suit.Value, "Hardsuit"))
|
||||||
|
{
|
||||||
|
// Only show popup to attacker
|
||||||
|
if (source != null)
|
||||||
|
_popup.PopupEntity(Loc.GetString(injector.Comp.BlockedByHardsuitPopupMessage, ("weapon", injector.Owner), ("target", target)), target, source.Value, PopupType.SmallCaution);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the target has anything equipped in a slot that would block injection
|
||||||
|
if (injector.Comp.BlockSlots != SlotFlags.NONE)
|
||||||
|
{
|
||||||
|
var blocked = false;
|
||||||
|
var containerEnumerator = _inventory.GetSlotEnumerator(target, injector.Comp.BlockSlots);
|
||||||
|
while (containerEnumerator.MoveNext(out var container))
|
||||||
|
{
|
||||||
|
if (container.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
blocked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blocked)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the target has a bloodstream
|
||||||
|
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
|
||||||
|
// Checks passed; add this target's bloodstream to the list
|
||||||
|
targetBloodstreams.Add((target, bloodstream));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we got at least one bloodstream
|
||||||
|
if (targetBloodstreams.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Extract total needed solution from the injector
|
||||||
|
var removedSolution = _solutionContainer.SplitSolution(injectorSolution.Value, injector.Comp.TransferAmount * targetBloodstreams.Count);
|
||||||
|
// Adjust solution amount based on transfer efficiency
|
||||||
|
var solutionToInject = removedSolution.SplitSolution(removedSolution.Volume * injector.Comp.TransferEfficiency);
|
||||||
|
// Calculate how much of the adjusted solution each target will get
|
||||||
|
var volumePerBloodstream = solutionToInject.Volume * (1f / targetBloodstreams.Count);
|
||||||
|
|
||||||
|
var anySuccess = false;
|
||||||
|
foreach (var targetBloodstream in targetBloodstreams)
|
||||||
|
{
|
||||||
|
// Take our portion of the adjusted solution for this target
|
||||||
|
var individualInjection = solutionToInject.SplitSolution(volumePerBloodstream);
|
||||||
|
// Inject our portion into the target's bloodstream
|
||||||
|
if (_bloodstream.TryAddToChemicals(targetBloodstream.Owner, individualInjection, targetBloodstream.Comp))
|
||||||
|
anySuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Huzzah!
|
||||||
|
return anySuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
using Content.Server.Administration.Logs;
|
|
||||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
|
||||||
using Content.Shared.Chemistry;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Content.Shared.Interaction;
|
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Verbs;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Player;
|
|
||||||
|
|
||||||
namespace Content.Server.Chemistry.EntitySystems
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class SolutionTransferSystem : EntitySystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
|
||||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default transfer amounts for the set-transfer verb.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly List<int> DefaultTransferAmounts = new() { 1, 5, 10, 25, 50, 100, 250, 500, 1000 };
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<SolutionTransferComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
|
|
||||||
SubscribeLocalEvent<SolutionTransferComponent, AfterInteractEvent>(OnAfterInteract);
|
|
||||||
SubscribeLocalEvent<SolutionTransferComponent, TransferAmountSetValueMessage>(OnTransferAmountSetValueMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTransferAmountSetValueMessage(Entity<SolutionTransferComponent> entity, ref TransferAmountSetValueMessage message)
|
|
||||||
{
|
|
||||||
var newTransferAmount = FixedPoint2.Clamp(message.Value, entity.Comp.MinimumTransferAmount, entity.Comp.MaximumTransferAmount);
|
|
||||||
entity.Comp.TransferAmount = newTransferAmount;
|
|
||||||
|
|
||||||
if (message.Session.AttachedEntity is { Valid: true } user)
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", newTransferAmount)), entity.Owner, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddSetTransferVerbs(Entity<SolutionTransferComponent> entity, ref GetVerbsEvent<AlternativeVerb> args)
|
|
||||||
{
|
|
||||||
var (uid, component) = entity;
|
|
||||||
|
|
||||||
if (!args.CanAccess || !args.CanInteract || !component.CanChangeTransferAmount || args.Hands == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Custom transfer verb
|
|
||||||
AlternativeVerb custom = new();
|
|
||||||
custom.Text = Loc.GetString("comp-solution-transfer-verb-custom-amount");
|
|
||||||
custom.Category = VerbCategory.SetTransferAmount;
|
|
||||||
custom.Act = () => _userInterfaceSystem.TryOpen(uid, TransferAmountUiKey.Key, actor.PlayerSession);
|
|
||||||
custom.Priority = 1;
|
|
||||||
args.Verbs.Add(custom);
|
|
||||||
|
|
||||||
// Add specific transfer verbs according to the container's size
|
|
||||||
var priority = 0;
|
|
||||||
var user = args.User;
|
|
||||||
foreach (var amount in DefaultTransferAmounts)
|
|
||||||
{
|
|
||||||
if (amount < component.MinimumTransferAmount.Int() || amount > component.MaximumTransferAmount.Int())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AlternativeVerb verb = new();
|
|
||||||
verb.Text = Loc.GetString("comp-solution-transfer-verb-amount", ("amount", amount));
|
|
||||||
verb.Category = VerbCategory.SetTransferAmount;
|
|
||||||
verb.Act = () =>
|
|
||||||
{
|
|
||||||
component.TransferAmount = FixedPoint2.New(amount);
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), uid, user);
|
|
||||||
};
|
|
||||||
|
|
||||||
// we want to sort by size, not alphabetically by the verb text.
|
|
||||||
verb.Priority = priority;
|
|
||||||
priority--;
|
|
||||||
|
|
||||||
args.Verbs.Add(verb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAfterInteract(Entity<SolutionTransferComponent> entity, ref AfterInteractEvent args)
|
|
||||||
{
|
|
||||||
if (!args.CanReach || args.Target == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var target = args.Target!.Value;
|
|
||||||
var (uid, component) = entity;
|
|
||||||
|
|
||||||
//Special case for reagent tanks, because normally clicking another container will give solution, not take it.
|
|
||||||
if (component.CanReceive && !EntityManager.HasComponent<RefillableSolutionComponent>(target) // target must not be refillable (e.g. Reagent Tanks)
|
|
||||||
&& _solutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out _) // target must be drainable
|
|
||||||
&& EntityManager.TryGetComponent(uid, out RefillableSolutionComponent? refillComp)
|
|
||||||
&& _solutionContainerSystem.TryGetRefillableSolution((uid, refillComp, null), out var ownerSoln, out var ownerRefill))
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
var transferAmount = component.TransferAmount; // This is the player-configurable transfer amount of "uid," not the target reagent tank.
|
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(uid, out RefillableSolutionComponent? refill) && refill.MaxRefill != null) // uid is the entity receiving solution from target.
|
|
||||||
{
|
|
||||||
transferAmount = FixedPoint2.Min(transferAmount, (FixedPoint2) refill.MaxRefill); // if the receiver has a smaller transfer limit, use that instead
|
|
||||||
}
|
|
||||||
|
|
||||||
var transferred = Transfer(args.User, target, targetSoln.Value, uid, ownerSoln.Value, transferAmount);
|
|
||||||
if (transferred > 0)
|
|
||||||
{
|
|
||||||
var toTheBrim = ownerRefill.AvailableVolume == 0;
|
|
||||||
var msg = toTheBrim
|
|
||||||
? "comp-solution-transfer-fill-fully"
|
|
||||||
: "comp-solution-transfer-fill-normal";
|
|
||||||
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString(msg, ("owner", args.Target), ("amount", transferred), ("target", uid)), uid, args.User);
|
|
||||||
|
|
||||||
args.Handled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if target is refillable, and owner is drainable
|
|
||||||
if (component.CanSend && _solutionContainerSystem.TryGetRefillableSolution(target, out targetSoln, out var targetRefill)
|
|
||||||
&& _solutionContainerSystem.TryGetDrainableSolution(uid, out ownerSoln, out var ownerDrain))
|
|
||||||
{
|
|
||||||
var transferAmount = component.TransferAmount;
|
|
||||||
|
|
||||||
if (EntityManager.TryGetComponent(target, out RefillableSolutionComponent? refill) && refill.MaxRefill != null)
|
|
||||||
{
|
|
||||||
transferAmount = FixedPoint2.Min(transferAmount, (FixedPoint2) refill.MaxRefill);
|
|
||||||
}
|
|
||||||
|
|
||||||
var transferred = Transfer(args.User, uid, ownerSoln.Value, target, targetSoln.Value, transferAmount);
|
|
||||||
|
|
||||||
if (transferred > 0)
|
|
||||||
{
|
|
||||||
var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", target));
|
|
||||||
_popupSystem.PopupEntity(message, uid, args.User);
|
|
||||||
|
|
||||||
args.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transfer from a solution to another.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The actual amount transferred.</returns>
|
|
||||||
public FixedPoint2 Transfer(EntityUid user,
|
|
||||||
EntityUid sourceEntity,
|
|
||||||
Entity<SolutionComponent> source,
|
|
||||||
EntityUid targetEntity,
|
|
||||||
Entity<SolutionComponent> target,
|
|
||||||
FixedPoint2 amount)
|
|
||||||
{
|
|
||||||
var transferAttempt = new SolutionTransferAttemptEvent(sourceEntity, targetEntity);
|
|
||||||
|
|
||||||
// Check if the source is cancelling the transfer
|
|
||||||
RaiseLocalEvent(sourceEntity, transferAttempt, broadcast: true);
|
|
||||||
if (transferAttempt.Cancelled)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(transferAttempt.CancelReason!, sourceEntity, user);
|
|
||||||
return FixedPoint2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceSolution = source.Comp.Solution;
|
|
||||||
if (sourceSolution.Volume == 0)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)), sourceEntity, user);
|
|
||||||
return FixedPoint2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the target is cancelling the transfer
|
|
||||||
RaiseLocalEvent(targetEntity, transferAttempt, broadcast: true);
|
|
||||||
if (transferAttempt.Cancelled)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(transferAttempt.CancelReason!, sourceEntity, user);
|
|
||||||
return FixedPoint2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetSolution = target.Comp.Solution;
|
|
||||||
if (targetSolution.AvailableVolume == 0)
|
|
||||||
{
|
|
||||||
_popupSystem.PopupEntity(Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity)), targetEntity, user);
|
|
||||||
return FixedPoint2.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(sourceSolution.Volume, targetSolution.AvailableVolume));
|
|
||||||
|
|
||||||
var solution = _solutionContainerSystem.Drain(sourceEntity, source, actualAmount);
|
|
||||||
_solutionContainerSystem.Refill(targetEntity, target, solution);
|
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
|
||||||
$"{EntityManager.ToPrettyString(user):player} transferred {string.Join(", ", solution.Contents)} to {EntityManager.ToPrettyString(targetEntity):entity}, which now contains {SolutionContainerSystem.ToPrettyString(targetSolution)}");
|
|
||||||
|
|
||||||
return actualAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raised when attempting to transfer from one solution to another.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class SolutionTransferAttemptEvent : CancellableEntityEventArgs
|
|
||||||
{
|
|
||||||
public SolutionTransferAttemptEvent(EntityUid from, EntityUid to)
|
|
||||||
{
|
|
||||||
From = from;
|
|
||||||
To = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityUid From { get; }
|
|
||||||
public EntityUid To { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Why the transfer has been cancelled.
|
|
||||||
/// </summary>
|
|
||||||
public string? CancelReason { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cancels the transfer.
|
|
||||||
/// </summary>
|
|
||||||
public void Cancel(string reason)
|
|
||||||
{
|
|
||||||
base.Cancel();
|
|
||||||
CancelReason = reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
@@ -13,6 +13,7 @@ using Content.Shared._White;
|
|||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Server.Connection
|
namespace Content.Server.Connection
|
||||||
@@ -21,6 +22,18 @@ namespace Content.Server.Connection
|
|||||||
{
|
{
|
||||||
void Initialize();
|
void Initialize();
|
||||||
Task<bool> HavePrivilegedJoin(NetUserId userId); // WD-EDIT
|
Task<bool> HavePrivilegedJoin(NetUserId userId); // WD-EDIT
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Temporarily allow a user to bypass regular connection requirements.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The specified user will be allowed to bypass regular player cap,
|
||||||
|
/// whitelist and panic bunker restrictions for <paramref name="duration"/>.
|
||||||
|
/// Bans are not bypassed.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="user">The user to give a temporary bypass.</param>
|
||||||
|
/// <param name="duration">How long the bypass should last for.</param>
|
||||||
|
void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -35,19 +48,36 @@ namespace Content.Server.Connection
|
|||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||||
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
|
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
|
||||||
|
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
|
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
|
||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
|
_sawmill = _logManager.GetSawmill("connections");
|
||||||
|
|
||||||
_netMgr.Connecting += NetMgrOnConnecting;
|
_netMgr.Connecting += NetMgrOnConnecting;
|
||||||
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
||||||
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
|
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
|
||||||
// _netMgr.HandleApprovalCallback = HandleApproval;
|
// _netMgr.HandleApprovalCallback = HandleApproval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddTemporaryConnectBypass(NetUserId user, TimeSpan duration)
|
||||||
|
{
|
||||||
|
ref var time = ref CollectionsMarshal.GetValueRefOrAddDefault(_temporaryBypasses, user, out _);
|
||||||
|
var newTime = _gameTiming.RealTime + duration;
|
||||||
|
// Make sure we only update the time if we wouldn't shrink it.
|
||||||
|
if (newTime > time)
|
||||||
|
time = newTime;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
|
private async Task<NetApproval> HandleApproval(NetApprovalEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -117,6 +147,20 @@ namespace Content.Server.Connection
|
|||||||
hwId = null;
|
hwId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
|
||||||
|
if (bans.Count > 0 && bans.Any(x=> x.ExpirationTime == null)) // Miracle Edit
|
||||||
|
{
|
||||||
|
var firstBan = bans[0];
|
||||||
|
var message = firstBan.FormatBanMessage(_cfg, _loc);
|
||||||
|
return (ConnectionDenyReason.Ban, message, bans);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasTemporaryBypass(userId))
|
||||||
|
{
|
||||||
|
_sawmill.Verbose("User {UserId} has temporary bypass, skipping further connection checks", userId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
|
var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
|
||||||
|
|
||||||
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
|
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
|
||||||
@@ -175,14 +219,6 @@ namespace Content.Server.Connection
|
|||||||
}
|
}
|
||||||
//WD-EDIT
|
//WD-EDIT
|
||||||
|
|
||||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false, _cfg.GetCVar(CCVars.AdminLogsServerName));
|
|
||||||
if (bans.Count > 0 && bans.Any(x=> x.ExpirationTime == null)) //Miracle edit
|
|
||||||
{
|
|
||||||
var firstBan = bans[0];
|
|
||||||
var message = firstBan.FormatBanMessage(_cfg, _loc);
|
|
||||||
return (ConnectionDenyReason.Ban, message, bans);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
|
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
|
||||||
{
|
{
|
||||||
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
|
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
|
||||||
@@ -203,6 +239,11 @@ namespace Content.Server.Connection
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasTemporaryBypass(NetUserId user)
|
||||||
|
{
|
||||||
|
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<NetUserId?> AssignUserIdCallback(string name)
|
private async Task<NetUserId?> AssignUserIdCallback(string name)
|
||||||
{
|
{
|
||||||
if (!_cfg.GetCVar(CCVars.GamePersistGuests))
|
if (!_cfg.GetCVar(CCVars.GamePersistGuests))
|
||||||
@@ -227,8 +268,7 @@ namespace Content.Server.Connection
|
|||||||
var adminData = await _dbManager.GetAdminDataForAsync(userId);
|
var adminData = await _dbManager.GetAdminDataForAsync(userId);
|
||||||
|
|
||||||
var havePriorityJoin = _sponsorsManager.TryGetInfo(userId, out var sponsorData) && sponsorData.HavePriorityJoin;
|
var havePriorityJoin = _sponsorsManager.TryGetInfo(userId, out var sponsorData) && sponsorData.HavePriorityJoin;
|
||||||
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
|
var wasInGame = _gameTicker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
||||||
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
|
|
||||||
status == PlayerGameStatus.JoinedGame;
|
status == PlayerGameStatus.JoinedGame;
|
||||||
return adminData != null ||
|
return adminData != null ||
|
||||||
havePriorityJoin ||
|
havePriorityJoin ||
|
||||||
|
|||||||
60
Content.Server/Connection/GrantConnectBypassCommand.cs
Normal file
60
Content.Server/Connection/GrantConnectBypassCommand.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Server.Connection;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Admin)]
|
||||||
|
public sealed class GrantConnectBypassCommand : LocalizedCommands
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan DefaultDuration = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
[Dependency] private readonly IConnectionManager _connectionManager = default!;
|
||||||
|
|
||||||
|
public override string Command => "grant_connect_bypass";
|
||||||
|
|
||||||
|
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length is not (1 or 2))
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-invalid-args"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var argPlayer = args[0];
|
||||||
|
var info = await _playerLocator.LookupIdByNameOrIdAsync(argPlayer);
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
shell.WriteError(Loc.GetString("cmd-grant_connect_bypass-unknown-user", ("user", argPlayer)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration = DefaultDuration;
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
var argDuration = args[2];
|
||||||
|
if (!uint.TryParse(argDuration, out var minutes))
|
||||||
|
{
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-invalid-duration", ("duration", argDuration)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = TimeSpan.FromMinutes(minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectionManager.AddTemporaryConnectBypass(info.UserId, duration);
|
||||||
|
shell.WriteLine(Loc.GetString("cmd-grant_connect_bypass-success", ("user", argPlayer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length == 1)
|
||||||
|
return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-user"));
|
||||||
|
|
||||||
|
if (args.Length == 2)
|
||||||
|
return CompletionResult.FromHint(Loc.GetString("cmd-grant_connect_bypass-arg-duration"));
|
||||||
|
|
||||||
|
return CompletionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using Content.Shared.Construction;
|
|
||||||
using Content.Shared.Examine;
|
|
||||||
using Content.Shared.Toilet;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.Construction.Conditions
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
[DataDefinition]
|
|
||||||
public sealed partial class ToiletLidClosed : IGraphCondition
|
|
||||||
{
|
|
||||||
public bool Condition(EntityUid uid, IEntityManager entityManager)
|
|
||||||
{
|
|
||||||
if (!entityManager.TryGetComponent(uid, out ToiletComponent? toilet))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return !toilet.LidOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DoExamine(ExaminedEvent args)
|
|
||||||
{
|
|
||||||
var entity = args.Examined;
|
|
||||||
|
|
||||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out ToiletComponent? toilet)) return false;
|
|
||||||
if (!toilet.LidOpen) return false;
|
|
||||||
|
|
||||||
args.PushMarkup(Loc.GetString("construction-examine-condition-toilet-lid-closed") + "\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
|
|
||||||
{
|
|
||||||
yield return new ConstructionGuideEntry()
|
|
||||||
{
|
|
||||||
Localization = "construction-step-condition-toilet-lid-closed"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,11 @@ namespace Content.Server.Damage.Components;
|
|||||||
[RegisterComponent, Access(typeof(DamagePopupSystem))]
|
[RegisterComponent, Access(typeof(DamagePopupSystem))]
|
||||||
public sealed partial class DamagePopupComponent : Component
|
public sealed partial class DamagePopupComponent : Component
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Bool that will be used to determine if the popup type can be changed with a left click.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("allowTypeChange")] [ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool AllowTypeChange = false;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum that will be used to determine the type of damage popup displayed.
|
/// Enum that will be used to determine the type of damage popup displayed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
using System.Linq;
|
||||||
using Content.Server.Damage.Components;
|
using Content.Server.Damage.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Robust.Shared.Player;
|
using Content.Shared.Interaction;
|
||||||
|
|
||||||
namespace Content.Server.Damage.Systems;
|
namespace Content.Server.Damage.Systems;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ public sealed class DamagePopupSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<DamagePopupComponent, DamageChangedEvent>(OnDamageChange);
|
SubscribeLocalEvent<DamagePopupComponent, DamageChangedEvent>(OnDamageChange);
|
||||||
|
SubscribeLocalEvent<DamagePopupComponent, InteractHandEvent>(OnInteractHand);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
|
private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
|
||||||
@@ -38,4 +40,20 @@ public sealed class DamagePopupSystem : EntitySystem
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnInteractHand(EntityUid uid, DamagePopupComponent component, InteractHandEvent args)
|
||||||
|
{
|
||||||
|
if (component.AllowTypeChange)
|
||||||
|
{
|
||||||
|
if (component.Type == Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().Last())
|
||||||
|
{
|
||||||
|
component.Type = Enum.GetValues(typeof(DamagePopupType)).Cast<DamagePopupType>().First();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.Type = (DamagePopupType) (int) component.Type + 1;
|
||||||
|
}
|
||||||
|
_popupSystem.PopupEntity("Target set to type: " + component.Type.ToString(), uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
|
|
||||||
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
namespace Content.Server.Destructible.Thresholds.Behaviors;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ public sealed partial class SignalTimerComponent : Component
|
|||||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
public string Label = string.Empty;
|
public string Label = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default max width of a label (how many letters can this render?)
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int MaxLength = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The port that gets signaled when the timer triggers.
|
/// The port that gets signaled when the timer triggers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public sealed class SignalTimerSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
|
private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
|
||||||
{
|
{
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.DefaultText, component.Label);
|
||||||
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
||||||
_signalSystem.EnsureSinkPorts(uid, component.Trigger);
|
_signalSystem.EnsureSinkPorts(uid, component.Trigger);
|
||||||
}
|
}
|
||||||
@@ -74,11 +75,6 @@ public sealed class SignalTimerSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
RemComp<ActiveSignalTimerComponent>(uid);
|
RemComp<ActiveSignalTimerComponent>(uid);
|
||||||
|
|
||||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
|
||||||
{
|
|
||||||
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, signalTimer.Label, appearance);
|
|
||||||
}
|
|
||||||
|
|
||||||
var announceMessage = signalTimer.Label;
|
var announceMessage = signalTimer.Label;
|
||||||
if (string.IsNullOrWhiteSpace(announceMessage)) { announceMessage = Loc.GetString("label-none"); }
|
if (string.IsNullOrWhiteSpace(announceMessage)) { announceMessage = Loc.GetString("label-none"); }
|
||||||
|
|
||||||
@@ -152,10 +148,15 @@ public sealed class SignalTimerSystem : EntitySystem
|
|||||||
if (!IsMessageValid(uid, args))
|
if (!IsMessageValid(uid, args))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
component.Label = args.Text[..Math.Min(5, args.Text.Length)];
|
component.Label = args.Text[..Math.Min(component.MaxLength, args.Text.Length)];
|
||||||
|
|
||||||
if (!HasComp<ActiveSignalTimerComponent>(uid))
|
if (!HasComp<ActiveSignalTimerComponent>(uid))
|
||||||
|
{
|
||||||
|
// could maybe move the defaulttext update out of this block,
|
||||||
|
// if you delved deep into appearance update batching
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.DefaultText, component.Label);
|
||||||
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
_appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -183,7 +184,14 @@ public sealed class SignalTimerSystem : EntitySystem
|
|||||||
if (!IsMessageValid(uid, args))
|
if (!IsMessageValid(uid, args))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnStartTimer(uid, component);
|
// feedback received: pressing the timer button while a timer is running should cancel the timer.
|
||||||
|
if (HasComp<ActiveSignalTimerComponent>(uid))
|
||||||
|
{
|
||||||
|
_appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, _gameTiming.CurTime);
|
||||||
|
Trigger(uid, component);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
OnStartTimer(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSignalReceived(EntityUid uid, SignalTimerComponent component, ref SignalReceivedEvent args)
|
private void OnSignalReceived(EntityUid uid, SignalTimerComponent component, ref SignalReceivedEvent args)
|
||||||
|
|||||||
@@ -136,8 +136,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
|||||||
{
|
{
|
||||||
// This is not an interaction, activation, or alternative verb type because unfortunately most users are
|
// This is not an interaction, activation, or alternative verb type because unfortunately most users are
|
||||||
// unwilling to accept that this is where they belong and don't want to accidentally climb inside.
|
// unwilling to accept that this is where they belong and don't want to accidentally climb inside.
|
||||||
if (!component.MobsCanEnter ||
|
if (!args.CanAccess ||
|
||||||
!args.CanAccess ||
|
|
||||||
!args.CanInteract ||
|
!args.CanInteract ||
|
||||||
component.Container.ContainedEntities.Contains(args.User) ||
|
component.Container.ContainedEntities.Contains(args.User) ||
|
||||||
!_actionBlockerSystem.CanMove(args.User))
|
!_actionBlockerSystem.CanMove(args.User))
|
||||||
@@ -628,10 +627,10 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case DisposalsPressureState.Flushed:
|
case DisposalsPressureState.Flushed:
|
||||||
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Flushing, appearance);
|
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.OverlayFlushing, appearance);
|
||||||
break;
|
break;
|
||||||
case DisposalsPressureState.Pressurizing:
|
case DisposalsPressureState.Pressurizing:
|
||||||
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Charging, appearance);
|
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.OverlayCharging, appearance);
|
||||||
break;
|
break;
|
||||||
case DisposalsPressureState.Ready:
|
case DisposalsPressureState.Ready:
|
||||||
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Anchored, appearance);
|
_appearance.SetData(uid, SharedDisposalUnitComponent.Visuals.VisualState, SharedDisposalUnitComponent.VisualState.Anchored, appearance);
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Doors.Electronics;
|
||||||
|
using Content.Shared.Access;
|
||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Access.Systems;
|
||||||
|
using Content.Shared.DeviceNetwork.Components;
|
||||||
|
using Content.Shared.Doors.Electronics;
|
||||||
|
using Content.Shared.Doors;
|
||||||
|
using Content.Shared.Interaction;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Doors.Electronics;
|
||||||
|
|
||||||
|
public sealed class DoorElectronicsSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||||
|
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<DoorElectronicsComponent, DoorElectronicsUpdateConfigurationMessage>(OnChangeConfiguration);
|
||||||
|
SubscribeLocalEvent<DoorElectronicsComponent, AccessReaderConfigurationChangedEvent>(OnAccessReaderChanged);
|
||||||
|
SubscribeLocalEvent<DoorElectronicsComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateUserInterface(EntityUid uid, DoorElectronicsComponent component)
|
||||||
|
{
|
||||||
|
var accesses = new List<ProtoId<AccessLevelPrototype>>();
|
||||||
|
|
||||||
|
if (TryComp<AccessReaderComponent>(uid, out var accessReader))
|
||||||
|
{
|
||||||
|
foreach (var accessList in accessReader.AccessLists)
|
||||||
|
{
|
||||||
|
var access = accessList.FirstOrDefault();
|
||||||
|
accesses.Add(access);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = new DoorElectronicsConfigurationState(accesses);
|
||||||
|
_uiSystem.TrySetUiState(uid, DoorElectronicsConfigurationUiKey.Key, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChangeConfiguration(
|
||||||
|
EntityUid uid,
|
||||||
|
DoorElectronicsComponent component,
|
||||||
|
DoorElectronicsUpdateConfigurationMessage args)
|
||||||
|
{
|
||||||
|
var accessReader = EnsureComp<AccessReaderComponent>(uid);
|
||||||
|
_accessReader.SetAccesses(uid, accessReader, args.AccessList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAccessReaderChanged(
|
||||||
|
EntityUid uid,
|
||||||
|
DoorElectronicsComponent component,
|
||||||
|
AccessReaderConfigurationChangedEvent args)
|
||||||
|
{
|
||||||
|
UpdateUserInterface(uid, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBoundUIOpened(
|
||||||
|
EntityUid uid,
|
||||||
|
DoorElectronicsComponent component,
|
||||||
|
BoundUIOpenedEvent args)
|
||||||
|
{
|
||||||
|
UpdateUserInterface(uid, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user