Compare commits
1 Commits
magick
...
ed_worldev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8ce6d0141 |
@@ -9,7 +9,7 @@ indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf:suggestion
|
||||
#end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -104,7 +104,6 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class SpawnEquipDeleteBenchmark
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
|
||||
_spawnSys.EquipStartingGear(_entity, _gear);
|
||||
_spawnSys.EquipStartingGear(_entity, _gear, null);
|
||||
server.EntMan.DeleteEntity(_entity);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,20 +9,20 @@ namespace Content.Client.Access;
|
||||
|
||||
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 SharedTransformSystem _transformSystem;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedTransformSystem _xform;
|
||||
private readonly Font _font;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
|
||||
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_transformSystem = transformSystem;
|
||||
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
|
||||
_entityManager = entManager;
|
||||
_lookup = lookup;
|
||||
_xform = xform;
|
||||
|
||||
_font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -30,65 +30,52 @@ public sealed class AccessOverlay : Overlay
|
||||
if (args.ViewportControl == null)
|
||||
return;
|
||||
|
||||
var textBuffer = new StringBuilder();
|
||||
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var accessReader, out var transform))
|
||||
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
|
||||
LookupFlags.Static | LookupFlags.Approximate))
|
||||
{
|
||||
textBuffer.Clear();
|
||||
|
||||
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)
|
||||
if (!readerQuery.TryGetComponent(ent, out var reader) ||
|
||||
!xformQuery.TryGetComponent(ent, out var xform))
|
||||
{
|
||||
textBuffer.AppendLine("-Disabled");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accessReader.AccessLists.Count > 0)
|
||||
var text = new StringBuilder();
|
||||
var index = 0;
|
||||
var a = $"{_entityManager.ToPrettyString(ent)}";
|
||||
text.Append(a);
|
||||
|
||||
foreach (var list in reader.AccessLists)
|
||||
{
|
||||
var groupNumber = 0;
|
||||
foreach (var accessList in accessReader.AccessLists)
|
||||
a = $"Tag {index}";
|
||||
text.AppendLine(a);
|
||||
|
||||
foreach (var entry in list)
|
||||
{
|
||||
groupNumber++;
|
||||
foreach (var entry in accessList)
|
||||
{
|
||||
textBuffer.Append("+Set ");
|
||||
textBuffer.Append(groupNumber);
|
||||
textBuffer.Append(": ");
|
||||
textBuffer.Append(entry.Id);
|
||||
textBuffer.AppendLine();
|
||||
}
|
||||
a = $"- {entry}";
|
||||
text.AppendLine(a);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
string textStr;
|
||||
|
||||
if (text.Length >= 2)
|
||||
{
|
||||
textStr = text.ToString();
|
||||
textStr = textStr[..^2];
|
||||
}
|
||||
else
|
||||
{
|
||||
textBuffer.AppendLine("+Unrestricted");
|
||||
textStr = "";
|
||||
}
|
||||
|
||||
foreach (var key in accessReader.AccessKeys)
|
||||
{
|
||||
textBuffer.Append("+Key ");
|
||||
textBuffer.Append(key.OriginStation);
|
||||
textBuffer.Append(": ");
|
||||
textBuffer.Append(key.Id);
|
||||
textBuffer.AppendLine();
|
||||
}
|
||||
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
|
||||
|
||||
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);
|
||||
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,8 @@ namespace Content.Client.Access.Commands;
|
||||
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showaccessreaders";
|
||||
|
||||
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 string Description => "Shows all access readers in the viewport";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var collection = IoCManager.Instance;
|
||||
@@ -34,9 +26,10 @@ public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||
|
||||
var entManager = collection.Resolve<IEntityManager>();
|
||||
var cache = collection.Resolve<IResourceCache>();
|
||||
var lookup = entManager.System<EntityLookupSystem>();
|
||||
var xform = entManager.System<SharedTransformSystem>();
|
||||
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
|
||||
shell.WriteLine($"Set access reader debug overlay to true");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new BountyPrintLabelMessage(id));
|
||||
};
|
||||
|
||||
_menu.OnSkipButtonPressed += id =>
|
||||
{
|
||||
SendMessage(new BountySkipMessage(id));
|
||||
};
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
@@ -42,7 +37,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
||||
if (message is not CargoBountyConsoleState state)
|
||||
return;
|
||||
|
||||
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
|
||||
_menu?.UpdateEntries(state.Bounties);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -13,18 +13,7 @@
|
||||
</BoxContainer>
|
||||
<Control MinWidth="10"/>
|
||||
<BoxContainer Orientation="Vertical" MinWidth="120">
|
||||
<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>
|
||||
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
|
||||
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
@@ -16,19 +14,15 @@ public sealed partial class BountyEntry : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public Action? OnLabelButtonPressed;
|
||||
public Action? OnSkipButtonPressed;
|
||||
public Action? OnButtonPressed;
|
||||
|
||||
public TimeSpan EndTime;
|
||||
public TimeSpan UntilNextSkip;
|
||||
|
||||
public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
|
||||
public BountyEntry(CargoBountyData bounty)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
UntilNextSkip = untilNextSkip;
|
||||
|
||||
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
@@ -44,27 +38,6 @@ public sealed partial class BountyEntry : BoxContainer
|
||||
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
|
||||
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
|
||||
|
||||
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);
|
||||
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,19 @@ namespace Content.Client.Cargo.UI;
|
||||
public sealed partial class CargoBountyMenu : FancyWindow
|
||||
{
|
||||
public Action<string>? OnLabelButtonPressed;
|
||||
public Action<string>? OnSkipButtonPressed;
|
||||
|
||||
public CargoBountyMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||
public void UpdateEntries(List<CargoBountyData> bounties)
|
||||
{
|
||||
BountyEntriesContainer.Children.Clear();
|
||||
foreach (var b in bounties)
|
||||
{
|
||||
var entry = new BountyEntry(b, untilNextSkip);
|
||||
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
|
||||
var entry = new BountyEntry(b);
|
||||
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||
|
||||
BountyEntriesContainer.AddChild(entry);
|
||||
}
|
||||
|
||||
@@ -36,20 +36,13 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
{Jumpsuit, "INNERCLOTHING"},
|
||||
{"neck", "NECK"},
|
||||
{"back", "BACKPACK"},
|
||||
{"belt1", "BELT1"},
|
||||
{"belt2", "BELT2"},
|
||||
{"belt", "BELT"},
|
||||
{"gloves", "HAND"},
|
||||
{"shoes", "FEET"},
|
||||
{"id", "IDCARD"},
|
||||
{"pocket1", "POCKET1"},
|
||||
{"pocket2", "POCKET2"},
|
||||
{"suitstorage", "SUITSTORAGE"},
|
||||
{"ring1", "RING1"},
|
||||
{"ring2", "RING2"},
|
||||
{"pants", "PANTS"},
|
||||
{"shirt", "SHIRT"},
|
||||
{"cloak", "CLOAK"},
|
||||
{"keys", "KEYS"},
|
||||
};
|
||||
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
||||
namespace Content.Client.Clothing.Systems;
|
||||
|
||||
public sealed class WaddleClothingSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WaddleWhenWornComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<WaddleWhenWornComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args)
|
||||
{
|
||||
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Equipee);
|
||||
|
||||
waddleAnimComp.AnimationLength = comp.AnimationLength;
|
||||
waddleAnimComp.HopIntensity = comp.HopIntensity;
|
||||
waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
|
||||
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args)
|
||||
{
|
||||
RemComp<WaddleAnimationComponent>(args.Equipee);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Ame.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Client;
|
||||
@@ -158,7 +157,7 @@ namespace Content.Client.Entry
|
||||
_clientPreferencesManager.Initialize();
|
||||
_euiManager.Initialize();
|
||||
_voteManager.Initialize();
|
||||
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme));
|
||||
_userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
|
||||
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
|
||||
_documentParsingManager.Initialize();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
@@ -80,7 +79,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
);
|
||||
|
||||
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
|
||||
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
|
||||
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
|
||||
);
|
||||
|
||||
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
|
||||
|
||||
@@ -21,7 +21,6 @@ using Content.Shared.Module;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Replay;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
|
||||
|
||||
namespace Content.Client.IoC
|
||||
@@ -30,29 +29,26 @@ namespace Content.Client.IoC
|
||||
{
|
||||
public static void Register()
|
||||
{
|
||||
var collection = IoCManager.Instance!;
|
||||
|
||||
collection.Register<IParallaxManager, ParallaxManager>();
|
||||
collection.Register<IChatManager, ChatManager>();
|
||||
collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
|
||||
collection.Register<IStylesheetManager, StylesheetManager>();
|
||||
collection.Register<IScreenshotHook, ScreenshotHook>();
|
||||
collection.Register<FullscreenHook, FullscreenHook>();
|
||||
collection.Register<IClickMapManager, ClickMapManager>();
|
||||
collection.Register<IClientAdminManager, ClientAdminManager>();
|
||||
collection.Register<ISharedAdminManager, ClientAdminManager>();
|
||||
collection.Register<EuiManager, EuiManager>();
|
||||
collection.Register<IVoteManager, VoteManager>();
|
||||
collection.Register<ChangelogManager, ChangelogManager>();
|
||||
collection.Register<RulesManager, RulesManager>();
|
||||
collection.Register<ViewportManager, ViewportManager>();
|
||||
collection.Register<ISharedAdminLogManager, SharedAdminLogManager>();
|
||||
collection.Register<GhostKickManager>();
|
||||
collection.Register<ExtendedDisconnectInformationManager>();
|
||||
collection.Register<JobRequirementsManager>();
|
||||
collection.Register<DocumentParsingManager>();
|
||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||
IoCManager.Register<IParallaxManager, ParallaxManager>();
|
||||
IoCManager.Register<IChatManager, ChatManager>();
|
||||
IoCManager.Register<IClientPreferencesManager, ClientPreferencesManager>();
|
||||
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
||||
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
||||
IoCManager.Register<FullscreenHook, FullscreenHook>();
|
||||
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
||||
IoCManager.Register<IClientAdminManager, ClientAdminManager>();
|
||||
IoCManager.Register<ISharedAdminManager, ClientAdminManager>();
|
||||
IoCManager.Register<EuiManager, EuiManager>();
|
||||
IoCManager.Register<IVoteManager, VoteManager>();
|
||||
IoCManager.Register<ChangelogManager, ChangelogManager>();
|
||||
IoCManager.Register<RulesManager, RulesManager>();
|
||||
IoCManager.Register<ViewportManager, ViewportManager>();
|
||||
IoCManager.Register<ISharedAdminLogManager, SharedAdminLogManager>();
|
||||
IoCManager.Register<GhostKickManager>();
|
||||
IoCManager.Register<ExtendedDisconnectInformationManager>();
|
||||
IoCManager.Register<JobRequirementsManager>();
|
||||
IoCManager.Register<DocumentParsingManager>();
|
||||
IoCManager.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,12 +104,41 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
RecipeList.Children.Clear();
|
||||
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
|
||||
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
|
||||
: _spriteSystem.Frame0(prototype.Icon);
|
||||
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
|
||||
|
||||
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
|
||||
var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
|
||||
control.OnButtonPressed += s =>
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
@@ -120,51 +149,6 @@ 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()
|
||||
{
|
||||
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();
|
||||
|
||||
@@ -11,16 +11,17 @@ namespace Content.Client.Lathe.UI;
|
||||
public sealed partial class RecipeControl : Control
|
||||
{
|
||||
public Action<string>? OnButtonPressed;
|
||||
public Func<string> TooltipTextSupplier;
|
||||
|
||||
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Texture? texture = null)
|
||||
public string TooltipText;
|
||||
|
||||
public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = recipe.Name;
|
||||
RecipeTexture.Texture = texture;
|
||||
Button.Disabled = !canProduce;
|
||||
TooltipTextSupplier = tooltipTextSupplier;
|
||||
TooltipText = tooltip;
|
||||
Button.TooltipSupplier = SupplyTooltip;
|
||||
|
||||
Button.OnPressed += (_) =>
|
||||
@@ -31,6 +32,6 @@ public sealed partial class RecipeControl : Control
|
||||
|
||||
private Control? SupplyTooltip(Control sender)
|
||||
{
|
||||
return new RecipeTooltip(TooltipTextSupplier());
|
||||
return new RecipeTooltip(TooltipText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,19 +64,13 @@ namespace Content.Client.Lobby
|
||||
|
||||
_characterSetup.CloseButton.OnPressed += _ =>
|
||||
{
|
||||
// Reset sliders etc.
|
||||
_characterSetup?.UpdateControls();
|
||||
|
||||
var controller = _userInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.SetClothes(true);
|
||||
controller.UpdateProfile();
|
||||
_lobby.SwitchState(LobbyGui.LobbyGuiState.Default);
|
||||
};
|
||||
|
||||
_characterSetup.SaveButton.OnPressed += _ =>
|
||||
{
|
||||
_characterSetup.Save();
|
||||
_userInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
|
||||
_lobby.CharacterPreview.UpdateUI();
|
||||
};
|
||||
|
||||
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
|
||||
@@ -90,6 +84,10 @@ namespace Content.Client.Lobby
|
||||
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
|
||||
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
|
||||
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
|
||||
|
||||
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
|
||||
|
||||
_lobby.CharacterPreview.UpdateUI();
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
@@ -111,6 +109,13 @@ namespace Content.Client.Lobby
|
||||
|
||||
_characterSetup?.Dispose();
|
||||
_characterSetup = null;
|
||||
|
||||
_preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
|
||||
}
|
||||
|
||||
private void PreferencesDataLoaded()
|
||||
{
|
||||
_lobby?.CharacterPreview.UpdateUI();
|
||||
}
|
||||
|
||||
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Preferences.UI;
|
||||
using Content.Client.Station;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Lobby;
|
||||
|
||||
public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
|
||||
{
|
||||
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
|
||||
private LobbyCharacterPreviewPanel? _previewPanel;
|
||||
|
||||
private bool _showClothes = true;
|
||||
|
||||
/*
|
||||
* Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor
|
||||
* that is shared too.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Preview dummy for role gear.
|
||||
/// </summary>
|
||||
private EntityUid? _previewDummy;
|
||||
|
||||
/// <summary>
|
||||
/// If we currently have a job prototype selected.
|
||||
/// </summary>
|
||||
private JobPrototype? _dummyJob;
|
||||
|
||||
// TODO: Load the species directly and don't update entity ever.
|
||||
public event Action<EntityUid>? PreviewDummyUpdated;
|
||||
|
||||
private HumanoidCharacterProfile? _profile;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
|
||||
}
|
||||
|
||||
private void PreferencesDataLoaded()
|
||||
{
|
||||
UpdateProfile();
|
||||
}
|
||||
|
||||
public void OnStateEntered(LobbyState state)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStateExited(LobbyState state)
|
||||
{
|
||||
EntityManager.DeleteEntity(_previewDummy);
|
||||
_previewDummy = null;
|
||||
}
|
||||
|
||||
public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel)
|
||||
{
|
||||
_previewPanel = panel;
|
||||
ReloadProfile();
|
||||
}
|
||||
|
||||
public void SetClothes(bool value)
|
||||
{
|
||||
if (_showClothes == value)
|
||||
return;
|
||||
|
||||
_showClothes = value;
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
public void SetDummyJob(JobPrototype? job)
|
||||
{
|
||||
_dummyJob = job;
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the character only with the specified profile change.
|
||||
/// </summary>
|
||||
public void ReloadProfile()
|
||||
{
|
||||
// Test moment
|
||||
if (_profile == null || _stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
// Ignore job clothes and the likes so we don't spam entities out every frame of color changes.
|
||||
var previewDummy = EnsurePreviewDummy(_profile);
|
||||
_humanoid.LoadProfile(previewDummy, _profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the currently selected character's preview.
|
||||
/// </summary>
|
||||
public void ReloadCharacterUI()
|
||||
{
|
||||
// Test moment
|
||||
if (_profile == null || _stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
EntityManager.DeleteEntity(_previewDummy);
|
||||
_previewDummy = null;
|
||||
_previewDummy = EnsurePreviewDummy(_profile);
|
||||
_previewPanel?.SetSprite(_previewDummy.Value);
|
||||
_previewPanel?.SetSummaryText(_profile.Summary);
|
||||
_humanoid.LoadProfile(_previewDummy.Value, _profile);
|
||||
|
||||
if (_showClothes)
|
||||
GiveDummyJobClothesLoadout(_previewDummy.Value, _profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates character profile to the default.
|
||||
/// </summary>
|
||||
public void UpdateProfile()
|
||||
{
|
||||
if (!_preferencesManager.ServerDataLoaded)
|
||||
{
|
||||
_profile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter)
|
||||
{
|
||||
_profile = selectedCharacter;
|
||||
_previewPanel?.SetLoaded(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_previewPanel?.SetSummaryText(string.Empty);
|
||||
_previewPanel?.SetLoaded(false);
|
||||
}
|
||||
|
||||
ReloadCharacterUI();
|
||||
}
|
||||
|
||||
public void UpdateProfile(HumanoidCharacterProfile? profile)
|
||||
{
|
||||
if (_profile?.Equals(profile) == true)
|
||||
return;
|
||||
|
||||
if (_stateManager.CurrentState is not LobbyState)
|
||||
return;
|
||||
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile)
|
||||
{
|
||||
if (_previewDummy != null)
|
||||
return _previewDummy.Value;
|
||||
|
||||
_previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(profile.Species).DollPrototype, MapCoordinates.Nullspace);
|
||||
PreviewDummyUpdated?.Invoke(_previewDummy.Value);
|
||||
return _previewDummy.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the highest priority job's clothes to the dummy.
|
||||
/// </summary>
|
||||
public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
|
||||
{
|
||||
var job = _dummyJob ?? GetPreferredJob(profile);
|
||||
GiveDummyJobClothes(dummy, profile, job);
|
||||
|
||||
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
|
||||
{
|
||||
var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager);
|
||||
GiveDummyLoadout(dummy, loadout);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the highest priority job for the profile.
|
||||
/// </summary>
|
||||
public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
|
||||
{
|
||||
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
||||
return _prototypeManager.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
|
||||
}
|
||||
|
||||
public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout)
|
||||
{
|
||||
if (roleLoadout == null)
|
||||
return;
|
||||
|
||||
foreach (var group in roleLoadout.SelectedLoadouts.Values)
|
||||
{
|
||||
foreach (var loadout in group)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
_spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the specified job's clothes to the dummy.
|
||||
/// </summary>
|
||||
public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job)
|
||||
{
|
||||
if (!_inventory.TryGetSlots(dummy, out var slots))
|
||||
return;
|
||||
|
||||
// Apply loadout
|
||||
if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout))
|
||||
{
|
||||
foreach (var loadouts in jobLoadout.SelectedLoadouts.Values)
|
||||
{
|
||||
foreach (var loadout in loadouts)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto))
|
||||
continue;
|
||||
|
||||
// TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude.
|
||||
var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var itemType = loadoutGear.GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
EntityManager.DeleteEntity(unequippedItem.Value);
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
_inventory.TryEquip(dummy, item, slot.Name, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (job.StartingGear == null)
|
||||
return;
|
||||
|
||||
var gear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var itemType = gear.GetGear(slot.Name);
|
||||
|
||||
if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
EntityManager.DeleteEntity(unequippedItem.Value);
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
{
|
||||
var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
_inventory.TryEquip(dummy, item, slot.Name, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EntityUid? GetPreviewDummy()
|
||||
{
|
||||
return _previewDummy;
|
||||
}
|
||||
}
|
||||
166
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
Normal file
166
Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Alerts;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Lobby.UI
|
||||
{
|
||||
public sealed class LobbyCharacterPreviewPanel : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
|
||||
private EntityUid? _previewDummy;
|
||||
private readonly Label _summaryLabel;
|
||||
private readonly BoxContainer _loaded;
|
||||
private readonly BoxContainer _viewBox;
|
||||
private readonly Label _unloaded;
|
||||
|
||||
public LobbyCharacterPreviewPanel()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var header = new NanoHeading
|
||||
{
|
||||
Text = Loc.GetString("lobby-character-preview-panel-header")
|
||||
};
|
||||
|
||||
CharacterSetupButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(0, 5, 0, 0),
|
||||
};
|
||||
|
||||
_summaryLabel = new Label
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(3, 3),
|
||||
};
|
||||
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical
|
||||
};
|
||||
_unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
|
||||
|
||||
_loaded = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Visible = false
|
||||
};
|
||||
_viewBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
};
|
||||
var _vSpacer = new VSpacer();
|
||||
|
||||
_loaded.AddChild(_summaryLabel);
|
||||
_loaded.AddChild(_viewBox);
|
||||
_loaded.AddChild(_vSpacer);
|
||||
_loaded.AddChild(CharacterSetupButton);
|
||||
|
||||
vBox.AddChild(header);
|
||||
vBox.AddChild(_loaded);
|
||||
vBox.AddChild(_unloaded);
|
||||
AddChild(vBox);
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public Button CharacterSetupButton { get; }
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing) return;
|
||||
if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
|
||||
_previewDummy = default;
|
||||
}
|
||||
|
||||
public void UpdateUI()
|
||||
{
|
||||
if (!_preferencesManager.ServerDataLoaded)
|
||||
{
|
||||
_loaded.Visible = false;
|
||||
_unloaded.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_loaded.Visible = true;
|
||||
_unloaded.Visible = false;
|
||||
if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
|
||||
{
|
||||
_summaryLabel.Text = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
_previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index<SpeciesPrototype>(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
|
||||
_viewBox.DisposeAllChildren();
|
||||
var spriteView = new SpriteView
|
||||
{
|
||||
OverrideDirection = Direction.South,
|
||||
Scale = new Vector2(4f, 4f),
|
||||
MaxSize = new Vector2(112, 112),
|
||||
Stretch = SpriteView.StretchMode.Fill,
|
||||
};
|
||||
spriteView.SetEntity(_previewDummy.Value);
|
||||
_viewBox.AddChild(spriteView);
|
||||
_summaryLabel.Text = selectedCharacter.Summary;
|
||||
_entityManager.System<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
|
||||
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
|
||||
{
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var invSystem = EntitySystem.Get<ClientInventorySystem>();
|
||||
|
||||
var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
|
||||
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
|
||||
var job = protoMan.Index<JobPrototype>(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
|
||||
|
||||
if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
|
||||
{
|
||||
var gear = protoMan.Index<StartingGearPrototype>(job.StartingGear);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
var itemType = gear.GetGear(slot.Name, profile);
|
||||
|
||||
if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
|
||||
{
|
||||
entMan.DeleteEntity(unequippedItem.Value);
|
||||
}
|
||||
|
||||
if (itemType != string.Empty)
|
||||
{
|
||||
var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
|
||||
invSystem.TryEquip(dummy, item, slot.Name, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Name="VBox" Orientation="Vertical">
|
||||
<controls:NanoHeading Name="Header" Text="{Loc 'lobby-character-preview-panel-header'}">
|
||||
|
||||
</controls:NanoHeading>
|
||||
<BoxContainer Name="Loaded" Orientation="Vertical"
|
||||
Visible="False">
|
||||
<Label Name="Summary" HorizontalAlignment="Center" Margin="3 3"/>
|
||||
<BoxContainer Name="ViewBox" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
|
||||
</BoxContainer>
|
||||
<controls:VSpacer/>
|
||||
<Button Name="CharacterSetup" Text="{Loc 'lobby-character-preview-panel-character-setup-button'}"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 5 0 0"/>
|
||||
</BoxContainer>
|
||||
<Label Name="Unloaded" Text="{Loc 'lobby-character-preview-panel-unloaded-preferences-label'}"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Lobby.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LobbyCharacterPreviewPanel : Control
|
||||
{
|
||||
public Button CharacterSetupButton => CharacterSetup;
|
||||
|
||||
public LobbyCharacterPreviewPanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
UserInterfaceManager.GetUIController<LobbyUIController>().SetPreviewPanel(this);
|
||||
}
|
||||
|
||||
public void SetLoaded(bool value)
|
||||
{
|
||||
Loaded.Visible = value;
|
||||
Unloaded.Visible = !value;
|
||||
}
|
||||
|
||||
public void SetSummaryText(string value)
|
||||
{
|
||||
Summary.Text = string.Empty;
|
||||
}
|
||||
|
||||
public void SetSprite(EntityUid uid)
|
||||
{
|
||||
ViewBox.DisposeAllChildren();
|
||||
var spriteView = new SpriteView
|
||||
{
|
||||
OverrideDirection = Direction.South,
|
||||
Scale = new Vector2(4f, 4f),
|
||||
MaxSize = new Vector2(112, 112),
|
||||
Stretch = SpriteView.StretchMode.Fill,
|
||||
};
|
||||
spriteView.SetEntity(uid);
|
||||
ViewBox.AddChild(spriteView);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,23 @@
|
||||
using Content.Client.Chat.UI;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Preferences.UI;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Lobby.UI
|
||||
{
|
||||
|
||||
@@ -210,9 +210,9 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
|
||||
}
|
||||
|
||||
else if (sensor.DamagePercentage != null)
|
||||
else if (sensor.TotalDamage != null)
|
||||
{
|
||||
var index = MathF.Round(4f * sensor.DamagePercentage.Value);
|
||||
var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
|
||||
|
||||
if (index >= 5)
|
||||
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Buckle;
|
||||
using Content.Client.Gravity;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
|
||||
public sealed class WaddleAnimationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly GravitySystem _gravity = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
}
|
||||
|
||||
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
|
||||
{
|
||||
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
|
||||
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
|
||||
{
|
||||
var stopped = new StoppedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref stopped);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only start waddling if we're not currently AND we're actually moving.
|
||||
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
|
||||
return;
|
||||
|
||||
var started = new StartedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref started);
|
||||
}
|
||||
|
||||
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
|
||||
{
|
||||
if (_animation.HasRunningAnimation(uid, component.KeyName))
|
||||
return;
|
||||
|
||||
if (!TryComp<InputMoverComponent>(uid, out var mover))
|
||||
return;
|
||||
|
||||
if (_gravity.IsWeightless(uid))
|
||||
return;
|
||||
|
||||
|
||||
if (!_actionBlocker.CanMove(uid, mover))
|
||||
return;
|
||||
|
||||
// Do nothing if buckled in
|
||||
if (_buckle.IsBuckled(uid))
|
||||
return;
|
||||
|
||||
// Do nothing if crit or dead (for obvious reasons)
|
||||
if (_mobState.IsIncapacitated(uid))
|
||||
return;
|
||||
|
||||
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
|
||||
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
|
||||
|
||||
component.LastStep = !component.LastStep;
|
||||
component.IsCurrentlyWaddling = true;
|
||||
|
||||
var anim = new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(len),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Rotation),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
|
||||
}
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
|
||||
new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_animation.Play(uid, anim, component.KeyName);
|
||||
}
|
||||
|
||||
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
var started = new StartedWaddlingEvent(uid);
|
||||
|
||||
RaiseLocalEvent(uid, ref started);
|
||||
}
|
||||
|
||||
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
|
||||
{
|
||||
if (!component.IsCurrentlyWaddling)
|
||||
return;
|
||||
|
||||
_animation.Stop(uid, component.KeyName);
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.Offset = new Vector2();
|
||||
sprite.Rotation = Angle.FromDegrees(0);
|
||||
|
||||
component.IsCurrentlyWaddling = false;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,6 @@
|
||||
<Label Text="{Loc 'ui-options-general-speech'}"
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
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="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||
|
||||
@@ -3,14 +3,11 @@ using Content.Client.UserInterface.Screens;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.HUD;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||
|
||||
@@ -19,7 +16,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiscTab : Control
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
@@ -59,13 +55,8 @@ namespace Content.Client.Options.UI.Tabs
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
|
||||
HudThemeOption.OnItemSelected += OnHudThemeChanged;
|
||||
DiscordRich.OnToggled += OnCheckBoxToggled;
|
||||
ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
|
||||
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
@@ -82,7 +73,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
|
||||
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
|
||||
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
|
||||
ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
|
||||
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||
@@ -140,7 +130,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
|
||||
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
||||
@@ -169,7 +158,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
|
||||
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
|
||||
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 isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
@@ -187,7 +175,6 @@ namespace Content.Client.Options.UI.Tabs
|
||||
isShowHeldItemSame &&
|
||||
isCombatModeIndicatorsSame &&
|
||||
isOpaqueStorageWindow &&
|
||||
isOocPatronColorShowSame &&
|
||||
isLoocShowSame &&
|
||||
isFancyChatSame &&
|
||||
isFancyBackgroundSame &&
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Content.Client.Overlays;
|
||||
/// </summary>
|
||||
public sealed class EntityHealthBarOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly MobStateSystem _mobStateSystem;
|
||||
@@ -26,14 +27,17 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
private readonly ProgressColorSystem _progressColor;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
public HashSet<string> DamageContainers = new();
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public EntityHealthBarOverlay(IEntityManager entManager)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_entManager = entManager;
|
||||
_transform = _entManager.System<SharedTransformSystem>();
|
||||
_mobStateSystem = _entManager.System<MobStateSystem>();
|
||||
_mobThresholdSystem = _entManager.System<MobThresholdSystem>();
|
||||
_progressColor = _entManager.System<ProgressColorSystem>();
|
||||
_shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -46,6 +50,8 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
|
||||
var rotationMatrix = Matrix3.CreateRotation(-rotation);
|
||||
|
||||
handle.UseShader(_shader);
|
||||
|
||||
var query = _entManager.AllEntityQueryEnumerator<MobThresholdsComponent, MobStateComponent, DamageableComponent, SpriteComponent>();
|
||||
while (query.MoveNext(out var uid,
|
||||
out var mobThresholdsComponent,
|
||||
@@ -116,6 +122,7 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
|
||||
}
|
||||
|
||||
handle.UseShader(null);
|
||||
handle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,12 @@ using Robust.Client;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Players.PlayTimeTracking;
|
||||
|
||||
public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
||||
public sealed class JobRequirementsManager
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
@@ -134,13 +133,5 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, TimeSpan> GetPlayTimes(ICommonSession session)
|
||||
{
|
||||
if (session != _playerManager.LocalSession)
|
||||
{
|
||||
return new Dictionary<string, TimeSpan>();
|
||||
}
|
||||
|
||||
return _roles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,12 +184,6 @@ namespace Content.Client.Popups
|
||||
PopupEntity(message, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
PopupEntity(recipientMessage, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Event Handlers
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -18,7 +20,8 @@ namespace Content.Client.Preferences
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||
|
||||
public event Action? OnServerDataLoaded;
|
||||
|
||||
@@ -61,8 +64,7 @@ namespace Content.Client.Preferences
|
||||
|
||||
public void UpdateCharacter(ICharacterProfile profile, int slot)
|
||||
{
|
||||
var collection = IoCManager.Instance!;
|
||||
profile.EnsureValid(_playerManager.LocalSession!, collection);
|
||||
profile.EnsureValid(_cfg, _prototypes);
|
||||
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
|
||||
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
|
||||
var msg = new MsgUpdateCharacter
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
|
||||
{
|
||||
// 0 is yes and 1 is no
|
||||
public bool Preference
|
||||
{
|
||||
get => Options.SelectedValue == 0;
|
||||
set => Options.Select((value && !Disabled) ? 0 : 1);
|
||||
}
|
||||
|
||||
public event Action<bool>? PreferenceChanged;
|
||||
|
||||
public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup)
|
||||
: base(proto, btnGroup)
|
||||
{
|
||||
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
||||
("humanoid-profile-editor-antag-preference-no-button", 1)
|
||||
};
|
||||
var title = Loc.GetString(proto.Name);
|
||||
var description = Loc.GetString(proto.Objective);
|
||||
// Not supported yet get fucked.
|
||||
Setup(null, items, title, 250, description);
|
||||
|
||||
// immediately lock requirements if they arent met.
|
||||
// another function checks Disabled after creating the selector so this has to be done now
|
||||
var requirements = IoCManager.Resolve<JobRequirementsManager>();
|
||||
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
|
||||
{
|
||||
LockRequirements(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
<gfx:StyleBoxFlat BackgroundColor="{x:Static style:StyleNano.NanoGold}" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Name="CharEditor" HorizontalExpand="True" />
|
||||
<BoxContainer Name="CharEditor" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -3,23 +3,27 @@ using System.Numerics;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.Info.PlaytimeStats;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
@@ -32,6 +36,7 @@ namespace Content.Client.Preferences.UI
|
||||
private readonly IClientPreferencesManager _preferencesManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly Button _createNewCharacterButton;
|
||||
private readonly HumanoidProfileEditor _humanoidProfileEditor;
|
||||
|
||||
@@ -46,6 +51,7 @@ namespace Content.Client.Preferences.UI
|
||||
_entityManager = entityManager;
|
||||
_prototypeManager = prototypeManager;
|
||||
_preferencesManager = preferencesManager;
|
||||
_configurationManager = configurationManager;
|
||||
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
@@ -68,7 +74,7 @@ namespace Content.Client.Preferences.UI
|
||||
args.Event.Handle();
|
||||
};
|
||||
|
||||
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
|
||||
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
|
||||
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
|
||||
CharEditor.AddChild(_humanoidProfileEditor);
|
||||
|
||||
@@ -97,12 +103,6 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
public void UpdateControls()
|
||||
{
|
||||
// Reset sliders etc. upon going going back to GUI.
|
||||
_humanoidProfileEditor.LoadServerData();
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
var numberOfFullSlots = 0;
|
||||
@@ -120,6 +120,11 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
|
||||
{
|
||||
if (character is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
numberOfFullSlots++;
|
||||
var characterPickerButton = new CharacterPickerButton(_entityManager,
|
||||
_preferencesManager,
|
||||
@@ -143,12 +148,8 @@ namespace Content.Client.Preferences.UI
|
||||
_createNewCharacterButton.Disabled =
|
||||
numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
|
||||
Characters.AddChild(_createNewCharacterButton);
|
||||
// TODO: Move this shit to the Lobby UI controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows individual characters on the side of the character GUI.
|
||||
/// </summary>
|
||||
private sealed class CharacterPickerButton : ContainerButton
|
||||
{
|
||||
private EntityUid _previewDummy;
|
||||
@@ -179,15 +180,7 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
if (humanoid != null)
|
||||
{
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
var job = controller.GetPreferredJob(humanoid);
|
||||
controller.GiveDummyJobClothes(_previewDummy, humanoid, job);
|
||||
|
||||
if (prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
|
||||
{
|
||||
var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), entityManager, prototypeManager);
|
||||
controller.GiveDummyLoadout(_previewDummy, loadout);
|
||||
}
|
||||
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
|
||||
}
|
||||
|
||||
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<PanelContainer
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#2F2F35"
|
||||
ContentMarginTopOverride="10"
|
||||
ContentMarginBottomOverride="10"
|
||||
ContentMarginLeftOverride="10"
|
||||
ContentMarginRightOverride="10"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
@@ -1,14 +0,0 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class HighlightedContainer : PanelContainer
|
||||
{
|
||||
public HighlightedContainer()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace Content.Client.Preferences.UI
|
||||
{
|
||||
public sealed partial class HumanoidProfileEditor
|
||||
{
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
|
||||
private void RandomizeEverything()
|
||||
{
|
||||
Profile = HumanoidCharacterProfile.Random();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
|
||||
xmlns:humanoid="clr-namespace:Content.Client.Humanoid"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
HorizontalExpand="True">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<!-- Left side -->
|
||||
<BoxContainer Orientation="Vertical" Margin="10 10 10 10" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
|
||||
<!-- Middle container -->
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="10">
|
||||
<!-- Name box-->
|
||||
@@ -58,9 +58,7 @@
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-species-label'}" />
|
||||
<Control HorizontalExpand="True"/>
|
||||
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/>
|
||||
<TextureButton Name="SpeciesInfoButton" Scale="0.3 0.3" VerticalAlignment="Center"></TextureButton>
|
||||
<OptionButton Name="CSpeciesButton" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
<!-- Age -->
|
||||
@@ -87,6 +85,18 @@
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button Name="ShowClothes" Pressed="True" ToggleMode="True" Text="{Loc 'humanoid-profile-editor-clothing-show'}" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
<!-- Clothing -->
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-clothing-label'}" />
|
||||
<Control HorizontalExpand="True"/>
|
||||
<OptionButton Name="CClothingButton" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
<!-- Backpack -->
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-backpack-label'}" />
|
||||
<Control HorizontalExpand="True"/>
|
||||
<OptionButton Name="CBackpackButton" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
<!-- Spawn Priority -->
|
||||
<BoxContainer HorizontalExpand="True">
|
||||
<Label Text="{Loc 'humanoid-profile-editor-spawn-priority-label'}" />
|
||||
@@ -141,7 +151,7 @@
|
||||
</TabContainer>
|
||||
</BoxContainer>
|
||||
<!-- Right side -->
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Center">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Center">
|
||||
<SpriteView Name="CSpriteView" Scale="8 8" SizeFlagsStretchRatio="1" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 5">
|
||||
<Button Name="CSpriteRotateLeft" Text="◀" StyleClasses="OpenRight" />
|
||||
@@ -149,4 +159,5 @@
|
||||
<Button Name="CSpriteRotateRight" Text="▶" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -2,48 +2,69 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.Traits;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
|
||||
namespace Content.Client.Preferences.UI
|
||||
{
|
||||
public sealed class HighlightedContainer : PanelContainer
|
||||
{
|
||||
public HighlightedContainer()
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = new Color(47, 47, 53),
|
||||
ContentMarginTopOverride = 10,
|
||||
ContentMarginBottomOverride = 10,
|
||||
ContentMarginLeftOverride = 10,
|
||||
ContentMarginRightOverride = 10
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class HumanoidProfileEditor : BoxContainer
|
||||
public sealed partial class HumanoidProfileEditor : Control
|
||||
{
|
||||
private readonly IClientPreferencesManager _preferencesManager;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IEntityManager _entMan;
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
private readonly MarkingManager _markingManager;
|
||||
private readonly JobRequirementsManager _requirements;
|
||||
|
||||
private LineEdit _ageEdit => CAgeEdit;
|
||||
private LineEdit _nameEdit => CNameEdit;
|
||||
private TextEdit? _flavorTextEdit;
|
||||
private TextEdit _flavorTextEdit = null!;
|
||||
private Button _nameRandomButton => CNameRandomize;
|
||||
private Button _randomizeEverythingButton => CRandomizeEverything;
|
||||
private RichTextLabel _warningLabel => CWarningLabel;
|
||||
@@ -51,6 +72,8 @@ namespace Content.Client.Preferences.UI
|
||||
private OptionButton _sexButton => CSexButton;
|
||||
private OptionButton _genderButton => CPronounsButton;
|
||||
private Slider _skinColor => CSkin;
|
||||
private OptionButton _clothingButton => CClothingButton;
|
||||
private OptionButton _backpackButton => CBackpackButton;
|
||||
private OptionButton _spawnPriorityButton => CSpawnPriorityButton;
|
||||
private SingleMarkingPicker _hairPicker => CHairStylePicker;
|
||||
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
|
||||
@@ -65,39 +88,44 @@ namespace Content.Client.Preferences.UI
|
||||
private readonly Dictionary<string, BoxContainer> _jobCategories;
|
||||
// Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
|
||||
private readonly List<SpeciesPrototype> _speciesList;
|
||||
private readonly List<AntagPreferenceSelector> _antagPreferences = new();
|
||||
private readonly List<AntagPreferenceSelector> _antagPreferences;
|
||||
private readonly List<TraitPreferenceSelector> _traitPreferences;
|
||||
|
||||
private SpriteView _previewSpriteView => CSpriteView;
|
||||
private Button _previewRotateLeftButton => CSpriteRotateLeft;
|
||||
private Button _previewRotateRightButton => CSpriteRotateRight;
|
||||
private Direction _previewRotation = Direction.North;
|
||||
private EntityUid? _previewDummy;
|
||||
|
||||
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
|
||||
private ColorSelectorSliders _rgbSkinColorSelector;
|
||||
|
||||
private bool _isDirty;
|
||||
private bool _needUpdatePreview;
|
||||
public int CharacterSlot;
|
||||
public HumanoidCharacterProfile? Profile;
|
||||
private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
|
||||
|
||||
public event Action<HumanoidCharacterProfile, int>? OnProfileChanged;
|
||||
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultSpeciesGuidebook = "Species";
|
||||
|
||||
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager, IConfigurationManager configurationManager)
|
||||
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
|
||||
IEntityManager entityManager, IConfigurationManager configurationManager)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_prototypeManager = prototypeManager;
|
||||
_entMan = entityManager;
|
||||
_preferencesManager = preferencesManager;
|
||||
_configurationManager = configurationManager;
|
||||
_markingManager = IoCManager.Resolve<MarkingManager>();
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.PreviewDummyUpdated += OnDummyUpdate;
|
||||
|
||||
_previewSpriteView.SetEntity(controller.GetPreviewDummy());
|
||||
SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
|
||||
|
||||
#region Left
|
||||
|
||||
#region Randomize
|
||||
|
||||
#endregion Randomize
|
||||
|
||||
#region Name
|
||||
|
||||
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
|
||||
@@ -111,6 +139,8 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
|
||||
|
||||
ShowClothes.OnPressed += ToggleClothes;
|
||||
|
||||
#region Sex
|
||||
|
||||
_sexButton.OnItemSelected += args =>
|
||||
@@ -190,7 +220,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithHairStyleName(newStyle.id));
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_hairPicker.OnColorChanged += newColor =>
|
||||
@@ -200,7 +230,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
|
||||
UpdateCMarkingsHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_facialHairPicker.OnMarkingSelect += newStyle =>
|
||||
@@ -209,7 +239,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_facialHairPicker.OnColorChanged += newColor =>
|
||||
@@ -219,7 +249,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
|
||||
UpdateCMarkingsFacialHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_hairPicker.OnSlotRemove += _ =>
|
||||
@@ -231,7 +261,7 @@ namespace Content.Client.Preferences.UI
|
||||
);
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_facialHairPicker.OnSlotRemove += _ =>
|
||||
@@ -243,7 +273,7 @@ namespace Content.Client.Preferences.UI
|
||||
);
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsFacialHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_hairPicker.OnSlotAdd += delegate()
|
||||
@@ -263,7 +293,7 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_facialHairPicker.OnSlotAdd += delegate()
|
||||
@@ -283,11 +313,38 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsFacialHair();
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
#endregion Hair
|
||||
|
||||
#region Clothing
|
||||
|
||||
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpsuit"), (int) ClothingPreference.Jumpsuit);
|
||||
_clothingButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-jumpskirt"), (int) ClothingPreference.Jumpskirt);
|
||||
|
||||
_clothingButton.OnItemSelected += args =>
|
||||
{
|
||||
_clothingButton.SelectId(args.Id);
|
||||
SetClothing((ClothingPreference) args.Id);
|
||||
};
|
||||
|
||||
#endregion Clothing
|
||||
|
||||
#region Backpack
|
||||
|
||||
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-backpack"), (int) BackpackPreference.Backpack);
|
||||
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-satchel"), (int) BackpackPreference.Satchel);
|
||||
_backpackButton.AddItem(Loc.GetString("humanoid-profile-editor-preference-duffelbag"), (int) BackpackPreference.Duffelbag);
|
||||
|
||||
_backpackButton.OnItemSelected += args =>
|
||||
{
|
||||
_backpackButton.SelectId(args.Id);
|
||||
SetBackpack((BackpackPreference) args.Id);
|
||||
};
|
||||
|
||||
#endregion Backpack
|
||||
|
||||
#region SpawnPriority
|
||||
|
||||
foreach (var value in Enum.GetValues<SpawnPriorityPreference>())
|
||||
@@ -312,7 +369,7 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile.WithCharacterAppearance(
|
||||
Profile.Appearance.WithEyeColor(newColor));
|
||||
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
#endregion Eyes
|
||||
@@ -336,22 +393,46 @@ namespace Content.Client.Preferences.UI
|
||||
_preferenceUnavailableButton.SelectId(args.Id);
|
||||
|
||||
Profile = Profile?.WithPreferenceUnavailable((PreferenceUnavailableMode) args.Id);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
|
||||
_jobPriorities = new List<JobPrioritySelector>();
|
||||
_jobCategories = new Dictionary<string, BoxContainer>();
|
||||
_requirements = IoCManager.Resolve<JobRequirementsManager>();
|
||||
// TODO: Move this to the LobbyUIController instead of being spaghetti everywhere.
|
||||
_requirements.Updated += UpdateAntagRequirements;
|
||||
_requirements.Updated += UpdateRoleRequirements;
|
||||
UpdateAntagRequirements();
|
||||
UpdateRoleRequirements();
|
||||
|
||||
#endregion Jobs
|
||||
|
||||
#region Antags
|
||||
|
||||
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
|
||||
|
||||
_antagPreferences = new List<AntagPreferenceSelector>();
|
||||
|
||||
foreach (var antag in prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
|
||||
{
|
||||
if (!antag.SetPreference)
|
||||
continue;
|
||||
|
||||
var selector = new AntagPreferenceSelector(antag);
|
||||
_antagList.AddChild(selector);
|
||||
_antagPreferences.Add(selector);
|
||||
if (selector.Disabled)
|
||||
{
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, preference);
|
||||
IsDirty = true;
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Antags
|
||||
|
||||
#region Traits
|
||||
|
||||
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => Loc.GetString(t.Name)).ToList();
|
||||
@@ -369,7 +450,7 @@ namespace Content.Client.Preferences.UI
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
Profile = Profile?.WithTraitPreference(trait.ID, preference);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -402,7 +483,7 @@ namespace Content.Client.Preferences.UI
|
||||
|
||||
#region FlavorText
|
||||
|
||||
if (configurationManager.GetCVar(CCVars.FlavorText))
|
||||
if (_configurationManager.GetCVar(CCVars.FlavorText))
|
||||
{
|
||||
var flavorText = new FlavorText.FlavorText();
|
||||
_tabContainer.AddChild(flavorText);
|
||||
@@ -419,14 +500,22 @@ namespace Content.Client.Preferences.UI
|
||||
_previewRotateLeftButton.OnPressed += _ =>
|
||||
{
|
||||
_previewRotation = _previewRotation.TurnCw();
|
||||
SetPreviewRotation(_previewRotation);
|
||||
_needUpdatePreview = true;
|
||||
};
|
||||
_previewRotateRightButton.OnPressed += _ =>
|
||||
{
|
||||
_previewRotation = _previewRotation.TurnCcw();
|
||||
SetPreviewRotation(_previewRotation);
|
||||
_needUpdatePreview = true;
|
||||
};
|
||||
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
||||
|
||||
if (_previewDummy != null)
|
||||
_entMan.DeleteEntity(_previewDummy!.Value);
|
||||
|
||||
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
|
||||
_previewSpriteView.SetEntity(_previewDummy);
|
||||
#endregion Dummy
|
||||
|
||||
#endregion Left
|
||||
@@ -436,13 +525,6 @@ namespace Content.Client.Preferences.UI
|
||||
LoadServerData();
|
||||
}
|
||||
|
||||
ShowClothes.OnToggled += args =>
|
||||
{
|
||||
var lobby = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
lobby.SetClothes(args.Pressed);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
preferencesManager.OnServerDataLoaded += LoadServerData;
|
||||
|
||||
SpeciesInfoButton.OnPressed += OnSpeciesInfoButtonPressed;
|
||||
@@ -450,69 +532,28 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
|
||||
IsDirty = false;
|
||||
controller.UpdateProfile();
|
||||
}
|
||||
|
||||
private void SetDirty()
|
||||
{
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadCharacterUI();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var page = DefaultSpeciesGuidebook;
|
||||
var page = "Species";
|
||||
if (_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
page = species;
|
||||
|
||||
if (_prototypeManager.TryIndex<GuideEntryPrototype>(DefaultSpeciesGuidebook, out var guideRoot))
|
||||
if (_prototypeManager.TryIndex<GuideEntryPrototype>("Species", out var guideRoot))
|
||||
{
|
||||
var dict = new Dictionary<string, GuideEntry>();
|
||||
dict.Add(DefaultSpeciesGuidebook, guideRoot);
|
||||
dict.Add("Species", guideRoot);
|
||||
//TODO: Don't close the guidebook if its already open, just go to the correct page
|
||||
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDummyUpdate(EntityUid value)
|
||||
private void ToggleClothes(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
_previewSpriteView.SetEntity(value);
|
||||
}
|
||||
|
||||
private void UpdateAntagRequirements()
|
||||
{
|
||||
_antagList.DisposeAllChildren();
|
||||
_antagPreferences.Clear();
|
||||
var btnGroup = new ButtonGroup();
|
||||
|
||||
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
|
||||
{
|
||||
if (!antag.SetPreference)
|
||||
continue;
|
||||
|
||||
var selector = new AntagPreferenceSelector(antag, btnGroup)
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
_antagList.AddChild(selector);
|
||||
_antagPreferences.Add(selector);
|
||||
if (selector.Disabled)
|
||||
{
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
selector.PreferenceChanged += preference =>
|
||||
{
|
||||
Profile = Profile?.WithAntagPreference(antag.ID, preference);
|
||||
SetDirty();
|
||||
};
|
||||
}
|
||||
|
||||
RebuildSpriteView();
|
||||
}
|
||||
|
||||
private void UpdateRoleRequirements()
|
||||
@@ -573,19 +614,10 @@ namespace Content.Client.Preferences.UI
|
||||
.Where(job => job.SetPreference)
|
||||
.ToArray();
|
||||
Array.Sort(jobs, JobUIComparer.Instance);
|
||||
var jobLoadoutGroup = new ButtonGroup();
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
RoleLoadout? loadout = null;
|
||||
|
||||
// Clone so we don't modify the underlying loadout.
|
||||
Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
|
||||
loadout = loadout?.Clone();
|
||||
var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
|
||||
{
|
||||
Margin = new Thickness(3f, 3f, 3f, 0f),
|
||||
};
|
||||
var selector = new JobPrioritySelector(job, _prototypeManager);
|
||||
|
||||
if (!_requirements.IsAllowed(job, out var reason))
|
||||
{
|
||||
@@ -595,15 +627,10 @@ namespace Content.Client.Preferences.UI
|
||||
category.AddChild(selector);
|
||||
_jobPriorities.Add(selector);
|
||||
|
||||
selector.LoadoutUpdated += args =>
|
||||
{
|
||||
Profile = Profile?.WithLoadout(args);
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
selector.PriorityChanged += priority =>
|
||||
{
|
||||
Profile = Profile?.WithJobPriority(job.ID, priority);
|
||||
IsDirty = true;
|
||||
|
||||
foreach (var jobSelector in _jobPriorities)
|
||||
{
|
||||
@@ -619,8 +646,6 @@ namespace Content.Client.Preferences.UI
|
||||
Profile = Profile?.WithJobPriority(jobSelector.Proto.ID, JobPriority.Medium);
|
||||
}
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -638,7 +663,7 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
|
||||
Profile = Profile.WithFlavorText(content);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void OnMarkingChange(MarkingSet markings)
|
||||
@@ -647,12 +672,20 @@ namespace Content.Client.Preferences.UI
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
|
||||
_needUpdatePreview = true;
|
||||
IsDirty = true;
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadProfile();
|
||||
}
|
||||
|
||||
private void OnMarkingColorChange(List<Marking> markings)
|
||||
{
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
|
||||
private void OnSkinColorOnValueChanged()
|
||||
{
|
||||
if (Profile is null) return;
|
||||
@@ -704,9 +737,6 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
|
||||
IsDirty = true;
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(Profile);
|
||||
controller.ReloadProfile();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -715,28 +745,39 @@ namespace Content.Client.Preferences.UI
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.PreviewDummyUpdated -= OnDummyUpdate;
|
||||
_requirements.Updated -= UpdateAntagRequirements;
|
||||
if (_previewDummy != null)
|
||||
_entMan.DeleteEntity(_previewDummy.Value);
|
||||
|
||||
_requirements.Updated -= UpdateRoleRequirements;
|
||||
_preferencesManager.OnServerDataLoaded -= LoadServerData;
|
||||
}
|
||||
|
||||
public void LoadServerData()
|
||||
private void RebuildSpriteView()
|
||||
{
|
||||
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
|
||||
var dollProto = _prototypeManager.Index<SpeciesPrototype>(species).DollPrototype;
|
||||
|
||||
if (_previewDummy != null)
|
||||
_entMan.DeleteEntity(_previewDummy!.Value);
|
||||
|
||||
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
|
||||
_previewSpriteView.SetEntity(_previewDummy);
|
||||
_needUpdatePreview = true;
|
||||
}
|
||||
|
||||
private void LoadServerData()
|
||||
{
|
||||
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
|
||||
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
|
||||
|
||||
UpdateAntagRequirements();
|
||||
UpdateRoleRequirements();
|
||||
UpdateControls();
|
||||
ShowClothes.Pressed = true;
|
||||
_needUpdatePreview = true;
|
||||
}
|
||||
|
||||
private void SetAge(int newAge)
|
||||
{
|
||||
Profile = Profile?.WithAge(newAge);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetSex(Sex newSex)
|
||||
@@ -757,13 +798,13 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
UpdateGenderControls();
|
||||
CMarkings.SetSex(newSex);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetGender(Gender newGender)
|
||||
{
|
||||
Profile = Profile?.WithGender(newGender);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetSpecies(string newSpecies)
|
||||
@@ -772,34 +813,46 @@ namespace Content.Client.Preferences.UI
|
||||
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
|
||||
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
|
||||
UpdateSexControls(); // update sex for new species
|
||||
RebuildSpriteView(); // they might have different inv so we need a new dummy
|
||||
UpdateSpeciesGuidebookIcon();
|
||||
SetDirty();
|
||||
UpdatePreview();
|
||||
IsDirty = true;
|
||||
_needUpdatePreview = true;
|
||||
}
|
||||
|
||||
private void SetName(string newName)
|
||||
{
|
||||
Profile = Profile?.WithName(newName);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetClothing(ClothingPreference newClothing)
|
||||
{
|
||||
Profile = Profile?.WithClothingPreference(newClothing);
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetBackpack(BackpackPreference newBackpack)
|
||||
{
|
||||
Profile = Profile?.WithBackpackPreference(newBackpack);
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
|
||||
{
|
||||
Profile = Profile?.WithSpawnPriorityPreference(newSpawnPriority);
|
||||
SetDirty();
|
||||
IsDirty = true;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
IsDirty = false;
|
||||
|
||||
if (Profile == null)
|
||||
return;
|
||||
|
||||
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
|
||||
OnProfileChanged?.Invoke(Profile, CharacterSlot);
|
||||
// Reset profile to default.
|
||||
UserInterfaceManager.GetUIController<LobbyUIController>().UpdateProfile();
|
||||
if (Profile != null)
|
||||
{
|
||||
_preferencesManager.UpdateCharacter(Profile, CharacterSlot);
|
||||
OnProfileChanged?.Invoke(Profile, CharacterSlot);
|
||||
_needUpdatePreview = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDirty
|
||||
@@ -808,6 +861,7 @@ namespace Content.Client.Preferences.UI
|
||||
set
|
||||
{
|
||||
_isDirty = value;
|
||||
_needUpdatePreview = true;
|
||||
UpdateSaveButton();
|
||||
}
|
||||
}
|
||||
@@ -927,7 +981,7 @@ namespace Content.Client.Preferences.UI
|
||||
if (!_prototypeManager.HasIndex<GuideEntryPrototype>(species))
|
||||
return;
|
||||
|
||||
const string style = "SpeciesInfoDefault";
|
||||
var style = speciesProto.GuideBookIcon;
|
||||
SpeciesInfoButton.StyleClasses.Add(style);
|
||||
}
|
||||
|
||||
@@ -963,6 +1017,26 @@ namespace Content.Client.Preferences.UI
|
||||
_genderButton.SelectId((int) Profile.Gender);
|
||||
}
|
||||
|
||||
private void UpdateClothingControls()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_clothingButton.SelectId((int) Profile.Clothing);
|
||||
}
|
||||
|
||||
private void UpdateBackpackControls()
|
||||
{
|
||||
if (Profile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_backpackButton.SelectId((int) Profile.Backpack);
|
||||
}
|
||||
|
||||
private void UpdateSpawnPriorityControls()
|
||||
{
|
||||
if (Profile == null)
|
||||
@@ -1092,13 +1166,13 @@ namespace Content.Client.Preferences.UI
|
||||
if (Profile is null)
|
||||
return;
|
||||
|
||||
UserInterfaceManager.GetUIController<LobbyUIController>().ReloadProfile();
|
||||
SetPreviewRotation(_previewRotation);
|
||||
}
|
||||
var humanoid = _entMan.System<HumanoidAppearanceSystem>();
|
||||
humanoid.LoadProfile(_previewDummy!.Value, Profile);
|
||||
|
||||
private void SetPreviewRotation(Direction direction)
|
||||
{
|
||||
_previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
|
||||
if (ShowClothes.Pressed)
|
||||
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
|
||||
|
||||
_previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
|
||||
}
|
||||
|
||||
public void UpdateControls()
|
||||
@@ -1110,15 +1184,17 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateGenderControls();
|
||||
UpdateSkinColor();
|
||||
UpdateSpecies();
|
||||
UpdateClothingControls();
|
||||
UpdateBackpackControls();
|
||||
UpdateSpawnPriorityControls();
|
||||
UpdateAgeEdit();
|
||||
UpdateEyePickers();
|
||||
UpdateSaveButton();
|
||||
UpdateLoadouts();
|
||||
UpdateJobPriorities();
|
||||
UpdateAntagPreferences();
|
||||
UpdateTraitPreferences();
|
||||
UpdateMarkings();
|
||||
RebuildSpriteView();
|
||||
UpdateHairPickers();
|
||||
UpdateCMarkingsHair();
|
||||
UpdateCMarkingsFacialHair();
|
||||
@@ -1126,6 +1202,17 @@ namespace Content.Client.Preferences.UI
|
||||
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_needUpdatePreview)
|
||||
{
|
||||
UpdatePreview();
|
||||
_needUpdatePreview = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateJobPriorities()
|
||||
{
|
||||
foreach (var prioritySelector in _jobPriorities)
|
||||
@@ -1138,11 +1225,143 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLoadouts()
|
||||
private abstract class RequirementsSelector<T> : Control
|
||||
{
|
||||
foreach (var prioritySelector in _jobPriorities)
|
||||
public T Proto { get; }
|
||||
public bool Disabled => _lockStripe.Visible;
|
||||
|
||||
protected readonly RadioOptions<int> Options;
|
||||
private StripeBack _lockStripe;
|
||||
private Label _requirementsLabel;
|
||||
|
||||
protected RequirementsSelector(T proto)
|
||||
{
|
||||
prioritySelector.CloseLoadout();
|
||||
Proto = proto;
|
||||
|
||||
Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
|
||||
{
|
||||
FirstButtonStyle = StyleBase.ButtonOpenRight,
|
||||
ButtonStyle = StyleBase.ButtonOpenBoth,
|
||||
LastButtonStyle = StyleBase.ButtonOpenLeft
|
||||
};
|
||||
//Override default radio option button width
|
||||
Options.GenerateItem = GenerateButton;
|
||||
|
||||
Options.OnItemSelected += args => Options.Select(args.Id);
|
||||
|
||||
_requirementsLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("role-timer-locked"),
|
||||
Visible = true,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
StyleClasses = {StyleBase.StyleClassLabelSubText},
|
||||
};
|
||||
|
||||
_lockStripe = new StripeBack()
|
||||
{
|
||||
Visible = false,
|
||||
HorizontalExpand = true,
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
Children =
|
||||
{
|
||||
_requirementsLabel
|
||||
}
|
||||
};
|
||||
|
||||
// Setup must be called after
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually adds the controls, must be called in the inheriting class' constructor.
|
||||
/// </summary>
|
||||
protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
|
||||
{
|
||||
foreach (var (text, value) in items)
|
||||
{
|
||||
Options.AddItem(Loc.GetString(text), value);
|
||||
}
|
||||
|
||||
var titleLabel = new Label()
|
||||
{
|
||||
Margin = new Thickness(5f, 0, 5f, 0),
|
||||
Text = title,
|
||||
MinSize = new Vector2(titleSize, 0),
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
ToolTip = description
|
||||
};
|
||||
|
||||
var container = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
};
|
||||
|
||||
if (icon != null)
|
||||
container.AddChild(icon);
|
||||
container.AddChild(titleLabel);
|
||||
container.AddChild(Options);
|
||||
container.AddChild(_lockStripe);
|
||||
|
||||
AddChild(container);
|
||||
}
|
||||
|
||||
public void LockRequirements(FormattedMessage requirements)
|
||||
{
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.SetMessage(requirements);
|
||||
_lockStripe.TooltipSupplier = _ => tooltip;
|
||||
_lockStripe.Visible = true;
|
||||
Options.Visible = false;
|
||||
}
|
||||
|
||||
// TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
|
||||
public void UnlockRequirements()
|
||||
{
|
||||
_lockStripe.Visible = false;
|
||||
Options.Visible = true;
|
||||
}
|
||||
|
||||
private Button GenerateButton(string text, int value)
|
||||
{
|
||||
return new Button
|
||||
{
|
||||
Text = text,
|
||||
MinWidth = 90
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
|
||||
{
|
||||
public JobPriority Priority
|
||||
{
|
||||
get => (JobPriority) Options.SelectedValue;
|
||||
set => Options.SelectByValue((int) value);
|
||||
}
|
||||
|
||||
public event Action<JobPriority>? PriorityChanged;
|
||||
|
||||
public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
|
||||
: base(proto)
|
||||
{
|
||||
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
|
||||
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
|
||||
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
|
||||
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
|
||||
};
|
||||
|
||||
var icon = new TextureRect
|
||||
{
|
||||
TextureScale = new Vector2(2, 2),
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
|
||||
icon.Texture = jobIcon.Icon.Frame0();
|
||||
|
||||
Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1167,6 +1386,41 @@ namespace Content.Client.Preferences.UI
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
|
||||
{
|
||||
// 0 is yes and 1 is no
|
||||
public bool Preference
|
||||
{
|
||||
get => Options.SelectedValue == 0;
|
||||
set => Options.Select((value && !Disabled) ? 0 : 1);
|
||||
}
|
||||
|
||||
public event Action<bool>? PreferenceChanged;
|
||||
|
||||
public AntagPreferenceSelector(AntagPrototype proto)
|
||||
: base(proto)
|
||||
{
|
||||
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-antag-preference-yes-button", 0),
|
||||
("humanoid-profile-editor-antag-preference-no-button", 1)
|
||||
};
|
||||
var title = Loc.GetString(proto.Name);
|
||||
var description = Loc.GetString(proto.Objective);
|
||||
Setup(items, title, 250, description);
|
||||
|
||||
// immediately lock requirements if they arent met.
|
||||
// another function checks Disabled after creating the selector so this has to be done now
|
||||
var requirements = IoCManager.Resolve<JobRequirementsManager>();
|
||||
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
|
||||
{
|
||||
LockRequirements(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TraitPreferenceSelector : Control
|
||||
{
|
||||
public TraitPrototype Trait { get; }
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
public sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
|
||||
{
|
||||
public JobPriority Priority
|
||||
{
|
||||
get => (JobPriority) Options.SelectedValue;
|
||||
set => Options.SelectByValue((int) value);
|
||||
}
|
||||
|
||||
public event Action<JobPriority>? PriorityChanged;
|
||||
|
||||
public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan)
|
||||
: base(proto, btnGroup)
|
||||
{
|
||||
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
|
||||
("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
|
||||
("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
|
||||
("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
|
||||
};
|
||||
|
||||
var icon = new TextureRect
|
||||
{
|
||||
TextureScale = new Vector2(2, 2),
|
||||
VerticalAlignment = VAlignment.Center
|
||||
};
|
||||
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
|
||||
icon.Texture = jobIcon.Icon.Frame0();
|
||||
|
||||
Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<BoxContainer Name="Container" xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True"
|
||||
MouseFilter="Ignore"
|
||||
Margin="0 0 0 5">
|
||||
<Button Name="SelectButton" ToggleMode="True" Margin="0 0 5 0" HorizontalExpand="True"/>
|
||||
<PanelContainer SetSize="64 64" HorizontalAlignment="Right">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<SpriteView Name="Sprite" Scale="4 4" MouseFilter="Stop"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
@@ -1,74 +0,0 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutContainer : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private readonly EntityUid? _entity;
|
||||
|
||||
public Button Select => SelectButton;
|
||||
|
||||
public LoadoutContainer(ProtoId<LoadoutPrototype> proto, bool disabled, FormattedMessage? reason)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
SelectButton.Disabled = disabled;
|
||||
|
||||
if (disabled && reason != null)
|
||||
{
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.SetMessage(reason);
|
||||
SelectButton.TooltipSupplier = _ => tooltip;
|
||||
}
|
||||
|
||||
if (_protoManager.TryIndex(proto, out var loadProto))
|
||||
{
|
||||
var ent = _entManager.System<LoadoutSystem>().GetFirstOrNull(loadProto);
|
||||
|
||||
if (ent != null)
|
||||
{
|
||||
_entity = _entManager.SpawnEntity(ent, MapCoordinates.Nullspace);
|
||||
Sprite.SetEntity(_entity);
|
||||
|
||||
var spriteTooltip = new Tooltip();
|
||||
spriteTooltip.SetMessage(FormattedMessage.FromUnformatted(_entManager.GetComponent<MetaDataComponent>(_entity.Value).EntityDescription));
|
||||
Sprite.TooltipSupplier = _ => spriteTooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_entManager.DeleteEntity(_entity);
|
||||
}
|
||||
|
||||
public bool Pressed
|
||||
{
|
||||
get => SelectButton.Pressed;
|
||||
set => SelectButton.Pressed = value;
|
||||
}
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => SelectButton.Text;
|
||||
set => SelectButton.Text = value;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
|
||||
<BoxContainer Name="LoadoutsContainer" Orientation="Vertical"/>
|
||||
</PanelContainer>
|
||||
<!-- Buffer space so we have 10 margin between controls but also 10 to the borders -->
|
||||
<Label Text="{Loc 'loadout-restrictions'}" Margin="5 0 5 5"/>
|
||||
<BoxContainer Name="RestrictionsContainer" Orientation="Vertical" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
@@ -1,93 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
{
|
||||
private readonly LoadoutGroupPrototype _groupProto;
|
||||
|
||||
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
||||
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
|
||||
|
||||
public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_groupProto = groupProto;
|
||||
|
||||
RefreshLoadouts(loadout, session, collection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates button availabilities and buttons.
|
||||
/// </summary>
|
||||
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
|
||||
{
|
||||
var protoMan = collection.Resolve<IPrototypeManager>();
|
||||
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
|
||||
RestrictionsContainer.DisposeAllChildren();
|
||||
|
||||
if (_groupProto.MinLimit > 0)
|
||||
{
|
||||
RestrictionsContainer.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("loadouts-min-limit", ("count", _groupProto.MinLimit)),
|
||||
Margin = new Thickness(5, 0, 5, 5),
|
||||
});
|
||||
}
|
||||
|
||||
if (_groupProto.MaxLimit > 0)
|
||||
{
|
||||
RestrictionsContainer.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("loadouts-max-limit", ("count", _groupProto.MaxLimit)),
|
||||
Margin = new Thickness(5, 0, 5, 5),
|
||||
});
|
||||
}
|
||||
|
||||
if (protoMan.TryIndex(loadout.Role, out var roleProto) && roleProto.Points != null && loadout.Points != null)
|
||||
{
|
||||
RestrictionsContainer.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("loadouts-points-limit", ("count", loadout.Points.Value), ("max", roleProto.Points.Value)),
|
||||
Margin = new Thickness(5, 0, 5, 5),
|
||||
});
|
||||
}
|
||||
|
||||
LoadoutsContainer.DisposeAllChildren();
|
||||
// Didn't use options because this is more robust in future.
|
||||
|
||||
var selected = loadout.SelectedLoadouts[_groupProto.ID];
|
||||
|
||||
foreach (var loadoutProto in _groupProto.Loadouts)
|
||||
{
|
||||
if (!protoMan.TryIndex(loadoutProto, out var loadProto))
|
||||
continue;
|
||||
|
||||
var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
|
||||
var pressed = matchingLoadout != null;
|
||||
|
||||
var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
|
||||
var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
|
||||
loadoutContainer.Select.Pressed = pressed;
|
||||
loadoutContainer.Text = loadoutSystem.GetName(loadProto);
|
||||
|
||||
loadoutContainer.Select.OnPressed += args =>
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
OnLoadoutPressed?.Invoke(loadoutProto);
|
||||
else
|
||||
OnLoadoutUnpressed?.Invoke(loadoutProto);
|
||||
};
|
||||
|
||||
LoadoutsContainer.AddChild(loadoutContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="800 800"
|
||||
MinSize="800 64">
|
||||
<VerticalTabContainer Name="LoadoutGroupsContainer"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
</VerticalTabContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -1,60 +0,0 @@
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutWindow : FancyWindow
|
||||
{
|
||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
||||
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
|
||||
|
||||
private List<LoadoutGroupContainer> _groups = new();
|
||||
|
||||
public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
var protoManager = collection.Resolve<IPrototypeManager>();
|
||||
|
||||
foreach (var group in proto.Groups)
|
||||
{
|
||||
if (!protoManager.TryIndex(group, out var groupProto))
|
||||
continue;
|
||||
|
||||
var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
|
||||
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
|
||||
_groups.Add(container);
|
||||
|
||||
container.OnLoadoutPressed += args =>
|
||||
{
|
||||
OnLoadoutPressed?.Invoke(group, args);
|
||||
};
|
||||
|
||||
container.OnLoadoutUnpressed += args =>
|
||||
{
|
||||
OnLoadoutUnpressed?.Invoke(group, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.SetDummyJob(null);
|
||||
}
|
||||
|
||||
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
|
||||
{
|
||||
foreach (var group in _groups)
|
||||
{
|
||||
group.RefreshLoadouts(loadout, session, collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Preferences.UI;
|
||||
|
||||
public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototype
|
||||
{
|
||||
private ButtonGroup _loadoutGroup;
|
||||
|
||||
public T Proto { get; }
|
||||
public bool Disabled => _lockStripe.Visible;
|
||||
|
||||
protected readonly RadioOptions<int> Options;
|
||||
private readonly StripeBack _lockStripe;
|
||||
private LoadoutWindow? _loadoutWindow;
|
||||
|
||||
private RoleLoadout? _loadout;
|
||||
|
||||
/// <summary>
|
||||
/// Raised if a loadout has been updated.
|
||||
/// </summary>
|
||||
public event Action<RoleLoadout>? LoadoutUpdated;
|
||||
|
||||
protected RequirementsSelector(T proto, ButtonGroup loadoutGroup)
|
||||
{
|
||||
_loadoutGroup = loadoutGroup;
|
||||
Proto = proto;
|
||||
|
||||
Options = new RadioOptions<int>(RadioOptionsLayout.Horizontal)
|
||||
{
|
||||
FirstButtonStyle = StyleBase.ButtonOpenRight,
|
||||
ButtonStyle = StyleBase.ButtonOpenBoth,
|
||||
LastButtonStyle = StyleBase.ButtonOpenLeft,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
//Override default radio option button width
|
||||
Options.GenerateItem = GenerateButton;
|
||||
|
||||
Options.OnItemSelected += args => Options.Select(args.Id);
|
||||
|
||||
var requirementsLabel = new Label()
|
||||
{
|
||||
Text = Loc.GetString("role-timer-locked"),
|
||||
Visible = true,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
StyleClasses = {StyleBase.StyleClassLabelSubText},
|
||||
};
|
||||
|
||||
_lockStripe = new StripeBack()
|
||||
{
|
||||
Visible = false,
|
||||
HorizontalExpand = true,
|
||||
HasMargins = false,
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
Children =
|
||||
{
|
||||
requirementsLabel
|
||||
}
|
||||
};
|
||||
|
||||
// Setup must be called after
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually adds the controls, must be called in the inheriting class' constructor.
|
||||
/// </summary>
|
||||
protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
|
||||
{
|
||||
_loadout = loadout;
|
||||
|
||||
foreach (var (text, value) in items)
|
||||
{
|
||||
Options.AddItem(Loc.GetString(text), value);
|
||||
}
|
||||
|
||||
var titleLabel = new Label()
|
||||
{
|
||||
Margin = new Thickness(5f, 0, 5f, 0),
|
||||
Text = title,
|
||||
MinSize = new Vector2(titleSize, 0),
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
ToolTip = description
|
||||
};
|
||||
|
||||
if (icon != null)
|
||||
AddChild(icon);
|
||||
|
||||
AddChild(titleLabel);
|
||||
AddChild(Options);
|
||||
AddChild(_lockStripe);
|
||||
|
||||
var loadoutWindowBtn = new Button()
|
||||
{
|
||||
Text = Loc.GetString("loadout-window"),
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Group = _loadoutGroup,
|
||||
Margin = new Thickness(3f, 0f, 0f, 0f),
|
||||
};
|
||||
|
||||
var collection = IoCManager.Instance!;
|
||||
var protoManager = collection.Resolve<IPrototypeManager>();
|
||||
|
||||
// If no loadout found then disabled button
|
||||
if (!protoManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(Proto.ID)))
|
||||
{
|
||||
loadoutWindowBtn.Disabled = true;
|
||||
}
|
||||
// else
|
||||
else
|
||||
{
|
||||
var session = collection.Resolve<IPlayerManager>().LocalSession!;
|
||||
// TODO: Most of lobby state should be a uicontroller
|
||||
// trying to handle all this shit is a big-ass mess.
|
||||
// Every time I touch it I try to make it slightly better but it needs a howitzer dropped on it.
|
||||
loadoutWindowBtn.OnPressed += args =>
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
{
|
||||
// We only create a loadout when necessary to avoid unnecessary DB entries.
|
||||
_loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID));
|
||||
_loadout.SetDefault(protoManager);
|
||||
|
||||
_loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection)
|
||||
{
|
||||
Title = Loc.GetString(Proto.ID + "-loadout"),
|
||||
};
|
||||
|
||||
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
|
||||
|
||||
// If it's a job preview then refresh it.
|
||||
if (Proto is JobPrototype jobProto)
|
||||
{
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.SetDummyJob(jobProto);
|
||||
}
|
||||
|
||||
_loadoutWindow.OnLoadoutUnpressed += (selectedGroup, selectedLoadout) =>
|
||||
{
|
||||
if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager))
|
||||
return;
|
||||
|
||||
_loadout.EnsureValid(session, collection);
|
||||
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.ReloadProfile();
|
||||
LoadoutUpdated?.Invoke(_loadout);
|
||||
};
|
||||
|
||||
_loadoutWindow.OnLoadoutPressed += (selectedGroup, selectedLoadout) =>
|
||||
{
|
||||
if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager))
|
||||
return;
|
||||
|
||||
_loadout.EnsureValid(session, collection);
|
||||
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.ReloadProfile();
|
||||
LoadoutUpdated?.Invoke(_loadout);
|
||||
};
|
||||
|
||||
_loadoutWindow.OpenCenteredLeft();
|
||||
_loadoutWindow.OnClose += () =>
|
||||
{
|
||||
loadoutWindowBtn.Pressed = false;
|
||||
_loadoutWindow?.Dispose();
|
||||
_loadoutWindow = null;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseLoadout();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AddChild(loadoutWindowBtn);
|
||||
}
|
||||
|
||||
public void CloseLoadout()
|
||||
{
|
||||
_loadoutWindow?.Close();
|
||||
_loadoutWindow?.Dispose();
|
||||
_loadoutWindow = null;
|
||||
}
|
||||
|
||||
public void LockRequirements(FormattedMessage requirements)
|
||||
{
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.SetMessage(requirements);
|
||||
_lockStripe.TooltipSupplier = _ => tooltip;
|
||||
_lockStripe.Visible = true;
|
||||
Options.Visible = false;
|
||||
}
|
||||
|
||||
// TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
|
||||
public void UnlockRequirements()
|
||||
{
|
||||
_lockStripe.Visible = false;
|
||||
Options.Visible = true;
|
||||
}
|
||||
|
||||
private Button GenerateButton(string text, int value)
|
||||
{
|
||||
return new Button
|
||||
{
|
||||
Text = text,
|
||||
MinWidth = 90,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
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;
|
||||
@@ -18,24 +16,17 @@ 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");
|
||||
@@ -60,21 +51,14 @@ public sealed partial class RCDMenu : RadialMenu
|
||||
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 name = Loc.GetString(proto.SetName);
|
||||
name = char.ToUpper(name[0]) + name.Remove(0, 1);
|
||||
|
||||
var button = new RCDMenuButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = tooltip,
|
||||
ToolTip = name,
|
||||
ProtoId = protoId,
|
||||
};
|
||||
|
||||
@@ -136,27 +120,6 @@ public sealed partial class RCDMenu : RadialMenu
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.StatusIcon;
|
||||
|
||||
@@ -18,7 +18,7 @@ public sealed class StatusIconOverlay : Overlay
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly TransformSystem _transform;
|
||||
private readonly StatusIconSystem _statusIcon;
|
||||
private readonly ShaderInstance _unshadedShader;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed class StatusIconOverlay : Overlay
|
||||
_sprite = _entity.System<SpriteSystem>();
|
||||
_transform = _entity.System<TransformSystem>();
|
||||
_statusIcon = _entity.System<StatusIconSystem>();
|
||||
_unshadedShader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_shader = _prototype.Index<ShaderPrototype>("unshaded").Instance();
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -42,6 +42,8 @@ public sealed class StatusIconOverlay : Overlay
|
||||
var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1));
|
||||
var rotationMatrix = Matrix3.CreateRotation(-eyeRot);
|
||||
|
||||
handle.UseShader(_shader);
|
||||
|
||||
var query = _entity.AllEntityQueryEnumerator<StatusIconComponent, SpriteComponent, TransformComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta))
|
||||
{
|
||||
@@ -109,16 +111,11 @@ public sealed class StatusIconOverlay : Overlay
|
||||
|
||||
}
|
||||
|
||||
if (proto.IsShaded)
|
||||
handle.UseShader(null);
|
||||
else
|
||||
handle.UseShader(_unshadedShader);
|
||||
|
||||
var position = new Vector2(xOffset, yOffset);
|
||||
handle.DrawTexture(texture, position);
|
||||
}
|
||||
|
||||
handle.UseShader(null);
|
||||
}
|
||||
|
||||
handle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
private string _windowName = Loc.GetString("store-ui-default-title");
|
||||
|
||||
[ViewVariables]
|
||||
private string _search = string.Empty;
|
||||
private string _search = "";
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<ListingData> _listings = new();
|
||||
@@ -41,7 +41,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
_menu.OnCategoryButtonPressed += (_, category) =>
|
||||
{
|
||||
_menu.CurrentCategory = category;
|
||||
_menu?.UpdateListing();
|
||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||
};
|
||||
|
||||
_menu.OnWithdrawAttempt += (_, type, amount) =>
|
||||
@@ -49,6 +49,11 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||
SendMessage(new StoreRequestWithdrawMessage(type, amount));
|
||||
};
|
||||
|
||||
_menu.OnRefreshButtonPressed += (_) =>
|
||||
{
|
||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||
};
|
||||
|
||||
_menu.SearchTextUpdated += (_, search) =>
|
||||
{
|
||||
_search = search.Trim().ToLowerInvariant();
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
Margin="0,0,4,0"
|
||||
MinSize="48 48"
|
||||
Stretch="KeepAspectCentered" />
|
||||
<Control MinWidth="5"/>
|
||||
<RichTextLabel Name="StoreItemDescription" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,91 +1,25 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StoreListingControl : Control
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private readonly ClientGameTicker _ticker;
|
||||
|
||||
private readonly ListingData _data;
|
||||
|
||||
private readonly bool _hasBalance;
|
||||
private readonly string _price;
|
||||
public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null)
|
||||
public StoreListingControl(string itemName, string itemDescription,
|
||||
string price, bool canBuy, Texture? texture = null)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_ticker = _entity.System<ClientGameTicker>();
|
||||
StoreItemName.Text = itemName;
|
||||
StoreItemDescription.SetMessage(itemDescription);
|
||||
|
||||
_data = data;
|
||||
_hasBalance = hasBalance;
|
||||
_price = price;
|
||||
|
||||
StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
|
||||
StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype));
|
||||
|
||||
UpdateBuyButtonText();
|
||||
StoreItemBuyButton.Disabled = !CanBuy();
|
||||
StoreItemBuyButton.Text = price;
|
||||
StoreItemBuyButton.Disabled = !canBuy;
|
||||
|
||||
StoreItemTexture.Texture = texture;
|
||||
}
|
||||
|
||||
private bool CanBuy()
|
||||
{
|
||||
if (!_hasBalance)
|
||||
return false;
|
||||
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateBuyButtonText()
|
||||
{
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
{
|
||||
var timeLeftToBuy = stationTime - _data.RestockTime;
|
||||
StoreItemBuyButton.Text = timeLeftToBuy.Duration().ToString(@"mm\:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreItemBuyButton.Text = _price;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateName()
|
||||
{
|
||||
var name = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
|
||||
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
{
|
||||
name += Loc.GetString("store-ui-button-out-of-stock");
|
||||
}
|
||||
|
||||
StoreItemName.Text = name;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
UpdateBuyButtonText();
|
||||
UpdateName();
|
||||
StoreItemBuyButton.Disabled = !CanBuy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
HorizontalAlignment="Left"
|
||||
Access="Public"
|
||||
HorizontalExpand="True" />
|
||||
<Button
|
||||
Name="RefreshButton"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Right"
|
||||
Text="Refresh" />
|
||||
<Button
|
||||
Name="WithdrawButton"
|
||||
MinWidth="64"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
@@ -10,6 +11,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
@@ -18,6 +20,9 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
private readonly ClientGameTicker _gameTicker;
|
||||
|
||||
private StoreWithdrawWindow? _withdrawWindow;
|
||||
|
||||
@@ -25,19 +30,21 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnRefreshButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnRefundAttempt;
|
||||
|
||||
public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> Balance = new();
|
||||
public Dictionary<string, FixedPoint2> Balance = new();
|
||||
public string CurrentCategory = string.Empty;
|
||||
|
||||
private List<ListingData> _cachedListings = new();
|
||||
|
||||
public StoreMenu(string name)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_gameTicker = _entitySystem.GetEntitySystem<ClientGameTicker>();
|
||||
|
||||
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
||||
RefreshButton.OnButtonDown += OnRefreshButtonDown;
|
||||
RefundButton.OnButtonDown += OnRefundButtonDown;
|
||||
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
|
||||
|
||||
@@ -45,12 +52,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
Window.Title = name;
|
||||
}
|
||||
|
||||
public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
|
||||
public void UpdateBalance(Dictionary<string, FixedPoint2> balance)
|
||||
{
|
||||
Balance = balance;
|
||||
|
||||
var currency = balance.ToDictionary(type =>
|
||||
(type.Key, type.Value), type => _prototypeManager.Index(type.Key));
|
||||
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
||||
|
||||
var balanceStr = string.Empty;
|
||||
foreach (var ((_, amount), proto) in currency)
|
||||
@@ -73,13 +80,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
|
||||
public void UpdateListing(List<ListingData> listings)
|
||||
{
|
||||
_cachedListings = listings;
|
||||
UpdateListing();
|
||||
}
|
||||
|
||||
public void UpdateListing()
|
||||
{
|
||||
var sorted = _cachedListings.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.
|
||||
// maybe read clients prototypes instead?
|
||||
@@ -95,6 +96,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
TraitorFooter.Visible = visible;
|
||||
}
|
||||
|
||||
|
||||
private void OnRefreshButtonDown(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
OnRefreshButtonPressed?.Invoke(args);
|
||||
}
|
||||
|
||||
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// check if window is already open
|
||||
@@ -122,8 +129,10 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
if (!listing.Categories.Contains(CurrentCategory))
|
||||
return;
|
||||
|
||||
var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
|
||||
var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
|
||||
var listingPrice = listing.Cost;
|
||||
var hasBalance = HasListingPrice(Balance, listingPrice);
|
||||
var canBuy = CanBuyListing(Balance, listingPrice);
|
||||
|
||||
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
@@ -145,15 +154,39 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
texture = spriteSys.Frame0(action.Icon);
|
||||
}
|
||||
}
|
||||
var listingInStock = ListingInStock(listing);
|
||||
if (listingInStock != GetListingPriceString(listing))
|
||||
{
|
||||
listingName += " (Out of stock)";
|
||||
canBuy = false;
|
||||
}
|
||||
|
||||
var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture);
|
||||
var newListing = new StoreListingControl(listingName, listingDesc, listingInStock, canBuy, texture);
|
||||
newListing.StoreItemBuyButton.OnButtonDown += args
|
||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||
|
||||
StoreListingsContainer.AddChild(newListing);
|
||||
}
|
||||
|
||||
public bool HasListingPrice(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> currency, Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> price)
|
||||
/// <summary>
|
||||
/// Return time until available or the cost.
|
||||
/// </summary>
|
||||
/// <param name="listing"></param>
|
||||
/// <returns></returns>
|
||||
public string ListingInStock(ListingData listing)
|
||||
{
|
||||
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
|
||||
|
||||
TimeSpan restockTimeSpan = TimeSpan.FromMinutes(listing.RestockTime);
|
||||
if (restockTimeSpan > stationTime)
|
||||
{
|
||||
var timeLeftToBuy = stationTime - restockTimeSpan;
|
||||
return timeLeftToBuy.Duration().ToString(@"mm\:ss");
|
||||
}
|
||||
|
||||
return GetListingPriceString(listing);
|
||||
}
|
||||
public bool CanBuyListing(Dictionary<string, FixedPoint2> currency, Dictionary<string, FixedPoint2> price)
|
||||
{
|
||||
foreach (var type in price)
|
||||
{
|
||||
@@ -175,7 +208,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
{
|
||||
foreach (var (type, amount) in listing.Cost)
|
||||
{
|
||||
var currency = _prototypeManager.Index(type);
|
||||
var currency = _prototypeManager.Index<CurrencyPrototype>(type);
|
||||
text += Loc.GetString("store-ui-price-display", ("amount", amount),
|
||||
("currency", Loc.GetString(currency.DisplayName, ("amount", amount))));
|
||||
}
|
||||
@@ -196,7 +229,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
{
|
||||
foreach (var cat in listing.Categories)
|
||||
{
|
||||
var proto = _prototypeManager.Index(cat);
|
||||
var proto = _prototypeManager.Index<StoreCategoryPrototype>(cat);
|
||||
if (!allCategories.Contains(proto))
|
||||
allCategories.Add(proto);
|
||||
}
|
||||
@@ -215,17 +248,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
if (allCategories.Count < 1)
|
||||
return;
|
||||
|
||||
var group = new ButtonGroup();
|
||||
foreach (var proto in allCategories)
|
||||
{
|
||||
var catButton = new StoreCategoryButton
|
||||
{
|
||||
Text = Loc.GetString(proto.Name),
|
||||
Id = proto.ID,
|
||||
Pressed = proto.ID == CurrentCategory,
|
||||
Group = group,
|
||||
ToggleMode = true,
|
||||
StyleClasses = { "OpenBoth" }
|
||||
Id = proto.ID
|
||||
};
|
||||
|
||||
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.Id);
|
||||
@@ -241,7 +269,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
||||
|
||||
public void UpdateRefund(bool allowRefund)
|
||||
{
|
||||
RefundButton.Visible = allowRefund;
|
||||
RefundButton.Disabled = !allowRefund;
|
||||
}
|
||||
|
||||
private sealed class StoreCategoryButton : Button
|
||||
|
||||
@@ -28,12 +28,12 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void CreateCurrencyButtons(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
|
||||
public void CreateCurrencyButtons(Dictionary<string, FixedPoint2> balance)
|
||||
{
|
||||
_validCurrencies.Clear();
|
||||
foreach (var currency in balance)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(currency.Key, out var proto))
|
||||
if (!_prototypeManager.TryIndex<CurrencyPrototype>(currency.Key, out var proto))
|
||||
continue;
|
||||
|
||||
_validCurrencies.Add(currency.Value, proto);
|
||||
|
||||
@@ -93,12 +93,12 @@ namespace Content.Client.Stylesheets
|
||||
public static readonly Color DangerousRedFore = Color.FromHex("#BB3232");
|
||||
public static readonly Color DisabledFore = Color.FromHex("#5A5A5A");
|
||||
|
||||
public static readonly Color ButtonColorDefault = Color.FromHex("#664e46");
|
||||
public static readonly Color ButtonColorDefault = Color.FromHex("#464966");
|
||||
public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B");
|
||||
public static readonly Color ButtonColorHovered = Color.FromHex("#7f6357");
|
||||
public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f");
|
||||
public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B");
|
||||
public static readonly Color ButtonColorPressed = Color.FromHex("#6c4a3e");
|
||||
public static readonly Color ButtonColorDisabled = Color.FromHex("#3c3330");
|
||||
public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45");
|
||||
public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c");
|
||||
|
||||
public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232");
|
||||
public static readonly Color ButtonColorCautionHovered = Color.FromHex("#cf2f2f");
|
||||
|
||||
@@ -19,6 +19,5 @@ public enum WeaponArcAnimation : byte
|
||||
Thrust,
|
||||
Slash,
|
||||
//CrystallPunk Melee upgrade
|
||||
CPSlash,
|
||||
CPThrust
|
||||
CPSlashLight
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed partial class MeleeWeaponSystem
|
||||
private const string SlashAnimationKey = "melee-slash";
|
||||
private const string ThrustAnimationKey = "melee-thrust";
|
||||
|
||||
private const string CPSlashLightAnimationKey = "cp-melee-slash-light"; //CrystallPunk Melee upgrade
|
||||
|
||||
/// <summary>
|
||||
/// Does all of the melee effects for a player that are predicted, i.e. character lunge and weapon animation.
|
||||
/// </summary>
|
||||
@@ -43,6 +45,7 @@ public sealed partial class MeleeWeaponSystem
|
||||
}
|
||||
|
||||
var length = 1f; //CrystallPunk Melee upgrade
|
||||
var scale = 1f; //CrystallPunk Melee upgrade
|
||||
var offset = -1f; //CrystallPunk Melee upgrade
|
||||
|
||||
var spriteRotation = Angle.Zero;
|
||||
@@ -59,10 +62,12 @@ public sealed partial class MeleeWeaponSystem
|
||||
angle *= -1;
|
||||
|
||||
length = meleeWeaponComponent.CPAnimationLength; //CrystallPunk Melee upgrade
|
||||
scale = meleeWeaponComponent.CPAnimationScale; //CrystallPunk Melee upgrade
|
||||
offset = meleeWeaponComponent.CPAnimationOffset; //CrystallPunk Melee upgrade
|
||||
}
|
||||
sprite.NoRotation = true;
|
||||
sprite.Rotation = localPos.ToWorldAngle();
|
||||
sprite.Scale = new Vector2(scale); //CrystallPunk Melee upgrade
|
||||
var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f);
|
||||
|
||||
var xform = _xformQuery.GetComponent(animationUid);
|
||||
@@ -91,17 +96,12 @@ public sealed partial class MeleeWeaponSystem
|
||||
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
|
||||
break;
|
||||
//CrystallPunk MeleeUpgrade
|
||||
case WeaponArcAnimation.CPSlash:
|
||||
_animation.Play(animationUid, CPGetSlashAnimation(sprite, angle, spriteRotation, length, offset), SlashAnimationKey);
|
||||
case WeaponArcAnimation.CPSlashLight:
|
||||
_animation.Play(animationUid, CPGetSlashLightAnimation(sprite, angle, spriteRotation, length, offset), CPSlashLightAnimationKey);
|
||||
TransformSystem.SetParent(animationUid, xform, user, userXform);
|
||||
if (arcComponent.Fadeout)
|
||||
_animation.Play(animationUid, GetFadeAnimation(sprite, length * 0.5f, length + 0.15f), FadeAnimationKey);
|
||||
break;
|
||||
case WeaponArcAnimation.CPThrust:
|
||||
_animation.Play(animationUid, CPGetThrustAnimation(sprite, -offset, spriteRotation, length), ThrustAnimationKey);
|
||||
TransformSystem.SetParent(animationUid, xform, user, userXform);
|
||||
if (arcComponent.Fadeout)
|
||||
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
|
||||
|
||||
break;
|
||||
//CrystallPunk MeleeUpgrade end
|
||||
}
|
||||
@@ -219,8 +219,8 @@ public sealed partial class MeleeWeaponSystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Zero, 0f), //CrystallPunk MeleeUpgrade
|
||||
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length*0.4f), //CrystallPunk MeleeUpgrade
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length*0.8f) //CrystallPunk MeleeUpgrade
|
||||
new AnimationTrackProperty.KeyFrame(direction.Normalized() * 0.15f, length/2), //CrystallPunk MeleeUpgrade
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Zero, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ public sealed partial class MeleeWeaponSystem
|
||||
}
|
||||
|
||||
//CrystallPunk MeleeUpgrade start
|
||||
private Animation CPGetSlashAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation, float length, float offset = -1f)
|
||||
private Animation CPGetSlashLightAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation, float length, float offset = -1f)
|
||||
{
|
||||
var startRotation = sprite.Rotation + (arc * 0.5f);
|
||||
var endRotation = sprite.Rotation - (arc * 0.5f);
|
||||
@@ -270,34 +270,6 @@ public sealed partial class MeleeWeaponSystem
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Animation CPGetThrustAnimation(SpriteComponent sprite, float distance, Angle spriteRotation, float length)
|
||||
{
|
||||
var startOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f));
|
||||
var endOffset = sprite.Rotation.RotateVec(new Vector2(0f, -distance));
|
||||
|
||||
sprite.Rotation += spriteRotation;
|
||||
|
||||
return new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(length),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0f), length * 0f),
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 1f), length * 0.5f),
|
||||
new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startOffset, endOffset, 0.9f), length * 0.8f),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//CrystallPunk MeleeUpgrade end
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
// Spear and left over stacks should be on the floor.
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 8), (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
|
||||
@@ -100,7 +100,7 @@ public sealed class CraftingTests : InteractionTest
|
||||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||
Assert.That(rodStack, Has.Count.EqualTo(8));
|
||||
Assert.That(wireStack, Has.Count.EqualTo(7));
|
||||
Assert.That(wireStack, Has.Count.EqualTo(8));
|
||||
|
||||
await FindEntity(Spear, shouldSucceed: false);
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed partial class MindTests
|
||||
await using var pair = await PoolManager.GetServerClient(settings);
|
||||
|
||||
// Client is connected with a valid entity & mind
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
|
||||
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
|
||||
|
||||
// Delete **everything**
|
||||
@@ -28,12 +28,6 @@ public sealed partial class MindTests
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
Assert.That(pair.Server.EntMan.EntityCount, Is.EqualTo(0));
|
||||
|
||||
foreach (var ent in pair.Client.EntMan.GetEntities())
|
||||
{
|
||||
Console.WriteLine(pair.Client.EntMan.ToPrettyString(ent));
|
||||
}
|
||||
|
||||
Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0));
|
||||
|
||||
// Create a new map.
|
||||
@@ -42,7 +36,7 @@ public sealed partial class MindTests
|
||||
await pair.RunTicksSync(5);
|
||||
|
||||
// Client is not attached to anything
|
||||
Assert.That(pair.Client.AttachedEntity, Is.Null);
|
||||
Assert.That(pair.Client.Player?.ControlledEntity, Is.Null);
|
||||
Assert.That(pair.PlayerData?.Mind, Is.Null);
|
||||
|
||||
// Attempt to ghost
|
||||
@@ -51,9 +45,9 @@ public sealed partial class MindTests
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
// Client should be attached to a ghost placed on the new map.
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity));
|
||||
Assert.That(pair.Client.EntMan.EntityExists(pair.Client.Player?.ControlledEntity));
|
||||
Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind));
|
||||
var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value);
|
||||
var xform = pair.Client.Transform(pair.Client.Player!.ControlledEntity!.Value);
|
||||
Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId)));
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Preferences;
|
||||
|
||||
[TestFixture]
|
||||
[Ignore("HumanoidAppearance crashes upon loading default profiles.")]
|
||||
public sealed class LoadoutTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks that an empty loadout still spawns with default gear and not naked.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestEmptyLoadout()
|
||||
{
|
||||
var pair = await PoolManager.GetServerClient(new PoolSettings()
|
||||
{
|
||||
Dirty = true,
|
||||
});
|
||||
var server = pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
// Check that an empty role loadout spawns gear
|
||||
var stationSystem = entManager.System<StationSpawningSystem>();
|
||||
var testMap = await pair.CreateTestMap();
|
||||
|
||||
// That's right I can't even spawn a dummy profile without station spawning / humanoidappearance code crashing.
|
||||
var profile = new HumanoidCharacterProfile();
|
||||
|
||||
profile.SetLoadout(new RoleLoadout("TestRoleLoadout"));
|
||||
|
||||
stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
|
||||
{
|
||||
// Sue me, there's so much involved in setting up jobs
|
||||
Prototype = "CargoTechnician"
|
||||
}, profile, station: null);
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ using Content.Server.Database;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -55,6 +53,8 @@ namespace Content.IntegrationTests.Tests.Preferences
|
||||
Color.Beige,
|
||||
new ()
|
||||
),
|
||||
ClothingPreference.Jumpskirt,
|
||||
BackpackPreference.Backpack,
|
||||
SpawnPriorityPreference.None,
|
||||
new Dictionary<string, JobPriority>
|
||||
{
|
||||
@@ -62,8 +62,7 @@ namespace Content.IntegrationTests.Tests.Preferences
|
||||
},
|
||||
PreferenceUnavailableMode.StayInLobby,
|
||||
new List<string> (),
|
||||
new List<string>(),
|
||||
new Dictionary<string, RoleLoadout>()
|
||||
new List<string>()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ClothingRemoval : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "backpack",
|
||||
table: "profile");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "clothing",
|
||||
table: "profile");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "backpack",
|
||||
table: "profile",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "clothing",
|
||||
table: "profile",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,103 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Loadouts : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_role_loadout",
|
||||
columns: table => new
|
||||
{
|
||||
profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
profile_id = table.Column<int>(type: "integer", nullable: false),
|
||||
role_name = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_role_loadout_profile_profile_id",
|
||||
column: x => x.profile_id,
|
||||
principalTable: "profile",
|
||||
principalColumn: "profile_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_loadout_group",
|
||||
columns: table => new
|
||||
{
|
||||
profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
profile_role_loadout_id = table.Column<int>(type: "integer", nullable: false),
|
||||
group_name = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loa~",
|
||||
column: x => x.profile_role_loadout_id,
|
||||
principalTable: "profile_role_loadout",
|
||||
principalColumn: "profile_role_loadout_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_loadout",
|
||||
columns: table => new
|
||||
{
|
||||
profile_loadout_id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
profile_loadout_group_id = table.Column<int>(type: "integer", nullable: false),
|
||||
loadout_name = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group~",
|
||||
column: x => x.profile_loadout_group_id,
|
||||
principalTable: "profile_loadout_group",
|
||||
principalColumn: "profile_loadout_group_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_loadout_profile_loadout_group_id",
|
||||
table: "profile_loadout",
|
||||
column: "profile_loadout_group_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_loadout_group_profile_role_loadout_id",
|
||||
table: "profile_loadout_group",
|
||||
column: "profile_role_loadout_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_role_loadout_profile_id",
|
||||
table: "profile_role_loadout",
|
||||
column: "profile_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_loadout");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_loadout_group");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_role_loadout");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '-Infinity';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,11 +735,21 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("age");
|
||||
|
||||
b.Property<string>("Backpack")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("backpack");
|
||||
|
||||
b.Property<string>("CharacterName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("char_name");
|
||||
|
||||
b.Property<string>("Clothing")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("clothing");
|
||||
|
||||
b.Property<string>("EyeColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
@@ -822,84 +832,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("profile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_loadout_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("LoadoutName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("loadout_name");
|
||||
|
||||
b.Property<int>("ProfileLoadoutGroupId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_loadout_group_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_loadout");
|
||||
|
||||
b.HasIndex("ProfileLoadoutGroupId");
|
||||
|
||||
b.ToTable("profile_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_loadout_group_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("GroupName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("group_name");
|
||||
|
||||
b.Property<int>("ProfileRoleLoadoutId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_role_loadout_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_loadout_group");
|
||||
|
||||
b.HasIndex("ProfileRoleLoadoutId");
|
||||
|
||||
b.ToTable("profile_loadout_group", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_role_loadout_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.Property<string>("RoleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("role_name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_role_loadout");
|
||||
|
||||
b.HasIndex("ProfileId");
|
||||
|
||||
b.ToTable("profile_role_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -913,8 +845,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("server_id");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
b.Property<DateTime>("StartDate")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
||||
.HasColumnName("start_date");
|
||||
|
||||
b.HasKey("Id")
|
||||
@@ -1587,42 +1521,6 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("Preference");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
|
||||
.WithMany("Loadouts")
|
||||
.HasForeignKey("ProfileLoadoutGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~");
|
||||
|
||||
b.Navigation("ProfileLoadoutGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
|
||||
.WithMany("Groups")
|
||||
.HasForeignKey("ProfileRoleLoadoutId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~");
|
||||
|
||||
b.Navigation("ProfileRoleLoadout");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
.WithMany("Loadouts")
|
||||
.HasForeignKey("ProfileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_role_loadout_profile_profile_id");
|
||||
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -1835,21 +1733,9 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
|
||||
b.Navigation("Jobs");
|
||||
|
||||
b.Navigation("Loadouts");
|
||||
|
||||
b.Navigation("Traits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.Navigation("Loadouts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.Navigation("Groups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ClothingRemoval : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "backpack",
|
||||
table: "profile");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "clothing",
|
||||
table: "profile");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "backpack",
|
||||
table: "profile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "clothing",
|
||||
table: "profile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Loadouts : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_role_loadout",
|
||||
columns: table => new
|
||||
{
|
||||
profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
role_name = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_role_loadout", x => x.profile_role_loadout_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_role_loadout_profile_profile_id",
|
||||
column: x => x.profile_id,
|
||||
principalTable: "profile",
|
||||
principalColumn: "profile_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_loadout_group",
|
||||
columns: table => new
|
||||
{
|
||||
profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
profile_role_loadout_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
group_name = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_loadout_group", x => x.profile_loadout_group_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id",
|
||||
column: x => x.profile_role_loadout_id,
|
||||
principalTable: "profile_role_loadout",
|
||||
principalColumn: "profile_role_loadout_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "profile_loadout",
|
||||
columns: table => new
|
||||
{
|
||||
profile_loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
profile_loadout_group_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
loadout_name = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_profile_loadout", x => x.profile_loadout_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_profile_loadout_profile_loadout_group_profile_loadout_group_id",
|
||||
column: x => x.profile_loadout_group_id,
|
||||
principalTable: "profile_loadout_group",
|
||||
principalColumn: "profile_loadout_group_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_loadout_profile_loadout_group_id",
|
||||
table: "profile_loadout",
|
||||
column: "profile_loadout_group_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_loadout_group_profile_role_loadout_id",
|
||||
table: "profile_loadout_group",
|
||||
column: "profile_role_loadout_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_profile_role_loadout_profile_id",
|
||||
table: "profile_role_loadout",
|
||||
column: "profile_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_loadout");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_loadout_group");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "profile_role_loadout");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// This needs to be its own separate migration,
|
||||
// because EF Core re-arranges the order of the commands if it's a single migration...
|
||||
// (only relevant for SQLite since it needs cursed shit to do ALTER COLUMN)
|
||||
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '0001-01-01 00:00:00';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,11 +688,21 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("age");
|
||||
|
||||
b.Property<string>("Backpack")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("backpack");
|
||||
|
||||
b.Property<string>("CharacterName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("char_name");
|
||||
|
||||
b.Property<string>("Clothing")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("clothing");
|
||||
|
||||
b.Property<string>("EyeColor")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
@@ -775,78 +785,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("profile", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_loadout_id");
|
||||
|
||||
b.Property<string>("LoadoutName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("loadout_name");
|
||||
|
||||
b.Property<int>("ProfileLoadoutGroupId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_loadout_group_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_loadout");
|
||||
|
||||
b.HasIndex("ProfileLoadoutGroupId");
|
||||
|
||||
b.ToTable("profile_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_loadout_group_id");
|
||||
|
||||
b.Property<string>("GroupName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("group_name");
|
||||
|
||||
b.Property<int>("ProfileRoleLoadoutId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_role_loadout_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_loadout_group");
|
||||
|
||||
b.HasIndex("ProfileRoleLoadoutId");
|
||||
|
||||
b.ToTable("profile_loadout_group", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_role_loadout_id");
|
||||
|
||||
b.Property<int>("ProfileId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("profile_id");
|
||||
|
||||
b.Property<string>("RoleName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("role_name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_profile_role_loadout");
|
||||
|
||||
b.HasIndex("ProfileId");
|
||||
|
||||
b.ToTable("profile_role_loadout", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -858,8 +796,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_id");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
b.Property<DateTime>("StartDate")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
||||
.HasColumnName("start_date");
|
||||
|
||||
b.HasKey("Id")
|
||||
@@ -1512,42 +1452,6 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("Preference");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup")
|
||||
.WithMany("Loadouts")
|
||||
.HasForeignKey("ProfileLoadoutGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id");
|
||||
|
||||
b.Navigation("ProfileLoadoutGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout")
|
||||
.WithMany("Groups")
|
||||
.HasForeignKey("ProfileRoleLoadoutId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id");
|
||||
|
||||
b.Navigation("ProfileRoleLoadout");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
.WithMany("Loadouts")
|
||||
.HasForeignKey("ProfileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_profile_role_loadout_profile_profile_id");
|
||||
|
||||
b.Navigation("Profile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Server", "Server")
|
||||
@@ -1760,21 +1664,9 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
|
||||
b.Navigation("Jobs");
|
||||
|
||||
b.Navigation("Loadouts");
|
||||
|
||||
b.Navigation("Traits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b =>
|
||||
{
|
||||
b.Navigation("Loadouts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b =>
|
||||
{
|
||||
b.Navigation("Groups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
|
||||
@@ -56,26 +56,8 @@ namespace Content.Server.Database
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Trait>()
|
||||
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<ProfileRoleLoadout>()
|
||||
.HasOne(e => e.Profile)
|
||||
.WithMany(e => e.Loadouts)
|
||||
.HasForeignKey(e => e.ProfileId)
|
||||
.IsRequired();
|
||||
|
||||
modelBuilder.Entity<ProfileLoadoutGroup>()
|
||||
.HasOne(e => e.ProfileRoleLoadout)
|
||||
.WithMany(e => e.Groups)
|
||||
.HasForeignKey(e => e.ProfileRoleLoadoutId)
|
||||
.IsRequired();
|
||||
|
||||
modelBuilder.Entity<ProfileLoadout>()
|
||||
.HasOne(e => e.ProfileLoadoutGroup)
|
||||
.WithMany(e => e.Loadouts)
|
||||
.HasForeignKey(e => e.ProfileLoadoutGroupId)
|
||||
.IsRequired();
|
||||
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<Job>()
|
||||
.HasIndex(j => j.ProfileId);
|
||||
@@ -136,6 +118,10 @@ namespace Content.Server.Database
|
||||
modelBuilder.Entity<Round>()
|
||||
.HasIndex(round => round.StartDate);
|
||||
|
||||
modelBuilder.Entity<Round>()
|
||||
.Property(round => round.StartDate)
|
||||
.HasDefaultValue(default(DateTime));
|
||||
|
||||
modelBuilder.Entity<AdminLogPlayer>()
|
||||
.HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
|
||||
|
||||
@@ -355,13 +341,13 @@ namespace Content.Server.Database
|
||||
public string FacialHairColor { get; set; } = null!;
|
||||
public string EyeColor { get; set; } = null!;
|
||||
public string SkinColor { get; set; } = null!;
|
||||
public string Clothing { get; set; } = null!;
|
||||
public string Backpack { get; set; } = null!;
|
||||
public int SpawnPriority { get; set; } = 0;
|
||||
public List<Job> Jobs { get; } = new();
|
||||
public List<Antag> Antags { get; } = new();
|
||||
public List<Trait> Traits { get; } = new();
|
||||
|
||||
public List<ProfileRoleLoadout> Loadouts { get; } = new();
|
||||
|
||||
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
|
||||
|
||||
public int PreferenceId { get; set; }
|
||||
@@ -405,79 +391,6 @@ namespace Content.Server.Database
|
||||
public string TraitName { get; set; } = null!;
|
||||
}
|
||||
|
||||
#region Loadouts
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to a single role's loadout inside the DB.
|
||||
/// </summary>
|
||||
public class ProfileRoleLoadout
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ProfileId { get; set; }
|
||||
|
||||
public Profile Profile { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The corresponding role prototype on the profile.
|
||||
/// </summary>
|
||||
public string RoleName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
|
||||
/// </summary>
|
||||
public List<ProfileLoadoutGroup> Groups { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to a loadout group prototype with the specified loadouts attached.
|
||||
/// </summary>
|
||||
public class ProfileLoadoutGroup
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ProfileRoleLoadoutId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The corresponding RoleLoadout that owns this.
|
||||
/// </summary>
|
||||
public ProfileRoleLoadout ProfileRoleLoadout { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The corresponding group prototype.
|
||||
/// </summary>
|
||||
public string GroupName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Selected loadout prototype. Null if none is set.
|
||||
/// May get validated at runtime and updated to to the default.
|
||||
/// </summary>
|
||||
public List<ProfileLoadout> Loadouts { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to a selected loadout.
|
||||
/// </summary>
|
||||
public class ProfileLoadout
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ProfileLoadoutGroupId { get; set; }
|
||||
|
||||
public ProfileLoadoutGroup ProfileLoadoutGroup { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Corresponding loadout prototype.
|
||||
/// </summary>
|
||||
public string LoadoutName { get; set; } = string.Empty;
|
||||
|
||||
/*
|
||||
* Insert extra data here like custom descriptions or colors or whatever.
|
||||
*/
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public enum DbPreferenceUnavailableMode
|
||||
{
|
||||
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
|
||||
@@ -581,7 +494,7 @@ namespace Content.Server.Database
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
public List<Player> Players { get; set; } = default!;
|
||||
|
||||
@@ -966,35 +879,8 @@ namespace Content.Server.Database
|
||||
public byte[] Data { get; set; } = default!;
|
||||
}
|
||||
|
||||
// Note: this interface isn't used by the game, but it *is* used by SS14.Admin.
|
||||
// Don't remove! Or face the consequences!
|
||||
public interface IAdminRemarksCommon
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
public int? RoundId { get; }
|
||||
public Round? Round { get; }
|
||||
|
||||
public Guid? PlayerUserId { get; }
|
||||
public Player? Player { get; }
|
||||
public TimeSpan PlaytimeAtNote { get; }
|
||||
|
||||
public string Message { get; }
|
||||
|
||||
public Player? CreatedBy { get; }
|
||||
|
||||
public DateTime CreatedAt { get; }
|
||||
|
||||
public Player? LastEditedBy { get; }
|
||||
|
||||
public DateTime? LastEditedAt { get; }
|
||||
public DateTime? ExpirationTime { get; }
|
||||
|
||||
public bool Deleted { get; }
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminNote : IAdminRemarksCommon
|
||||
public class AdminNote
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
@@ -1028,7 +914,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminWatchlist : IAdminRemarksCommon
|
||||
public class AdminWatchlist
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
@@ -1059,7 +945,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Index(nameof(PlayerUserId))]
|
||||
public class AdminMessage : IAdminRemarksCommon
|
||||
public class AdminMessage
|
||||
{
|
||||
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Content.Server.Administration.Commands
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent);
|
||||
var gearStr = startingGear.GetGear(slot.Name);
|
||||
var gearStr = startingGear.GetGear(slot.Name, profile);
|
||||
if (gearStr == string.Empty)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Server.ServerStatus;
|
||||
|
||||
namespace Content.Server.Administration;
|
||||
|
||||
public sealed partial class ServerApi
|
||||
{
|
||||
private void RegisterHandler(HttpMethod method, string exactPath, Func<IStatusHandlerContext, Task> handler)
|
||||
{
|
||||
_statusHost.AddHandler(async context =>
|
||||
{
|
||||
if (context.RequestMethod != method || context.Url.AbsolutePath != exactPath)
|
||||
return false;
|
||||
|
||||
if (!await CheckAccess(context))
|
||||
return true;
|
||||
|
||||
await handler(context);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterActorHandler(HttpMethod method, string exactPath, Func<IStatusHandlerContext, Actor, Task> handler)
|
||||
{
|
||||
RegisterHandler(method, exactPath, async context =>
|
||||
{
|
||||
if (await CheckActor(context) is not { } actor)
|
||||
return;
|
||||
|
||||
await handler(context, actor);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Async helper function which runs a task on the main thread and returns the result.
|
||||
/// </summary>
|
||||
private async Task<T> RunOnMainThread<T>(Func<T> func)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<T>();
|
||||
_taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
taskCompletionSource.TrySetResult(func());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
taskCompletionSource.TrySetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
var result = await taskCompletionSource.Task;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an action on the main thread. This does not return any value and is meant to be used for void functions. Use <see cref="RunOnMainThread{T}"/> for functions that return a value.
|
||||
/// </summary>
|
||||
private async Task RunOnMainThread(Action action)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource();
|
||||
_taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
taskCompletionSource.TrySetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
taskCompletionSource.TrySetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
private async Task RunOnMainThread(Func<Task> action)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource();
|
||||
// ReSharper disable once AsyncVoidLambda
|
||||
_taskManager.RunOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await action();
|
||||
taskCompletionSource.TrySetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
taskCompletionSource.TrySetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to read JSON encoded data from the request body.
|
||||
/// </summary>
|
||||
private static async Task<T?> ReadJson<T>(IStatusHandlerContext context) where T : notnull
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await context.RequestBodyJsonAsync<T>();
|
||||
if (json == null)
|
||||
await RespondBadRequest(context, "Request body is null");
|
||||
|
||||
return json;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await RespondBadRequest(context, "Unable to parse request body", ExceptionData.FromException(e));
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RespondError(
|
||||
IStatusHandlerContext context,
|
||||
ErrorCode errorCode,
|
||||
HttpStatusCode statusCode,
|
||||
string message,
|
||||
ExceptionData? exception = null)
|
||||
{
|
||||
await context.RespondJsonAsync(new BaseResponse(message, errorCode, exception), statusCode)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task RespondBadRequest(
|
||||
IStatusHandlerContext context,
|
||||
string message,
|
||||
ExceptionData? exception = null)
|
||||
{
|
||||
await RespondError(context, ErrorCode.BadRequest, HttpStatusCode.BadRequest, message, exception)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task RespondOk(IStatusHandlerContext context)
|
||||
{
|
||||
await context.RespondJsonAsync(new BaseResponse("OK"))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string FormatLogActor(Actor actor) => $"{actor.Name} ({actor.Guid})";
|
||||
}
|
||||
@@ -1,711 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Server.ServerStatus;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes various admin-related APIs via the game server's <see cref="StatusHost"/>.
|
||||
/// </summary>
|
||||
public sealed partial class ServerApi : IPostInjectInit
|
||||
{
|
||||
private const string SS14TokenScheme = "SS14Token";
|
||||
|
||||
private static readonly HashSet<string> PanicBunkerCVars =
|
||||
[
|
||||
CCVars.PanicBunkerEnabled.Name,
|
||||
CCVars.PanicBunkerDisableWithAdmins.Name,
|
||||
CCVars.PanicBunkerEnableWithoutAdmins.Name,
|
||||
CCVars.PanicBunkerCountDeadminnedAdmins.Name,
|
||||
CCVars.PanicBunkerShowReason.Name,
|
||||
CCVars.PanicBunkerMinAccountAge.Name,
|
||||
CCVars.PanicBunkerMinOverallHours.Name,
|
||||
CCVars.PanicBunkerCustomReason.Name,
|
||||
];
|
||||
|
||||
[Dependency] private readonly IStatusHost _statusHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IGameMapManager _gameMapManager = default!;
|
||||
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly EntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private string _token = string.Empty;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("serverApi");
|
||||
|
||||
// Get
|
||||
RegisterActorHandler(HttpMethod.Get, "/admin/info", InfoHandler);
|
||||
RegisterHandler(HttpMethod.Get, "/admin/game_rules", GetGameRules);
|
||||
RegisterHandler(HttpMethod.Get, "/admin/presets", GetPresets);
|
||||
|
||||
// Post
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/start", ActionRoundStart);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/end", ActionRoundEnd);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/round/restartnow", ActionRoundRestartNow);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/kick", ActionKick);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/add_game_rule", ActionAddGameRule);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/end_game_rule", ActionEndGameRule);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/force_preset", ActionForcePreset);
|
||||
RegisterActorHandler(HttpMethod.Post, "/admin/actions/set_motd", ActionForceMotd);
|
||||
RegisterActorHandler(HttpMethod.Patch, "/admin/actions/panic_bunker", ActionPanicPunker);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_config.OnValueChanged(CCVars.AdminApiToken, UpdateToken, true);
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_config.UnsubValueChanged(CCVars.AdminApiToken, UpdateToken);
|
||||
}
|
||||
|
||||
private void UpdateToken(string token)
|
||||
{
|
||||
_token = token;
|
||||
}
|
||||
|
||||
|
||||
#region Actions
|
||||
|
||||
/// <summary>
|
||||
/// Changes the panic bunker settings.
|
||||
/// </summary>
|
||||
private async Task ActionPanicPunker(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var request = await ReadJson<JsonObject>(context);
|
||||
if (request == null)
|
||||
return;
|
||||
|
||||
var toSet = new Dictionary<string, object>();
|
||||
foreach (var (cVar, value) in request)
|
||||
{
|
||||
if (!PanicBunkerCVars.Contains(cVar))
|
||||
{
|
||||
await RespondBadRequest(context, $"Invalid panic bunker CVar: '{cVar}'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
await RespondBadRequest(context, $"Value is null: '{cVar}'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value is not JsonValue jsonValue)
|
||||
{
|
||||
await RespondBadRequest(context, $"Value is not valid: '{cVar}'");
|
||||
return;
|
||||
}
|
||||
|
||||
object castValue;
|
||||
var cVarType = _config.GetCVarType(cVar);
|
||||
if (cVarType == typeof(bool))
|
||||
{
|
||||
if (!jsonValue.TryGetValue(out bool b))
|
||||
{
|
||||
await RespondBadRequest(context, $"CVar '{cVar}' must be of type bool.");
|
||||
return;
|
||||
}
|
||||
|
||||
castValue = b;
|
||||
}
|
||||
else if (cVarType == typeof(int))
|
||||
{
|
||||
if (!jsonValue.TryGetValue(out int i))
|
||||
{
|
||||
await RespondBadRequest(context, $"CVar '{cVar}' must be of type int.");
|
||||
return;
|
||||
}
|
||||
|
||||
castValue = i;
|
||||
}
|
||||
else if (cVarType == typeof(string))
|
||||
{
|
||||
if (!jsonValue.TryGetValue(out string? s))
|
||||
{
|
||||
await RespondBadRequest(context, $"CVar '{cVar}' must be of type string.");
|
||||
return;
|
||||
}
|
||||
|
||||
castValue = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unsupported CVar type");
|
||||
}
|
||||
|
||||
toSet[cVar] = castValue;
|
||||
}
|
||||
|
||||
await RunOnMainThread(() =>
|
||||
{
|
||||
foreach (var (cVar, value) in toSet)
|
||||
{
|
||||
_config.SetCVar(cVar, value);
|
||||
_sawmill.Info(
|
||||
$"Panic bunker property '{cVar}' changed to '{value}' by {FormatLogActor(actor)}.");
|
||||
}
|
||||
});
|
||||
|
||||
await RespondOk(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current MOTD.
|
||||
/// </summary>
|
||||
private async Task ActionForceMotd(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var motd = await ReadJson<MotdActionBody>(context);
|
||||
if (motd == null)
|
||||
return;
|
||||
|
||||
_sawmill.Info($"MOTD changed to \"{motd.Motd}\" by {FormatLogActor(actor)}.");
|
||||
|
||||
await RunOnMainThread(() => _config.SetCVar(CCVars.MOTD, motd.Motd));
|
||||
// A hook in the MOTD system sends the changes to each client
|
||||
await RespondOk(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the next preset-
|
||||
/// </summary>
|
||||
private async Task ActionForcePreset(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var body = await ReadJson<PresetActionBody>(context);
|
||||
if (body == null)
|
||||
return;
|
||||
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.InvalidRoundState,
|
||||
HttpStatusCode.Conflict,
|
||||
"Game must be in pre-round lobby");
|
||||
return;
|
||||
}
|
||||
|
||||
var preset = ticker.FindGamePreset(body.PresetId);
|
||||
if (preset == null)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.GameRuleNotFound,
|
||||
HttpStatusCode.UnprocessableContent,
|
||||
$"Game rule '{body.PresetId}' doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
ticker.SetGamePreset(preset);
|
||||
_sawmill.Info($"Forced the game to start with preset {body.PresetId} by {FormatLogActor(actor)}.");
|
||||
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends an active game rule.
|
||||
/// </summary>
|
||||
private async Task ActionEndGameRule(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var body = await ReadJson<GameRuleActionBody>(context);
|
||||
if (body == null)
|
||||
return;
|
||||
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
var gameRule = ticker
|
||||
.GetActiveGameRules()
|
||||
.FirstOrNull(rule =>
|
||||
_entityManager.MetaQuery.GetComponent(rule).EntityPrototype?.ID == body.GameRuleId);
|
||||
|
||||
if (gameRule == null)
|
||||
{
|
||||
await RespondError(context,
|
||||
ErrorCode.GameRuleNotFound,
|
||||
HttpStatusCode.UnprocessableContent,
|
||||
$"Game rule '{body.GameRuleId}' not found or not active");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Info($"Ended game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
|
||||
ticker.EndGameRule(gameRule.Value);
|
||||
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a game rule to the current round.
|
||||
/// </summary>
|
||||
private async Task ActionAddGameRule(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var body = await ReadJson<GameRuleActionBody>(context);
|
||||
if (body == null)
|
||||
return;
|
||||
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
if (!_prototypeManager.HasIndex<EntityPrototype>(body.GameRuleId))
|
||||
{
|
||||
await RespondError(context,
|
||||
ErrorCode.GameRuleNotFound,
|
||||
HttpStatusCode.UnprocessableContent,
|
||||
$"Game rule '{body.GameRuleId}' not found or not active");
|
||||
return;
|
||||
}
|
||||
|
||||
var ruleEntity = ticker.AddGameRule(body.GameRuleId);
|
||||
_sawmill.Info($"Added game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
|
||||
if (ticker.RunLevel == GameRunLevel.InRound)
|
||||
{
|
||||
ticker.StartGameRule(ruleEntity);
|
||||
_sawmill.Info($"Started game rule {body.GameRuleId} by {FormatLogActor(actor)}.");
|
||||
}
|
||||
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a player.
|
||||
/// </summary>
|
||||
private async Task ActionKick(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
var body = await ReadJson<KickActionBody>(context);
|
||||
if (body == null)
|
||||
return;
|
||||
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
if (!_playerManager.TryGetSessionById(new NetUserId(body.Guid), out var player))
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.PlayerNotFound,
|
||||
HttpStatusCode.UnprocessableContent,
|
||||
"Player not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var reason = body.Reason ?? "No reason supplied";
|
||||
reason += " (kicked by admin)";
|
||||
|
||||
_netManager.DisconnectChannel(player.Channel, reason);
|
||||
await RespondOk(context);
|
||||
|
||||
_sawmill.Info($"Kicked player {player.Name} ({player.UserId}) for {reason} by {FormatLogActor(actor)}");
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ActionRoundStart(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.InvalidRoundState,
|
||||
HttpStatusCode.Conflict,
|
||||
"Round already started");
|
||||
return;
|
||||
}
|
||||
|
||||
ticker.StartRound();
|
||||
_sawmill.Info($"Forced round start by {FormatLogActor(actor)}");
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ActionRoundEnd(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var roundEndSystem = _entitySystemManager.GetEntitySystem<RoundEndSystem>();
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
|
||||
if (ticker.RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.InvalidRoundState,
|
||||
HttpStatusCode.Conflict,
|
||||
"Round is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
roundEndSystem.EndRound();
|
||||
_sawmill.Info($"Forced round end by {FormatLogActor(actor)}");
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ActionRoundRestartNow(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
await RunOnMainThread(async () =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
|
||||
ticker.RestartRound();
|
||||
_sawmill.Info($"Forced instant round restart by {FormatLogActor(actor)}");
|
||||
await RespondOk(context);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fetching
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array containing all available presets.
|
||||
/// </summary>
|
||||
private async Task GetPresets(IStatusHandlerContext context)
|
||||
{
|
||||
var presets = await RunOnMainThread(() =>
|
||||
{
|
||||
var presets = new List<PresetResponse.Preset>();
|
||||
|
||||
foreach (var preset in _prototypeManager.EnumeratePrototypes<GamePresetPrototype>())
|
||||
{
|
||||
presets.Add(new PresetResponse.Preset
|
||||
{
|
||||
Id = preset.ID,
|
||||
ModeTitle = _loc.GetString(preset.ModeTitle),
|
||||
Description = _loc.GetString(preset.Description)
|
||||
});
|
||||
}
|
||||
|
||||
return presets;
|
||||
});
|
||||
|
||||
await context.RespondJsonAsync(new PresetResponse
|
||||
{
|
||||
Presets = presets
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array containing all game rules.
|
||||
/// </summary>
|
||||
private async Task GetGameRules(IStatusHandlerContext context)
|
||||
{
|
||||
var gameRules = new List<string>();
|
||||
foreach (var gameRule in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (gameRule.Abstract)
|
||||
continue;
|
||||
|
||||
if (gameRule.HasComponent<GameRuleComponent>(_componentFactory))
|
||||
gameRules.Add(gameRule.ID);
|
||||
}
|
||||
|
||||
await context.RespondJsonAsync(new GameruleResponse
|
||||
{
|
||||
GameRules = gameRules
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles fetching information.
|
||||
/// </summary>
|
||||
private async Task InfoHandler(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
/*
|
||||
Information to display
|
||||
Round number
|
||||
Connected players
|
||||
Active admins
|
||||
Active game rules
|
||||
Active game preset
|
||||
Active map
|
||||
MOTD
|
||||
Panic bunker status
|
||||
*/
|
||||
|
||||
var info = await RunOnMainThread<InfoResponse>(() =>
|
||||
{
|
||||
var ticker = _entitySystemManager.GetEntitySystem<GameTicker>();
|
||||
var adminSystem = _entitySystemManager.GetEntitySystem<AdminSystem>();
|
||||
|
||||
var players = new List<InfoResponse.Player>();
|
||||
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
var adminData = _adminManager.GetAdminData(player, true);
|
||||
|
||||
players.Add(new InfoResponse.Player
|
||||
{
|
||||
UserId = player.UserId.UserId,
|
||||
Name = player.Name,
|
||||
IsAdmin = adminData != null,
|
||||
IsDeadminned = !adminData?.Active ?? false
|
||||
});
|
||||
}
|
||||
|
||||
InfoResponse.MapInfo? mapInfo = null;
|
||||
if (_gameMapManager.GetSelectedMap() is { } mapPrototype)
|
||||
{
|
||||
mapInfo = new InfoResponse.MapInfo
|
||||
{
|
||||
Id = mapPrototype.ID,
|
||||
Name = mapPrototype.MapName
|
||||
};
|
||||
}
|
||||
|
||||
var gameRules = new List<string>();
|
||||
foreach (var addedGameRule in ticker.GetActiveGameRules())
|
||||
{
|
||||
var meta = _entityManager.MetaQuery.GetComponent(addedGameRule);
|
||||
gameRules.Add(meta.EntityPrototype?.ID ?? meta.EntityPrototype?.Name ?? "Unknown");
|
||||
}
|
||||
|
||||
var panicBunkerCVars = PanicBunkerCVars.ToDictionary(c => c, c => _config.GetCVar(c));
|
||||
return new InfoResponse
|
||||
{
|
||||
Players = players,
|
||||
RoundId = ticker.RoundId,
|
||||
Map = mapInfo,
|
||||
PanicBunker = panicBunkerCVars,
|
||||
GamePreset = ticker.CurrentPreset?.ID,
|
||||
GameRules = gameRules,
|
||||
MOTD = _config.GetCVar(CCVars.MOTD)
|
||||
};
|
||||
});
|
||||
|
||||
await context.RespondJsonAsync(info);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<bool> CheckAccess(IStatusHandlerContext context)
|
||||
{
|
||||
var auth = context.RequestHeaders.TryGetValue("Authorization", out var authToken);
|
||||
if (!auth)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.AuthenticationNeeded,
|
||||
HttpStatusCode.Unauthorized,
|
||||
"Authorization is required");
|
||||
return false;
|
||||
}
|
||||
|
||||
var authHeaderValue = authToken.ToString();
|
||||
var spaceIndex = authHeaderValue.IndexOf(' ');
|
||||
if (spaceIndex == -1)
|
||||
{
|
||||
await RespondBadRequest(context, "Invalid Authorization header value");
|
||||
return false;
|
||||
}
|
||||
|
||||
var authScheme = authHeaderValue[..spaceIndex];
|
||||
var authValue = authHeaderValue[spaceIndex..].Trim();
|
||||
|
||||
if (authScheme != SS14TokenScheme)
|
||||
{
|
||||
await RespondBadRequest(context, "Invalid Authorization scheme");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_token == "")
|
||||
{
|
||||
_sawmill.Debug("No authorization token set for admin API");
|
||||
}
|
||||
else if (CryptographicOperations.FixedTimeEquals(
|
||||
Encoding.UTF8.GetBytes(authValue),
|
||||
Encoding.UTF8.GetBytes(_token)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.AuthenticationInvalid,
|
||||
HttpStatusCode.Unauthorized,
|
||||
"Authorization is invalid");
|
||||
|
||||
// Invalid auth header, no access
|
||||
_sawmill.Info($"Unauthorized access attempt to admin API from {context.RemoteEndPoint}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<Actor?> CheckActor(IStatusHandlerContext context)
|
||||
{
|
||||
// The actor is JSON encoded in the header
|
||||
var actor = context.RequestHeaders.TryGetValue("Actor", out var actorHeader) ? actorHeader.ToString() : null;
|
||||
if (actor == null)
|
||||
{
|
||||
await RespondBadRequest(context, "Actor must be supplied");
|
||||
return null;
|
||||
}
|
||||
|
||||
Actor? actorData;
|
||||
try
|
||||
{
|
||||
actorData = JsonSerializer.Deserialize<Actor>(actor);
|
||||
if (actorData == null)
|
||||
{
|
||||
await RespondBadRequest(context, "Actor is null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (JsonException exception)
|
||||
{
|
||||
await RespondBadRequest(context, "Actor field JSON is invalid", ExceptionData.FromException(exception));
|
||||
return null;
|
||||
}
|
||||
|
||||
return actorData;
|
||||
}
|
||||
|
||||
#region From Client
|
||||
|
||||
private sealed class Actor
|
||||
{
|
||||
public required Guid Guid { get; init; }
|
||||
public required string Name { get; init; }
|
||||
}
|
||||
|
||||
private sealed class KickActionBody
|
||||
{
|
||||
public required Guid Guid { get; init; }
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
private sealed class GameRuleActionBody
|
||||
{
|
||||
public required string GameRuleId { get; init; }
|
||||
}
|
||||
|
||||
private sealed class PresetActionBody
|
||||
{
|
||||
public required string PresetId { get; init; }
|
||||
}
|
||||
|
||||
private sealed class MotdActionBody
|
||||
{
|
||||
public required string Motd { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Responses
|
||||
|
||||
private record BaseResponse(
|
||||
string Message,
|
||||
ErrorCode ErrorCode = ErrorCode.None,
|
||||
ExceptionData? Exception = null);
|
||||
|
||||
private record ExceptionData(string Message, string? StackTrace = null)
|
||||
{
|
||||
public static ExceptionData FromException(Exception e)
|
||||
{
|
||||
return new ExceptionData(e.Message, e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private enum ErrorCode
|
||||
{
|
||||
None = 0,
|
||||
AuthenticationNeeded = 1,
|
||||
AuthenticationInvalid = 2,
|
||||
InvalidRoundState = 3,
|
||||
PlayerNotFound = 4,
|
||||
GameRuleNotFound = 5,
|
||||
BadRequest = 6,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Misc
|
||||
|
||||
/// <summary>
|
||||
/// Record used to send the response for the info endpoint.
|
||||
/// </summary>
|
||||
private sealed class InfoResponse
|
||||
{
|
||||
public required int RoundId { get; init; }
|
||||
public required List<Player> Players { get; init; }
|
||||
public required List<string> GameRules { get; init; }
|
||||
public required string? GamePreset { get; init; }
|
||||
public required MapInfo? Map { get; init; }
|
||||
public required string? MOTD { get; init; }
|
||||
public required Dictionary<string, object> PanicBunker { get; init; }
|
||||
|
||||
public sealed class Player
|
||||
{
|
||||
public required Guid UserId { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required bool IsAdmin { get; init; }
|
||||
public required bool IsDeadminned { get; init; }
|
||||
}
|
||||
|
||||
public sealed class MapInfo
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Name { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PresetResponse
|
||||
{
|
||||
public required List<Preset> Presets { get; init; }
|
||||
|
||||
public sealed class Preset
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required string ModeTitle { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class GameruleResponse
|
||||
{
|
||||
public required List<string> GameRules { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace Content.Server.Administration.Systems
|
||||
public IReadOnlySet<NetUserId> RoundActivePlayers => _roundActivePlayers;
|
||||
|
||||
private readonly HashSet<NetUserId> _roundActivePlayers = new();
|
||||
public readonly PanicBunkerStatus PanicBunker = new();
|
||||
private readonly PanicBunkerStatus _panicBunker = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -240,7 +240,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
private void OnPanicBunkerChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.Enabled = enabled;
|
||||
_panicBunker.Enabled = enabled;
|
||||
_chat.SendAdminAlert(Loc.GetString(enabled
|
||||
? "admin-ui-panic-bunker-enabled-admin-alert"
|
||||
: "admin-ui-panic-bunker-disabled-admin-alert"
|
||||
@@ -251,52 +251,52 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.DisableWithAdmins = enabled;
|
||||
_panicBunker.DisableWithAdmins = enabled;
|
||||
UpdatePanicBunker();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerEnableWithoutAdminsChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.EnableWithoutAdmins = enabled;
|
||||
_panicBunker.EnableWithoutAdmins = enabled;
|
||||
UpdatePanicBunker();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.CountDeadminnedAdmins = enabled;
|
||||
_panicBunker.CountDeadminnedAdmins = enabled;
|
||||
UpdatePanicBunker();
|
||||
}
|
||||
|
||||
private void OnShowReasonChanged(bool enabled)
|
||||
{
|
||||
PanicBunker.ShowReason = enabled;
|
||||
_panicBunker.ShowReason = enabled;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
|
||||
{
|
||||
PanicBunker.MinAccountAgeHours = minutes / 60;
|
||||
_panicBunker.MinAccountAgeHours = minutes / 60;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void OnPanicBunkerMinOverallHoursChanged(int hours)
|
||||
{
|
||||
PanicBunker.MinOverallHours = hours;
|
||||
_panicBunker.MinOverallHours = hours;
|
||||
SendPanicBunkerStatusAll();
|
||||
}
|
||||
|
||||
private void UpdatePanicBunker()
|
||||
{
|
||||
var admins = PanicBunker.CountDeadminnedAdmins
|
||||
var admins = _panicBunker.CountDeadminnedAdmins
|
||||
? _adminManager.AllAdmins
|
||||
: _adminManager.ActiveAdmins;
|
||||
var hasAdmins = admins.Any();
|
||||
|
||||
if (hasAdmins && PanicBunker.DisableWithAdmins)
|
||||
if (hasAdmins && _panicBunker.DisableWithAdmins)
|
||||
{
|
||||
_config.SetCVar(CCVars.PanicBunkerEnabled, false);
|
||||
}
|
||||
else if (!hasAdmins && PanicBunker.EnableWithoutAdmins)
|
||||
else if (!hasAdmins && _panicBunker.EnableWithoutAdmins)
|
||||
{
|
||||
_config.SetCVar(CCVars.PanicBunkerEnabled, true);
|
||||
}
|
||||
@@ -306,7 +306,7 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
private void SendPanicBunkerStatusAll()
|
||||
{
|
||||
var ev = new PanicBunkerChangedEvent(PanicBunker);
|
||||
var ev = new PanicBunkerChangedEvent(_panicBunker);
|
||||
foreach (var admin in _adminManager.AllAdmins)
|
||||
{
|
||||
RaiseNetworkEvent(ev, admin);
|
||||
|
||||
@@ -24,14 +24,6 @@ public sealed partial class AdvertiseComponent : Component
|
||||
[DataField]
|
||||
public int MaximumWait { get; private set; } = 10 * 60;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the delay before the first advertisement (at MapInit) will ignore <see cref="MinimumWait"/>
|
||||
/// and instead be rolled between 0 and <see cref="MaximumWait"/>. This only applies to the initial delay;
|
||||
/// <see cref="MinimumWait"/> will be respected after that.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Prewarm = true;
|
||||
|
||||
/// <summary>
|
||||
/// The identifier for the advertisements pack prototype.
|
||||
/// </summary>
|
||||
|
||||
@@ -37,14 +37,13 @@ public sealed class AdvertiseSystem : EntitySystem
|
||||
|
||||
private void OnMapInit(EntityUid uid, AdvertiseComponent advert, MapInitEvent args)
|
||||
{
|
||||
var prewarm = advert.Prewarm;
|
||||
RandomizeNextAdvertTime(advert, prewarm);
|
||||
RandomizeNextAdvertTime(advert);
|
||||
_nextCheckTime = MathHelper.Min(advert.NextAdvertisementTime, _nextCheckTime);
|
||||
}
|
||||
|
||||
private void RandomizeNextAdvertTime(AdvertiseComponent advert, bool prewarm = false)
|
||||
private void RandomizeNextAdvertTime(AdvertiseComponent advert)
|
||||
{
|
||||
var minDuration = prewarm ? 0 : Math.Max(1, advert.MinimumWait);
|
||||
var minDuration = Math.Max(1, advert.MinimumWait);
|
||||
var maxDuration = Math.Max(minDuration, advert.MaximumWait);
|
||||
var waitDuration = TimeSpan.FromSeconds(_random.Next(minDuration, maxDuration));
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.CrystallPunk.Temperature;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Mobs;
|
||||
|
||||
namespace Content.Server.Audio;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Content.Server.Audio;
|
||||
public sealed class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
[ValidatePrototypeId<SoundCollectionPrototype>]
|
||||
private const string LobbyMusicCollection = "CPLobbyMusic";
|
||||
private const string LobbyMusicCollection = "LobbyMusic";
|
||||
|
||||
[Dependency] private readonly AudioSystem _serverAudio = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationRecords;
|
||||
using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Bed.Cryostorage;
|
||||
@@ -37,7 +32,6 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly ClimbSystem _climb = default!;
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
@@ -46,7 +40,6 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
@@ -170,30 +163,26 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
{
|
||||
var comp = ent.Comp;
|
||||
var cryostorageEnt = ent.Comp.Cryostorage;
|
||||
|
||||
var station = _station.GetOwningStation(ent);
|
||||
var name = Name(ent.Owner);
|
||||
|
||||
if (!TryComp<CryostorageComponent>(cryostorageEnt, out var cryostorageComponent))
|
||||
return;
|
||||
|
||||
// if we have a session, we use that to add back in all the job slots the player had.
|
||||
if (userId != null)
|
||||
{
|
||||
foreach (var uniqueStation in _station.GetStationsSet())
|
||||
foreach (var station in _station.GetStationsSet())
|
||||
{
|
||||
if (!TryComp<StationJobsComponent>(uniqueStation, out var stationJobs))
|
||||
if (!TryComp<StationJobsComponent>(station, out var stationJobs))
|
||||
continue;
|
||||
|
||||
if (!_stationJobs.TryGetPlayerJobs(uniqueStation, userId.Value, out var jobs, stationJobs))
|
||||
if (!_stationJobs.TryGetPlayerJobs(station, userId.Value, out var jobs, stationJobs))
|
||||
continue;
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
_stationJobs.TryAdjustJobSlot(uniqueStation, job, 1, clamp: true);
|
||||
_stationJobs.TryAdjustJobSlot(station, job, 1, clamp: true);
|
||||
}
|
||||
|
||||
_stationJobs.TryRemovePlayerJobs(uniqueStation, userId.Value, stationJobs);
|
||||
_stationJobs.TryRemovePlayerJobs(station, userId.Value, stationJobs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,36 +203,12 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
_gameTicker.OnGhostAttempt(mind.Value, false);
|
||||
}
|
||||
}
|
||||
|
||||
comp.AllowReEnteringBody = false;
|
||||
_transform.SetParent(ent, PausedMap.Value);
|
||||
cryostorageComponent.StoredPlayers.Add(ent);
|
||||
Dirty(ent, comp);
|
||||
UpdateCryostorageUIState((cryostorageEnt.Value, cryostorageComponent));
|
||||
AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(ent):player} was entered into cryostorage inside of {ToPrettyString(cryostorageEnt.Value)}");
|
||||
|
||||
if (!TryComp<StationRecordsComponent>(station, out var stationRecords))
|
||||
return;
|
||||
|
||||
var jobName = Loc.GetString("earlyleave-cryo-job-unknown");
|
||||
var recordId = _stationRecords.GetRecordByName(station.Value, name);
|
||||
if (recordId != null)
|
||||
{
|
||||
var key = new StationRecordKey(recordId.Value, station.Value);
|
||||
if (_stationRecords.TryGetRecord<GeneralStationRecord>(key, out var entry, stationRecords))
|
||||
jobName = entry.JobTitle;
|
||||
|
||||
_stationRecords.RemoveRecord(key, stationRecords);
|
||||
}
|
||||
|
||||
_chatSystem.DispatchStationAnnouncement(station.Value,
|
||||
Loc.GetString(
|
||||
"earlyleave-cryo-announcement",
|
||||
("character", name),
|
||||
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
|
||||
), Loc.GetString("earlyleave-cryo-sender"),
|
||||
playDefaultSound: false
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleCryostorageReconnection(Entity<CryostorageContainedComponent> entity)
|
||||
|
||||
@@ -159,6 +159,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
if (!_botany.TryGetSeed(seeds, out var seed))
|
||||
return;
|
||||
|
||||
float? seedHealth = seeds.HealthOverride;
|
||||
var name = Loc.GetString(seed.Name);
|
||||
var noun = Loc.GetString(seed.Noun);
|
||||
_popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message",
|
||||
@@ -168,9 +169,9 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
component.Seed = seed;
|
||||
component.Dead = false;
|
||||
component.Age = 1;
|
||||
if (seeds.HealthOverride != null)
|
||||
if (seedHealth is float realSeedHealth)
|
||||
{
|
||||
component.Health = seeds.HealthOverride.Value;
|
||||
component.Health = realSeedHealth;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -287,18 +288,8 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
}
|
||||
|
||||
component.Health -= (_random.Next(3, 5) * 10);
|
||||
|
||||
float? healthOverride;
|
||||
if (component.Harvest)
|
||||
{
|
||||
healthOverride = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
healthOverride = component.Health;
|
||||
}
|
||||
component.Seed.Unique = false;
|
||||
var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, healthOverride);
|
||||
var seed = _botany.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates, args.User, component.Health);
|
||||
_randomHelper.RandomOffset(seed, 0.25f);
|
||||
var displayName = Loc.GetString(component.Seed.DisplayName);
|
||||
_popup.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Cargo;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
@@ -33,16 +32,4 @@ public sealed partial class StationCargoBountyDatabaseComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
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.NameIdentifier;
|
||||
using Content.Server.Paper;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
@@ -35,7 +35,6 @@ public sealed partial class CargoSystem
|
||||
{
|
||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
|
||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
|
||||
SubscribeLocalEvent<CargoBountyConsoleComponent, BountySkipMessage>(OnSkipBountyMessage);
|
||||
SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
|
||||
SubscribeLocalEvent<EntitySoldEvent>(OnSold);
|
||||
SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
|
||||
@@ -51,8 +50,7 @@ public sealed partial class CargoSystem
|
||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
|
||||
return;
|
||||
|
||||
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties));
|
||||
}
|
||||
|
||||
private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
|
||||
@@ -72,37 +70,6 @@ public sealed partial class CargoSystem
|
||||
_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)
|
||||
{
|
||||
if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
|
||||
@@ -464,8 +431,7 @@ public sealed partial class CargoSystem
|
||||
!TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
|
||||
continue;
|
||||
|
||||
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui);
|
||||
_uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties), ui: ui);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,8 @@ namespace Content.Server.Chat.Managers
|
||||
var prefs = _preferencesManager.GetPreferences(player.UserId);
|
||||
colorOverride = prefs.AdminOOCColor;
|
||||
}
|
||||
if ( _netConfigManager.GetClientCVar(player.Channel, CCVars.ShowOocPatronColor) && player.Channel.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor))
|
||||
if (player.Channel.UserData.PatronTier is { } patron &&
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -149,12 +149,12 @@ public sealed partial class SolutionContainerSystem : SharedSolutionContainerSys
|
||||
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
|
||||
AddComp(uid, relation);
|
||||
|
||||
MetaData.SetEntityName(uid, $"solution - {name}");
|
||||
ContainerSystem.Insert(uid, container, force: true);
|
||||
|
||||
return (uid, solution, relation);
|
||||
}
|
||||
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
@@ -11,7 +10,6 @@ using Content.Shared.Players.PlayTimeTracking;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
|
||||
namespace Content.Server.Connection
|
||||
@@ -19,18 +17,6 @@ namespace Content.Server.Connection
|
||||
public interface IConnectionManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <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>
|
||||
@@ -45,31 +31,15 @@ namespace Content.Server.Connection
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = 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!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("connections");
|
||||
|
||||
_netMgr.Connecting += NetMgrOnConnecting;
|
||||
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
|
||||
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
|
||||
// _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)
|
||||
{
|
||||
@@ -139,20 +109,6 @@ namespace Content.Server.Connection
|
||||
hwId = null;
|
||||
}
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
|
||||
if (bans.Count > 0)
|
||||
{
|
||||
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);
|
||||
|
||||
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
|
||||
@@ -211,6 +167,14 @@ namespace Content.Server.Connection
|
||||
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
|
||||
}
|
||||
|
||||
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
|
||||
if (bans.Count > 0)
|
||||
{
|
||||
var firstBan = bans[0];
|
||||
var message = firstBan.FormatBanMessage(_cfg, _loc);
|
||||
return (ConnectionDenyReason.Ban, message, bans);
|
||||
}
|
||||
|
||||
if (_cfg.GetCVar(CCVars.WhitelistEnabled))
|
||||
{
|
||||
var min = _cfg.GetCVar(CCVars.WhitelistMinPlayers);
|
||||
@@ -231,11 +195,6 @@ namespace Content.Server.Connection
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasTemporaryBypass(NetUserId user)
|
||||
{
|
||||
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
|
||||
}
|
||||
|
||||
private async Task<NetUserId?> AssignUserIdCallback(string name)
|
||||
{
|
||||
if (!_cfg.GetCVar(CCVars.GamePersistGuests))
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Content.Server.StationRecords.Systems;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Server.GameTicking;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.CriminalRecords.Systems;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Content.Server.CriminalRecords.Systems;
|
||||
/// </summary>
|
||||
public sealed class CriminalRecordsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -71,7 +71,7 @@ public sealed class CriminalRecordsSystem : EntitySystem
|
||||
/// </summary>
|
||||
public bool TryAddHistory(StationRecordKey key, string line)
|
||||
{
|
||||
var entry = new CrimeHistory(_ticker.RoundDuration(), line);
|
||||
var entry = new CrimeHistory(_timing.CurTime, line);
|
||||
return TryAddHistory(key, entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,6 @@ namespace Content.Server.Damage.Components;
|
||||
[RegisterComponent, Access(typeof(DamagePopupSystem))]
|
||||
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>
|
||||
/// Enum that will be used to determine the type of damage popup displayed.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server._CP14.MeleeWeapon;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
@@ -33,14 +32,7 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
//CrystallPunk Melee upgrade
|
||||
var damage = component.Damage;
|
||||
|
||||
if (TryComp<CPSharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
//CrystallPunk Melee pgrade end
|
||||
var dmg = _damageable.TryChangeDamage(args.Target, component.Damage, component.IgnoreResistances, origin: args.Component.Thrower);
|
||||
|
||||
// Log damage only for mobs. Useful for when people throw spears at each other, but also avoids log-spam when explosions send glass shards flying.
|
||||
if (dmg != null && HasComp<MobStateComponent>(args.Target))
|
||||
@@ -67,12 +59,7 @@ namespace Content.Server.Damage.Systems
|
||||
|
||||
private void OnDamageExamine(EntityUid uid, DamageOtherOnHitComponent component, ref DamageExamineEvent args)
|
||||
{
|
||||
var damage = component.Damage;
|
||||
|
||||
if (TryComp<CPSharpenedComponent>(uid, out var sharp))
|
||||
damage *= sharp.Sharpness;
|
||||
|
||||
_damageExamine.AddDamageExamine(args.Message, damage, Loc.GetString("damage-throw"));
|
||||
_damageExamine.AddDamageExamine(args.Message, component.Damage, Loc.GetString("damage-throw"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Damage.Systems;
|
||||
|
||||
@@ -14,7 +13,6 @@ public sealed class DamagePopupSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<DamagePopupComponent, DamageChangedEvent>(OnDamageChange);
|
||||
SubscribeLocalEvent<DamagePopupComponent, InteractHandEvent>(OnInteractHand);
|
||||
}
|
||||
|
||||
private void OnDamageChange(EntityUid uid, DamagePopupComponent component, DamageChangedEvent args)
|
||||
@@ -35,20 +33,4 @@ public sealed class DamagePopupSystem : EntitySystem
|
||||
_popupSystem.PopupEntity(msg, uid);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,6 @@ public sealed record PlayerRecord(
|
||||
IPAddress LastSeenAddress,
|
||||
ImmutableArray<byte>? HWId);
|
||||
|
||||
public sealed record RoundRecord(int Id, DateTimeOffset? StartDate, ServerRecord Server);
|
||||
public sealed record RoundRecord(int Id, DateTimeOffset StartDate, ServerRecord Server);
|
||||
|
||||
public sealed record ServerRecord(int Id, string Name);
|
||||
|
||||
@@ -13,8 +13,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Preferences.Loadouts.Effects;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -42,10 +40,6 @@ namespace Content.Server.Database
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
|
||||
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
|
||||
.Include(p => p.Profiles)
|
||||
.ThenInclude(h => h.Loadouts)
|
||||
.ThenInclude(l => l.Groups)
|
||||
.ThenInclude(group => group.Loadouts)
|
||||
.AsSingleQuery()
|
||||
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
|
||||
|
||||
@@ -94,9 +88,6 @@ namespace Content.Server.Database
|
||||
.Include(p => p.Jobs)
|
||||
.Include(p => p.Antags)
|
||||
.Include(p => p.Traits)
|
||||
.Include(p => p.Loadouts)
|
||||
.ThenInclude(l => l.Groups)
|
||||
.ThenInclude(group => group.Loadouts)
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefault(h => h.Slot == slot);
|
||||
|
||||
@@ -188,6 +179,14 @@ namespace Content.Server.Database
|
||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||
sex = sexVal;
|
||||
|
||||
var clothing = ClothingPreference.Jumpsuit;
|
||||
if (Enum.TryParse<ClothingPreference>(profile.Clothing, true, out var clothingVal))
|
||||
clothing = clothingVal;
|
||||
|
||||
var backpack = BackpackPreference.Backpack;
|
||||
if (Enum.TryParse<BackpackPreference>(profile.Backpack, true, out var backpackVal))
|
||||
backpack = backpackVal;
|
||||
|
||||
var spawnPriority = (SpawnPriorityPreference) profile.SpawnPriority;
|
||||
|
||||
var gender = sex == Sex.Male ? Gender.Male : Gender.Female;
|
||||
@@ -210,27 +209,6 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
var loadouts = new Dictionary<string, RoleLoadout>();
|
||||
|
||||
foreach (var role in profile.Loadouts)
|
||||
{
|
||||
var loadout = new RoleLoadout(role.RoleName);
|
||||
|
||||
foreach (var group in role.Groups)
|
||||
{
|
||||
var groupLoadouts = loadout.SelectedLoadouts.GetOrNew(group.GroupName);
|
||||
foreach (var profLoadout in group.Loadouts)
|
||||
{
|
||||
groupLoadouts.Add(new Loadout()
|
||||
{
|
||||
Prototype = profLoadout.LoadoutName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadouts[role.RoleName] = loadout;
|
||||
}
|
||||
|
||||
return new HumanoidCharacterProfile(
|
||||
profile.CharacterName,
|
||||
profile.FlavorText,
|
||||
@@ -248,12 +226,13 @@ namespace Content.Server.Database
|
||||
Color.FromHex(profile.SkinColor),
|
||||
markings
|
||||
),
|
||||
clothing,
|
||||
backpack,
|
||||
spawnPriority,
|
||||
jobs,
|
||||
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
|
||||
antags.ToList(),
|
||||
traits.ToList(),
|
||||
loadouts
|
||||
traits.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -280,6 +259,8 @@ namespace Content.Server.Database
|
||||
profile.FacialHairColor = appearance.FacialHairColor.ToHex();
|
||||
profile.EyeColor = appearance.EyeColor.ToHex();
|
||||
profile.SkinColor = appearance.SkinColor.ToHex();
|
||||
profile.Clothing = humanoid.Clothing.ToString();
|
||||
profile.Backpack = humanoid.Backpack.ToString();
|
||||
profile.SpawnPriority = (int) humanoid.SpawnPriority;
|
||||
profile.Markings = markings;
|
||||
profile.Slot = slot;
|
||||
@@ -304,36 +285,6 @@ namespace Content.Server.Database
|
||||
.Select(t => new Trait {TraitName = t})
|
||||
);
|
||||
|
||||
profile.Loadouts.Clear();
|
||||
|
||||
foreach (var (role, loadouts) in humanoid.Loadouts)
|
||||
{
|
||||
var dz = new ProfileRoleLoadout()
|
||||
{
|
||||
RoleName = role,
|
||||
};
|
||||
|
||||
foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts)
|
||||
{
|
||||
var profileGroup = new ProfileLoadoutGroup()
|
||||
{
|
||||
GroupName = group,
|
||||
};
|
||||
|
||||
foreach (var loadout in groupLoadouts)
|
||||
{
|
||||
profileGroup.Loadouts.Add(new ProfileLoadout()
|
||||
{
|
||||
LoadoutName = loadout.Prototype,
|
||||
});
|
||||
}
|
||||
|
||||
dz.Groups.Add(profileGroup);
|
||||
}
|
||||
|
||||
profile.Loadouts.Add(dz);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
#endregion
|
||||
@@ -745,7 +696,7 @@ namespace Content.Server.Database
|
||||
await db.DbContext.SaveChangesAsync(cancel);
|
||||
}
|
||||
|
||||
public async Task<int> AddNewRound(Server server, params Guid[] playerIds)
|
||||
public virtual async Task<int> AddNewRound(Server server, params Guid[] playerIds)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
|
||||
@@ -452,6 +452,34 @@ namespace Content.Server.Database
|
||||
return (admins.Select(p => (p.a, p.LastSeenUserName)).ToArray(), adminRanks)!;
|
||||
}
|
||||
|
||||
public override async Task<int> AddNewRound(Server server, params Guid[] playerIds)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
|
||||
var players = await db.DbContext.Player
|
||||
.Where(player => playerIds.Contains(player.UserId))
|
||||
.ToListAsync();
|
||||
|
||||
var nextId = 1;
|
||||
if (await db.DbContext.Round.AnyAsync())
|
||||
{
|
||||
nextId = db.DbContext.Round.Max(round => round.Id) + 1;
|
||||
}
|
||||
|
||||
var round = new Round
|
||||
{
|
||||
Id = nextId,
|
||||
Players = players,
|
||||
ServerId = server.Id
|
||||
};
|
||||
|
||||
db.DbContext.Round.Add(round);
|
||||
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
|
||||
return round.Id;
|
||||
}
|
||||
|
||||
protected override IQueryable<AdminLog> StartAdminLogsQuery(ServerDbContext db, LogFilter? filter = null)
|
||||
{
|
||||
IQueryable<AdminLog> query = db.AdminLog;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user