Implement traits system (#10693)
This commit is contained in:
@@ -2,8 +2,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:magicmirror="clr-namespace:Content.Client.CharacterAppearance"
|
xmlns:magicmirror="clr-namespace:Content.Client.CharacterAppearance"
|
||||||
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
|
xmlns:prefUi="clr-namespace:Content.Client.Preferences.UI"
|
||||||
xmlns:markings="clr-namespace:Content.Client.Markings"
|
xmlns:markings="clr-namespace:Content.Client.Markings">
|
||||||
xmlns:flavorText="clr-namespace:Content.Client.FlavorText">
|
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<!-- Left side -->
|
<!-- Left side -->
|
||||||
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
|
<BoxContainer Orientation="Vertical" Margin="10 10 10 10">
|
||||||
@@ -47,6 +46,7 @@
|
|||||||
</prefUi:HighlightedContainer>
|
</prefUi:HighlightedContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
<Control MinHeight="10" />
|
||||||
<!-- tabContainer -->
|
<!-- tabContainer -->
|
||||||
<TabContainer Name="CTabContainer" VerticalExpand="True">
|
<TabContainer Name="CTabContainer" VerticalExpand="True">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
@@ -133,13 +133,19 @@
|
|||||||
<BoxContainer Name="CJobList" Orientation="Vertical" />
|
<BoxContainer Name="CJobList" Orientation="Vertical" />
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical" Margin="10">
|
||||||
<!-- Antags -->
|
<!-- Antags -->
|
||||||
<ScrollContainer VerticalExpand="True">
|
<ScrollContainer VerticalExpand="True">
|
||||||
<BoxContainer Name="CAntagList" Orientation="Vertical" />
|
<BoxContainer Name="CAntagList" Orientation="Vertical" />
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<BoxContainer Name="CMarkingsTab" Orientation="Vertical">
|
<BoxContainer Orientation="Vertical" Margin="10">
|
||||||
|
<!-- Traits -->
|
||||||
|
<ScrollContainer VerticalExpand="True">
|
||||||
|
<BoxContainer Name="CTraitsList" Orientation="Vertical" />
|
||||||
|
</ScrollContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="CMarkingsTab" Orientation="Vertical" Margin="10">
|
||||||
<!-- Markings -->
|
<!-- Markings -->
|
||||||
<ScrollContainer VerticalExpand="True">
|
<ScrollContainer VerticalExpand="True">
|
||||||
<markings:MarkingPicker Name="CMarkings" />
|
<markings:MarkingPicker Name="CMarkings" />
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Content.Shared.Markings;
|
|||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Species;
|
using Content.Shared.Species;
|
||||||
|
using Content.Shared.Traits;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
@@ -80,12 +81,14 @@ namespace Content.Client.Preferences.UI
|
|||||||
private TabContainer _tabContainer => CTabContainer;
|
private TabContainer _tabContainer => CTabContainer;
|
||||||
private BoxContainer _jobList => CJobList;
|
private BoxContainer _jobList => CJobList;
|
||||||
private BoxContainer _antagList => CAntagList;
|
private BoxContainer _antagList => CAntagList;
|
||||||
|
private BoxContainer _traitsList => CTraitsList;
|
||||||
private readonly List<JobPrioritySelector> _jobPriorities;
|
private readonly List<JobPrioritySelector> _jobPriorities;
|
||||||
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
|
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
|
||||||
private readonly Dictionary<string, BoxContainer> _jobCategories;
|
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
|
// 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<SpeciesPrototype> _speciesList;
|
||||||
private readonly List<AntagPreferenceSelector> _antagPreferences;
|
private readonly List<AntagPreferenceSelector> _antagPreferences;
|
||||||
|
private readonly List<TraitPreferenceSelector> _traitPreferences;
|
||||||
|
|
||||||
private Control _previewSpriteControl => CSpriteViewFront;
|
private Control _previewSpriteControl => CSpriteViewFront;
|
||||||
private Control _previewSpriteSideControl => CSpriteViewSide;
|
private Control _previewSpriteSideControl => CSpriteViewSide;
|
||||||
@@ -461,14 +464,46 @@ namespace Content.Client.Preferences.UI
|
|||||||
|
|
||||||
#endregion Antags
|
#endregion Antags
|
||||||
|
|
||||||
|
#region Traits
|
||||||
|
|
||||||
|
var traits = prototypeManager.EnumeratePrototypes<TraitPrototype>().OrderBy(t => t.Name).ToList();
|
||||||
|
_traitPreferences = new List<TraitPreferenceSelector>();
|
||||||
|
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
|
||||||
|
|
||||||
|
if (traits.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var trait in traits)
|
||||||
|
{
|
||||||
|
var selector = new TraitPreferenceSelector(trait);
|
||||||
|
_traitsList.AddChild(selector);
|
||||||
|
_traitPreferences.Add(selector);
|
||||||
|
|
||||||
|
selector.PreferenceChanged += preference =>
|
||||||
|
{
|
||||||
|
Profile = Profile?.WithTraitPreference(trait.ID, preference);
|
||||||
|
IsDirty = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_traitsList.AddChild(new Label
|
||||||
|
{
|
||||||
|
Text = "No traits available :(",
|
||||||
|
FontColorOverride = Color.Gray,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Save
|
#region Save
|
||||||
|
|
||||||
_saveButton.OnPressed += args => { Save(); };
|
_saveButton.OnPressed += _ => { Save(); };
|
||||||
|
|
||||||
#endregion Save
|
#endregion Save
|
||||||
|
|
||||||
#region Markings
|
#region Markings
|
||||||
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-markings-tab"));
|
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
|
||||||
|
|
||||||
CMarkings.OnMarkingAdded += OnMarkingChange;
|
CMarkings.OnMarkingAdded += OnMarkingChange;
|
||||||
CMarkings.OnMarkingRemoved += OnMarkingChange;
|
CMarkings.OnMarkingRemoved += OnMarkingChange;
|
||||||
@@ -960,6 +995,7 @@ namespace Content.Client.Preferences.UI
|
|||||||
UpdateSaveButton();
|
UpdateSaveButton();
|
||||||
UpdateJobPriorities();
|
UpdateJobPriorities();
|
||||||
UpdateAntagPreferences();
|
UpdateAntagPreferences();
|
||||||
|
UpdateTraitPreferences();
|
||||||
UpdateMarkings();
|
UpdateMarkings();
|
||||||
|
|
||||||
NeedsDummyRebuild = true;
|
NeedsDummyRebuild = true;
|
||||||
@@ -1103,6 +1139,17 @@ namespace Content.Client.Preferences.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateTraitPreferences()
|
||||||
|
{
|
||||||
|
foreach (var preferenceSelector in _traitPreferences)
|
||||||
|
{
|
||||||
|
var traitId = preferenceSelector.Trait.ID;
|
||||||
|
var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
|
||||||
|
|
||||||
|
preferenceSelector.Preference = preference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class AntagPreferenceSelector : Control
|
private sealed class AntagPreferenceSelector : Control
|
||||||
{
|
{
|
||||||
public AntagPrototype Antag { get; }
|
public AntagPrototype Antag { get; }
|
||||||
@@ -1138,5 +1185,38 @@ namespace Content.Client.Preferences.UI
|
|||||||
PreferenceChanged?.Invoke(Preference);
|
PreferenceChanged?.Invoke(Preference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class TraitPreferenceSelector : Control
|
||||||
|
{
|
||||||
|
public TraitPrototype Trait { get; }
|
||||||
|
private readonly CheckBox _checkBox;
|
||||||
|
|
||||||
|
public bool Preference
|
||||||
|
{
|
||||||
|
get => _checkBox.Pressed;
|
||||||
|
set => _checkBox.Pressed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<bool>? PreferenceChanged;
|
||||||
|
|
||||||
|
public TraitPreferenceSelector(TraitPrototype trait)
|
||||||
|
{
|
||||||
|
Trait = trait;
|
||||||
|
|
||||||
|
_checkBox = new CheckBox {Text = $"{trait.Name}"};
|
||||||
|
_checkBox.OnToggled += OnCheckBoxToggled;
|
||||||
|
|
||||||
|
AddChild(new BoxContainer
|
||||||
|
{
|
||||||
|
Orientation = LayoutOrientation.Horizontal,
|
||||||
|
Children = { _checkBox },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
PreferenceChanged?.Invoke(Preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1333
Content.Server.Database/Migrations/Postgres/20220816163319_Traits.Designer.cs
generated
Normal file
1333
Content.Server.Database/Migrations/Postgres/20220816163319_Traits.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
public partial class Traits : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "trait",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
trait_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
profile_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
trait_name = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_trait", x => x.trait_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_trait_profile_profile_id",
|
||||||
|
column: x => x.profile_id,
|
||||||
|
principalTable: "profile",
|
||||||
|
principalColumn: "profile_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_trait_profile_id",
|
||||||
|
table: "trait",
|
||||||
|
column: "profile_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "trait");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -914,6 +914,33 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("server_unban", (string)null);
|
b.ToTable("server_unban", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("trait_id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("ProfileId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("profile_id");
|
||||||
|
|
||||||
|
b.Property<string>("TraitName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("trait_name");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_trait");
|
||||||
|
|
||||||
|
b.HasIndex("ProfileId")
|
||||||
|
.HasDatabaseName("IX_trait_profile_id");
|
||||||
|
|
||||||
|
b.ToTable("trait", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1197,6 +1224,18 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Ban");
|
b.Navigation("Ban");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||||
|
.WithMany("Traits")
|
||||||
|
.HasForeignKey("ProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_trait_profile_profile_id");
|
||||||
|
|
||||||
|
b.Navigation("Profile");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlayerRound", b =>
|
modelBuilder.Entity("PlayerRound", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Player", null)
|
b.HasOne("Content.Server.Database.Player", null)
|
||||||
@@ -1261,6 +1300,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Antags");
|
b.Navigation("Antags");
|
||||||
|
|
||||||
b.Navigation("Jobs");
|
b.Navigation("Jobs");
|
||||||
|
|
||||||
|
b.Navigation("Traits");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
|
|||||||
1267
Content.Server.Database/Migrations/Sqlite/20220816163313_Traits.Designer.cs
generated
Normal file
1267
Content.Server.Database/Migrations/Sqlite/20220816163313_Traits.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
public partial class Traits : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "trait",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
trait_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
trait_name = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_trait", x => x.trait_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_trait_profile_profile_id",
|
||||||
|
column: x => x.profile_id,
|
||||||
|
principalTable: "profile",
|
||||||
|
principalColumn: "profile_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_trait_profile_id",
|
||||||
|
table: "trait",
|
||||||
|
column: "profile_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "trait");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -852,6 +852,31 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("server_unban", (string)null);
|
b.ToTable("server_unban", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("trait_id");
|
||||||
|
|
||||||
|
b.Property<int>("ProfileId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("profile_id");
|
||||||
|
|
||||||
|
b.Property<string>("TraitName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasColumnName("trait_name");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_trait");
|
||||||
|
|
||||||
|
b.HasIndex("ProfileId")
|
||||||
|
.HasDatabaseName("IX_trait_profile_id");
|
||||||
|
|
||||||
|
b.ToTable("trait", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1133,6 +1158,18 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Ban");
|
b.Navigation("Ban");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.Trait", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||||
|
.WithMany("Traits")
|
||||||
|
.HasForeignKey("ProfileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_trait_profile_profile_id");
|
||||||
|
|
||||||
|
b.Navigation("Profile");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlayerRound", b =>
|
modelBuilder.Entity("PlayerRound", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.Player", null)
|
b.HasOne("Content.Server.Database.Player", null)
|
||||||
@@ -1197,6 +1234,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Antags");
|
b.Navigation("Antags");
|
||||||
|
|
||||||
b.Navigation("Jobs");
|
b.Navigation("Jobs");
|
||||||
|
|
||||||
|
b.Navigation("Traits");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
modelBuilder.Entity("Content.Server.Database.Round", b =>
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ namespace Content.Server.Database
|
|||||||
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName})
|
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName})
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<Trait>()
|
||||||
|
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
modelBuilder.Entity<Job>()
|
modelBuilder.Entity<Job>()
|
||||||
.HasIndex(j => j.ProfileId);
|
.HasIndex(j => j.ProfileId);
|
||||||
|
|
||||||
@@ -218,6 +222,7 @@ namespace Content.Server.Database
|
|||||||
public string Backpack { get; set; } = null!;
|
public string Backpack { get; set; } = null!;
|
||||||
public List<Job> Jobs { get; } = new();
|
public List<Job> Jobs { get; } = new();
|
||||||
public List<Antag> Antags { get; } = new();
|
public List<Antag> Antags { get; } = new();
|
||||||
|
public List<Trait> Traits { get; } = new();
|
||||||
|
|
||||||
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
|
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
|
||||||
|
|
||||||
@@ -253,6 +258,15 @@ namespace Content.Server.Database
|
|||||||
public string AntagName { get; set; } = null!;
|
public string AntagName { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Trait
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public Profile Profile { get; set; } = null!;
|
||||||
|
public int ProfileId { get; set; }
|
||||||
|
|
||||||
|
public string TraitName { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
public enum DbPreferenceUnavailableMode
|
public enum DbPreferenceUnavailableMode
|
||||||
{
|
{
|
||||||
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
|
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace Content.Server.Database
|
|||||||
.Preference
|
.Preference
|
||||||
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
|
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
|
||||||
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
|
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
|
||||||
|
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
|
||||||
.AsSingleQuery()
|
.AsSingleQuery()
|
||||||
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
|
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
|
||||||
|
|
||||||
@@ -156,6 +157,7 @@ namespace Content.Server.Database
|
|||||||
{
|
{
|
||||||
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
|
||||||
var antags = profile.Antags.Select(a => a.AntagName);
|
var antags = profile.Antags.Select(a => a.AntagName);
|
||||||
|
var traits = profile.Traits.Select(t => t.TraitName);
|
||||||
|
|
||||||
var sex = Sex.Male;
|
var sex = Sex.Male;
|
||||||
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
|
||||||
@@ -211,7 +213,8 @@ namespace Content.Server.Database
|
|||||||
backpack,
|
backpack,
|
||||||
jobs,
|
jobs,
|
||||||
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
|
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
|
||||||
antags.ToList()
|
antags.ToList(),
|
||||||
|
traits.ToList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +257,10 @@ namespace Content.Server.Database
|
|||||||
humanoid.AntagPreferences
|
humanoid.AntagPreferences
|
||||||
.Select(a => new Antag {AntagName = a})
|
.Select(a => new Antag {AntagName = a})
|
||||||
);
|
);
|
||||||
|
entity.Traits.AddRange(
|
||||||
|
humanoid.TraitPreferences
|
||||||
|
.Select(t => new Trait {TraitName = t})
|
||||||
|
);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,9 +160,6 @@ namespace Content.Server.Nutrition.Components
|
|||||||
{
|
{
|
||||||
_currentHunger -= frametime * ActualDecayRate;
|
_currentHunger -= frametime * ActualDecayRate;
|
||||||
UpdateCurrentThreshold();
|
UpdateCurrentThreshold();
|
||||||
|
|
||||||
if (_currentHungerThreshold != HungerThreshold.Dead)
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCurrentThreshold()
|
private void UpdateCurrentThreshold()
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
using Content.Server.Nutrition.Components;
|
using Content.Server.Nutrition.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Content.Shared.MobState.Components;
|
|
||||||
using Content.Shared.Movement.Components;
|
using Content.Shared.Movement.Components;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Server.Administration.Logs;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
|
|
||||||
namespace Content.Server.Nutrition.EntitySystems
|
namespace Content.Server.Nutrition.EntitySystems
|
||||||
@@ -43,6 +39,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||||||
|
|
||||||
private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args)
|
private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||||
{
|
{
|
||||||
|
// TODO: This should really be taken care of somewhere else
|
||||||
if (_jetpack.IsUserFlying(component.Owner))
|
if (_jetpack.IsUserFlying(component.Owner))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
40
Content.Server/Traits/TraitSystem.cs
Normal file
40
Content.Server/Traits/TraitSystem.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Shared.Traits;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.Manager;
|
||||||
|
|
||||||
|
namespace Content.Server.Traits;
|
||||||
|
|
||||||
|
public sealed class TraitSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly ISerializationManager _serializationManager = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the player is spawned in, add all trait components selected during character creation
|
||||||
|
private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
|
||||||
|
{
|
||||||
|
foreach (var traitId in args.Profile.TraitPreferences)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex<TraitPrototype>(traitId, out var traitPrototype))
|
||||||
|
{
|
||||||
|
Logger.Warning($"No trait found with ID {traitId}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all components required by the prototype
|
||||||
|
foreach (var entry in traitPrototype.Components.Values)
|
||||||
|
{
|
||||||
|
var comp = (Component) _serializationManager.Copy(entry.Component);
|
||||||
|
comp.Owner = args.Mob;
|
||||||
|
EntityManager.AddComponent(args.Mob, comp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using Content.Shared.GameTicking;
|
|||||||
using Content.Shared.Random.Helpers;
|
using Content.Shared.Random.Helpers;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Species;
|
using Content.Shared.Species;
|
||||||
|
using Content.Shared.Traits;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -28,6 +29,7 @@ namespace Content.Shared.Preferences
|
|||||||
|
|
||||||
private readonly Dictionary<string, JobPriority> _jobPriorities;
|
private readonly Dictionary<string, JobPriority> _jobPriorities;
|
||||||
private readonly List<string> _antagPreferences;
|
private readonly List<string> _antagPreferences;
|
||||||
|
private readonly List<string> _traitPreferences;
|
||||||
|
|
||||||
private HumanoidCharacterProfile(
|
private HumanoidCharacterProfile(
|
||||||
string name,
|
string name,
|
||||||
@@ -41,7 +43,8 @@ namespace Content.Shared.Preferences
|
|||||||
BackpackPreference backpack,
|
BackpackPreference backpack,
|
||||||
Dictionary<string, JobPriority> jobPriorities,
|
Dictionary<string, JobPriority> jobPriorities,
|
||||||
PreferenceUnavailableMode preferenceUnavailable,
|
PreferenceUnavailableMode preferenceUnavailable,
|
||||||
List<string> antagPreferences)
|
List<string> antagPreferences,
|
||||||
|
List<string> traitPreferences)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
FlavorText = flavortext;
|
FlavorText = flavortext;
|
||||||
@@ -55,21 +58,23 @@ namespace Content.Shared.Preferences
|
|||||||
_jobPriorities = jobPriorities;
|
_jobPriorities = jobPriorities;
|
||||||
PreferenceUnavailable = preferenceUnavailable;
|
PreferenceUnavailable = preferenceUnavailable;
|
||||||
_antagPreferences = antagPreferences;
|
_antagPreferences = antagPreferences;
|
||||||
|
_traitPreferences = traitPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
|
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
|
||||||
private HumanoidCharacterProfile(
|
private HumanoidCharacterProfile(
|
||||||
HumanoidCharacterProfile other,
|
HumanoidCharacterProfile other,
|
||||||
Dictionary<string, JobPriority> jobPriorities,
|
Dictionary<string, JobPriority> jobPriorities,
|
||||||
List<string> antagPreferences)
|
List<string> antagPreferences,
|
||||||
|
List<string> traitPreferences)
|
||||||
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack,
|
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack,
|
||||||
jobPriorities, other.PreferenceUnavailable, antagPreferences)
|
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Copy constructor</summary>
|
/// <summary>Copy constructor</summary>
|
||||||
private HumanoidCharacterProfile(HumanoidCharacterProfile other)
|
private HumanoidCharacterProfile(HumanoidCharacterProfile other)
|
||||||
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences))
|
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +90,10 @@ namespace Content.Shared.Preferences
|
|||||||
BackpackPreference backpack,
|
BackpackPreference backpack,
|
||||||
IReadOnlyDictionary<string, JobPriority> jobPriorities,
|
IReadOnlyDictionary<string, JobPriority> jobPriorities,
|
||||||
PreferenceUnavailableMode preferenceUnavailable,
|
PreferenceUnavailableMode preferenceUnavailable,
|
||||||
IReadOnlyList<string> antagPreferences)
|
IReadOnlyList<string> antagPreferences,
|
||||||
|
IReadOnlyList<string> traitPreferences)
|
||||||
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, new Dictionary<string, JobPriority>(jobPriorities),
|
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, new Dictionary<string, JobPriority>(jobPriorities),
|
||||||
preferenceUnavailable, new List<string>(antagPreferences))
|
preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +114,7 @@ namespace Content.Shared.Preferences
|
|||||||
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
|
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
|
||||||
},
|
},
|
||||||
PreferenceUnavailableMode.SpawnAsOverflow,
|
PreferenceUnavailableMode.SpawnAsOverflow,
|
||||||
|
new List<string>(),
|
||||||
new List<string>());
|
new List<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,8 +134,8 @@ namespace Content.Shared.Preferences
|
|||||||
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack,
|
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack,
|
||||||
new Dictionary<string, JobPriority>
|
new Dictionary<string, JobPriority>
|
||||||
{
|
{
|
||||||
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
|
{SharedGameTicker.FallbackOverflowJob, JobPriority.High},
|
||||||
}, PreferenceUnavailableMode.StayInLobby, new List<string>());
|
}, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
@@ -143,6 +150,7 @@ namespace Content.Shared.Preferences
|
|||||||
public BackpackPreference Backpack { get; private set; }
|
public BackpackPreference Backpack { get; private set; }
|
||||||
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
|
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
|
||||||
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
|
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
|
||||||
|
public IReadOnlyList<string> TraitPreferences => _traitPreferences;
|
||||||
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
|
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithName(string name)
|
public HumanoidCharacterProfile WithName(string name)
|
||||||
@@ -191,7 +199,7 @@ namespace Content.Shared.Preferences
|
|||||||
}
|
}
|
||||||
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
|
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
|
||||||
{
|
{
|
||||||
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences);
|
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
|
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
|
||||||
@@ -205,7 +213,7 @@ namespace Content.Shared.Preferences
|
|||||||
{
|
{
|
||||||
dictionary[jobId] = priority;
|
dictionary[jobId] = priority;
|
||||||
}
|
}
|
||||||
return new(this, dictionary, _antagPreferences);
|
return new(this, dictionary, _antagPreferences, _traitPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
|
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
|
||||||
@@ -215,7 +223,7 @@ namespace Content.Shared.Preferences
|
|||||||
|
|
||||||
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
|
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
|
||||||
{
|
{
|
||||||
return new(this, _jobPriorities, new List<string>(antagPreferences));
|
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
|
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
|
||||||
@@ -235,7 +243,29 @@ namespace Content.Shared.Preferences
|
|||||||
list.Remove(antagId);
|
list.Remove(antagId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new(this, _jobPriorities, list);
|
return new(this, _jobPriorities, list, _traitPreferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
|
||||||
|
{
|
||||||
|
var list = new List<string>(_traitPreferences);
|
||||||
|
|
||||||
|
// TODO: Maybe just refactor this to HashSet? Same with _antagPreferences
|
||||||
|
if(pref)
|
||||||
|
{
|
||||||
|
if(!list.Contains(traitId))
|
||||||
|
{
|
||||||
|
list.Add(traitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(list.Contains(traitId))
|
||||||
|
{
|
||||||
|
list.Remove(traitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new(this, _jobPriorities, _antagPreferences, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Summary =>
|
public string Summary =>
|
||||||
@@ -258,6 +288,7 @@ namespace Content.Shared.Preferences
|
|||||||
if (Backpack != other.Backpack) return false;
|
if (Backpack != other.Backpack) return false;
|
||||||
if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false;
|
if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false;
|
||||||
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
|
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
|
||||||
|
if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false;
|
||||||
return Appearance.MemberwiseEquals(other.Appearance);
|
return Appearance.MemberwiseEquals(other.Appearance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +388,10 @@ namespace Content.Shared.Preferences
|
|||||||
.Where(prototypeManager.HasIndex<AntagPrototype>)
|
.Where(prototypeManager.HasIndex<AntagPrototype>)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var traits = TraitPreferences
|
||||||
|
.Where(prototypeManager.HasIndex<TraitPrototype>)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
FlavorText = flavortext;
|
FlavorText = flavortext;
|
||||||
Age = age;
|
Age = age;
|
||||||
@@ -377,6 +412,9 @@ namespace Content.Shared.Preferences
|
|||||||
|
|
||||||
_antagPreferences.Clear();
|
_antagPreferences.Clear();
|
||||||
_antagPreferences.AddRange(antags);
|
_antagPreferences.AddRange(antags);
|
||||||
|
|
||||||
|
_traitPreferences.Clear();
|
||||||
|
_traitPreferences.AddRange(traits);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
@@ -399,7 +437,8 @@ namespace Content.Shared.Preferences
|
|||||||
),
|
),
|
||||||
PreferenceUnavailable,
|
PreferenceUnavailable,
|
||||||
_jobPriorities,
|
_jobPriorities,
|
||||||
_antagPreferences
|
_antagPreferences,
|
||||||
|
_traitPreferences
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
Content.Shared/Traits/TraitPrototype.cs
Normal file
28
Content.Shared/Traits/TraitPrototype.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Robust.Shared.Prototypes.EntityPrototype; // don't worry about it
|
||||||
|
|
||||||
|
namespace Content.Shared.Traits
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a trait.
|
||||||
|
/// </summary>
|
||||||
|
[Prototype("trait")]
|
||||||
|
public sealed class TraitPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
[IdDataField]
|
||||||
|
public string ID { get; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this trait.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("name")]
|
||||||
|
public string Name { get; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The components that get added to the player, when they pick this trait.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("components")]
|
||||||
|
public ComponentRegistry Components { get; } = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,7 @@ namespace Content.Tests.Server.Preferences
|
|||||||
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
|
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
|
||||||
},
|
},
|
||||||
PreferenceUnavailableMode.StayInLobby,
|
PreferenceUnavailableMode.StayInLobby,
|
||||||
|
new List<string> (),
|
||||||
new List<string>()
|
new List<string>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ humanoid-profile-editor-preference-duffelbag = Duffelbag
|
|||||||
humanoid-profile-editor-jobs-amount-in-department-tooltip = Jobs in the {$departmentName} department
|
humanoid-profile-editor-jobs-amount-in-department-tooltip = Jobs in the {$departmentName} department
|
||||||
humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
|
humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
|
||||||
humanoid-profile-editor-antags-tab = Antags
|
humanoid-profile-editor-antags-tab = Antags
|
||||||
|
humanoid-profile-editor-traits-tab = Traits
|
||||||
humanoid-profile-editor-job-priority-high-button = High
|
humanoid-profile-editor-job-priority-high-button = High
|
||||||
humanoid-profile-editor-job-priority-medium-button = Medium
|
humanoid-profile-editor-job-priority-medium-button = Medium
|
||||||
humanoid-profile-editor-job-priority-low-button = Low
|
humanoid-profile-editor-job-priority-low-button = Low
|
||||||
|
|||||||
Reference in New Issue
Block a user