Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<BoxContainer Orientation="Vertical">
<!-- Jobs -->
<OptionButton Name="PreferenceUnavailableButton" />
<!-- Amour edit -->
<Button Name="BaseLoadoutButton" Text="{Loc loadout-window-base}" Margin="0 5" />
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="JobList" Orientation="Vertical" />
</ScrollContainer>
Expand Down
273 changes: 258 additions & 15 deletions Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions Content.Client/Lobby/UI/Loadouts/LoadoutContainer.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Content.Shared.Clothing;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
Expand All @@ -20,6 +21,14 @@

namespace Content.Client.Lobby.UI.Loadouts;

// Amour edit start
public enum LoadoutSourceType
{
UserSelected,
InheritedFromBase
}
// Amour edit end

[GenerateTypedNameReferences]
public sealed partial class LoadoutContainer : BoxContainer
{
Expand All @@ -28,6 +37,12 @@ public sealed partial class LoadoutContainer : BoxContainer

private readonly EntityUid? _entity;

// Amour edit start
private LoadoutSourceType _sourceType = LoadoutSourceType.UserSelected;

private static readonly Color ColorInheritedFromBase = new Color(0.4f, 0.7f, 0.9f, 1.0f);
// Amour edit end

public Button Select => SelectButton;

public string? Text
Expand All @@ -36,6 +51,33 @@ public string? Text
set => SelectButton.Text = value;
}

// Amour edit start
public LoadoutSourceType SourceType
{
get => _sourceType;
set
{
_sourceType = value;
UpdateButtonStyle();
}
}

private void UpdateButtonStyle()
{
if (!SelectButton.Pressed)
{
SelectButton.ModulateSelfOverride = null;
return;
}

SelectButton.ModulateSelfOverride = _sourceType switch
{
LoadoutSourceType.InheritedFromBase => ColorInheritedFromBase,
_ => null
};
}
// Amour edit end

public LoadoutContainer(ProtoId<LoadoutPrototype> proto, bool disabled, FormattedMessage? reason)
{
RobustXamlLoader.Load(this);
Expand Down
28 changes: 28 additions & 0 deletions Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ private void UpdateSubGroupSelectedInfo(LoadoutContainer loadout, string itemNam
/// <returns>A fully initialized LoadoutContainer for UI display.</returns>
private LoadoutContainer CreateLoadoutUI(LoadoutPrototype proto, HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, LoadoutSystem loadoutSystem)
{
var protoMan = collection.Resolve<IPrototypeManager>();
var selected = loadout.SelectedLoadouts[_groupProto.ID];

var pressed = selected.Any(e => e.Prototype == proto.ID);
Expand All @@ -234,6 +235,13 @@ private LoadoutContainer CreateLoadoutUI(LoadoutPrototype proto, HumanoidCharact

cont.Select.Pressed = pressed;

// Amour edit start
if (pressed)
{
cont.SourceType = DetermineLoadoutSourceType(proto.ID, loadout, profile);
}
// Amour edit end

cont.Select.OnPressed += args =>
{
if (args.Button.Pressed)
Expand All @@ -244,4 +252,24 @@ private LoadoutContainer CreateLoadoutUI(LoadoutPrototype proto, HumanoidCharact

return cont;
}

// Amour edit start
private LoadoutSourceType DetermineLoadoutSourceType(
ProtoId<LoadoutPrototype> loadoutId,
RoleLoadout roleLoadout,
HumanoidCharacterProfile profile)
{
// Check if this group is NOT overridden and the loadout comes from base
if (!roleLoadout.OverriddenGroups.Contains(_groupProto.ID))
{
if (profile.BaseLoadout.SelectedLoadouts.TryGetValue(_groupProto.ID, out var baseSelected))
{
if (baseSelected.Any(l => l.Prototype == loadoutId))
return LoadoutSourceType.InheritedFromBase;
}
}

return LoadoutSourceType.UserSelected;
}
// Amour edit end
}
5 changes: 5 additions & 0 deletions Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-or-later
SetSize="800 800"
MinSize="800 128">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<!-- Amour edit start -->
<BoxContainer Name="HeaderButtons" Orientation="Horizontal" Margin="10" HorizontalExpand="True">
<Button Name="RevertToBaseButton" Text="{Loc loadout-window-revert-to-base}" Visible="False"/>
</BoxContainer>
<!-- Amour edit end -->
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
<Label Name="LoadoutNameLabel"/>
<PanelContainer HorizontalExpand="True" SetHeight="24">
Expand Down
8 changes: 7 additions & 1 deletion Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public sealed partial class LoadoutWindow : FancyWindow
public event Action<string>? OnNameChanged;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<LoadoutGroupPrototype>, ProtoId<LoadoutPrototype>>? OnLoadoutUnpressed;
public event Action? OnRevertToBase; // Amour edit

private List<LoadoutGroupContainer> _groups = new();

Expand All @@ -76,10 +77,15 @@ public sealed partial class LoadoutWindow : FancyWindow
// CCvar.
private int _maxLoadoutNameLength;

public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
// Amour edit start
public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection, bool showRevertToBase = false)
{
RobustXamlLoader.Load(this);
Profile = profile;

RevertToBaseButton.Visible = showRevertToBase;
RevertToBaseButton.OnPressed += _ => OnRevertToBase?.Invoke();
// Amour edit end
var protoManager = collection.Resolve<IPrototypeManager>();
var configManager = collection.Resolve<IConfigurationManager>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2026
// SPDX-License-Identifier: MIT

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Content.Server.Database.Migrations.Postgres;

[DbContext(typeof(PostgresServerDbContext))]
[Migration("20260131214748_BaseLoadouts")]
/// <inheritdoc />
public partial class BaseLoadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "is_base",
table: "profile_role_loadout",
type: "boolean",
nullable: false,
defaultValue: false);

migrationBuilder.AddColumn<string>(
name: "overridden_groups",
table: "profile_role_loadout",
type: "text",
nullable: true);

migrationBuilder.AddColumn<bool>(
name: "entity_name_overridden",
table: "profile_role_loadout",
type: "boolean",
nullable: false,
defaultValue: false);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "is_base", table: "profile_role_loadout");
migrationBuilder.DropColumn(name: "overridden_groups", table: "profile_role_loadout");
migrationBuilder.DropColumn(name: "entity_name_overridden", table: "profile_role_loadout");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,18 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("character varying(256)")
.HasColumnName("entity_name");

b.Property<bool>("EntityNameOverridden")
.HasColumnType("boolean")
.HasColumnName("entity_name_overridden");

b.Property<bool>("IsBase")
.HasColumnType("boolean")
.HasColumnName("is_base");

b.Property<string>("OverriddenGroups")
.HasColumnType("text")
.HasColumnName("overridden_groups");

b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2026
// SPDX-License-Identifier: MIT

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Content.Server.Database.Migrations.Sqlite;

[DbContext(typeof(SqliteServerDbContext))]
[Migration("20260131214748_BaseLoadouts")]
/// <inheritdoc />
public partial class BaseLoadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "is_base",
table: "profile_role_loadout",
type: "INTEGER",
nullable: false,
defaultValue: false);

migrationBuilder.AddColumn<string>(
name: "overridden_groups",
table: "profile_role_loadout",
type: "TEXT",
nullable: true);

migrationBuilder.AddColumn<bool>(
name: "entity_name_overridden",
table: "profile_role_loadout",
type: "INTEGER",
nullable: false,
defaultValue: false);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "is_base", table: "profile_role_loadout");
migrationBuilder.DropColumn(name: "overridden_groups", table: "profile_role_loadout");
migrationBuilder.DropColumn(name: "entity_name_overridden", table: "profile_role_loadout");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,18 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("TEXT")
.HasColumnName("entity_name");

b.Property<bool>("EntityNameOverridden")
.HasColumnType("INTEGER")
.HasColumnName("entity_name_overridden");

b.Property<bool>("IsBase")
.HasColumnType("INTEGER")
.HasColumnName("is_base");

b.Property<string>("OverriddenGroups")
.HasColumnType("TEXT")
.HasColumnName("overridden_groups");

b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
Expand Down
19 changes: 19 additions & 0 deletions Content.Server.Database/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -748,12 +748,31 @@ public class ProfileRoleLoadout
/// </summary>
public string RoleName { get; set; } = string.Empty;

// Amour edit start
/// <summary>
/// If true, this entry represents the profile's base (global) loadout.
/// </summary>
public bool IsBase { get; set; }

/// <summary>
/// Comma-separated list of group IDs that are overridden for this role.
/// </summary>
public string? OverriddenGroups { get; set; }
// Amour edit end

/// <summary>
/// Custom name of the role loadout if it supports it.
/// </summary>
[MaxLength(256)]
public string? EntityName { get; set; }

// Amour edit start
/// <summary>
/// Whether <see cref="EntityName"/> is overridden for this role.
/// </summary>
public bool EntityNameOverridden { get; set; }
// Amour edit end

/// <summary>
/// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
/// </summary>
Expand Down
Loading
Loading