From c019d428a750dbacbbdd37c630a814479f4e9247 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 6 Jul 2020 16:24:29 -0500 Subject: [PATCH] Antag preferences and antag prototype (#1264) Co-authored-by: Pieter-Jan Briers --- .../UserInterface/HumanoidProfileEditor.cs | 97 ++++++++- .../20200706172726_Antags.Designer.cs | 185 ++++++++++++++++++ .../Postgres/20200706172726_Antags.cs | 43 ++++ ...stgresPreferencesDbContextModelSnapshot.cs | 31 +++ .../Sqlite/20200706172741_Antags.Designer.cs | 178 +++++++++++++++++ .../Sqlite/20200706172741_Antags.cs | 42 ++++ ...SqlitePreferencesDbContextModelSnapshot.cs | 30 +++ Content.Server.Database/Model.cs | 14 ++ Content.Server.Database/PrefsDb.cs | 5 +- Content.Server/GameTicking/GamePreset.cs | 2 + .../GamePresets/PresetSuspicion.cs | 55 +++++- .../GameTicking/GameRules/RuleSuspicion.cs | 4 +- Content.Server/GameTicking/GameTicker.cs | 22 ++- Content.Server/Mobs/Role.cs | 2 +- Content.Server/Mobs/Roles/Job.cs | 2 +- .../Mobs/Roles/SuspicionInnocentRole.cs | 16 +- .../Mobs/Roles/SuspicionTraitorRole.cs | 18 +- Content.Server/PDA/PDAUplinkManager.cs | 2 +- .../Preferences/PreferencesDatabase.cs | 8 +- .../Preferences/HumanoidCharacterProfile.cs | 64 ++++-- Content.Shared/Roles/AntagPrototype.cs | 48 +++++ .../{Jobs => Roles}/JobPrototype.cs | 0 .../{Jobs => Roles}/StartingGearPrototype.cs | 0 .../Preferences/PreferencesDatabaseTests.cs | 3 +- .../Antags/Suspicion/SuspicionInnocent.yml | 6 + .../Antags/Suspicion/SuspicionTraitor.yml | 6 + .../Jobs/Cargo/CargoTechnician.yml | 0 .../{ => Roles}/Jobs/Civilian/Assistant.yml | 0 .../{ => Roles}/Jobs/Civilian/Bartender.yml | 0 .../{ => Roles}/Jobs/Civilian/Chef.yml | 0 .../{ => Roles}/Jobs/Civilian/Clown.yml | 0 .../{ => Roles}/Jobs/Civilian/Janitor.yml | 0 .../{ => Roles}/Jobs/Command/captain.yml | 0 .../Jobs/Command/head_of_personnel.yml | 0 .../Jobs/Engineering/chief_engineer.yml | 0 .../Jobs/Engineering/station_engineer.yml | 0 .../Jobs/Medical/chief_medical_officer.yml | 0 .../Jobs/Medical/medical_doctor.yml | 0 .../Jobs/Science/research_director.yml | 0 .../{ => Roles}/Jobs/Science/scientist.yml | 0 .../Jobs/Security/head_of_security.yml | 0 .../Jobs/Security/security_officer.yml | 0 42 files changed, 833 insertions(+), 50 deletions(-) create mode 100644 Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs create mode 100644 Content.Shared/Roles/AntagPrototype.cs rename Content.Shared/{Jobs => Roles}/JobPrototype.cs (100%) rename Content.Shared/{Jobs => Roles}/StartingGearPrototype.cs (100%) create mode 100644 Resources/Prototypes/Roles/Antags/Suspicion/SuspicionInnocent.yml create mode 100644 Resources/Prototypes/Roles/Antags/Suspicion/SuspicionTraitor.yml rename Resources/Prototypes/{ => Roles}/Jobs/Cargo/CargoTechnician.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Civilian/Assistant.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Civilian/Bartender.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Civilian/Chef.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Civilian/Clown.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Civilian/Janitor.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Command/captain.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Command/head_of_personnel.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Engineering/chief_engineer.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Engineering/station_engineer.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Medical/chief_medical_officer.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Medical/medical_doctor.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Science/research_director.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Science/scientist.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Security/head_of_security.yml (100%) rename Resources/Prototypes/{ => Roles}/Jobs/Security/security_officer.yml (100%) diff --git a/Content.Client/UserInterface/HumanoidProfileEditor.cs b/Content.Client/UserInterface/HumanoidProfileEditor.cs index 83e0492885..7a6f5f33d4 100644 --- a/Content.Client/UserInterface/HumanoidProfileEditor.cs +++ b/Content.Client/UserInterface/HumanoidProfileEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Content.Client.GameObjects.Components; @@ -6,6 +6,7 @@ using Content.Client.Interfaces; using Content.Client.Utility; using Content.Shared; using Content.Shared.Jobs; +using Content.Shared.Antags; using Content.Shared.Preferences; using Robust.Client.Graphics.Drawing; using Robust.Client.UserInterface; @@ -42,6 +43,7 @@ namespace Content.Client.UserInterface private readonly FacialHairStylePicker _facialHairPicker; private readonly List _jobPriorities; private readonly OptionButton _preferenceUnavailableButton; + private readonly List _antagPreferences; private bool _isDirty; public int CharacterSlot; @@ -320,6 +322,52 @@ namespace Content.Client.UserInterface #endregion + #region Antags + + { + var antagList = new VBoxContainer(); + + var antagVBox = new VBoxContainer + { + Children = + { + new ScrollContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + Children = + { + antagList + } + } + } + }; + + tabContainer.AddChild(antagVBox); + + tabContainer.SetTabTitle(2, Loc.GetString("Antags")); + + _antagPreferences = new List(); + + foreach (var antag in prototypeManager.EnumeratePrototypes().OrderBy(a => a.Name)) + { + if(!antag.SetPreference) + { + continue; + } + var selector = new AntagPreferenceSelector(antag); + antagList.AddChild(selector); + _antagPreferences.Add(selector); + + selector.PreferenceChanged += preference => + { + Profile = Profile.WithAntagPreference(antag.ID, preference); + IsDirty = true; + }; + } + } + + #endregion + var rightColumn = new VBoxContainer(); middleContainer.AddChild(rightColumn); @@ -466,6 +514,7 @@ namespace Content.Client.UserInterface UpdateHairPickers(); UpdateSaveButton(); UpdateJobPriorities(); + UpdateAntagPreferences(); _preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable); } @@ -533,5 +582,51 @@ namespace Content.Client.UserInterface }); } } + + private void UpdateAntagPreferences() + { + foreach (var preferenceSelector in _antagPreferences) + { + var antagId = preferenceSelector.Antag.ID; + + var preference = Profile.AntagPreferences.Contains(antagId); + + preferenceSelector.Preference = preference; + } + } + + private class AntagPreferenceSelector : Control + { + public AntagPrototype Antag { get; } + private readonly CheckBox _checkBox; + + public bool Preference + { + get => _checkBox.Pressed; + set => _checkBox.Pressed = value; + } + + public event Action PreferenceChanged; + + public AntagPreferenceSelector(AntagPrototype antag) + { + Antag = antag; + + _checkBox = new CheckBox {Text = $"{antag.Name}"}; + _checkBox.OnToggled += OnCheckBoxToggled; + + AddChild(new HBoxContainer + { + Children = + { + _checkBox + } + }); + } + private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args) + { + PreferenceChanged?.Invoke(Preference); + } + } } } diff --git a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs new file mode 100644 index 0000000000..266aa377e5 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.Designer.cs @@ -0,0 +1,185 @@ +// +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresPreferencesDbContext))] + [Migration("20200706172726_Antags")] + partial class Antags + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("AntagId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text"); + + b.Property("HumanoidProfileId") + .HasColumnType("integer"); + + b.HasKey("AntagId"); + + b.HasIndex("HumanoidProfileId", "AntagName") + .IsUnique(); + + b.ToTable("Antag"); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.Property("HumanoidProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Age") + .HasColumnType("integer"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer"); + + b.Property("PrefsId") + .HasColumnType("integer"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text"); + + b.Property("Slot") + .HasColumnType("integer"); + + b.Property("SlotName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("HumanoidProfileId"); + + b.HasIndex("PrefsId"); + + b.HasIndex("Slot", "PrefsId") + .IsUnique(); + + b.ToTable("HumanoidProfile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("JobId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Priority") + .HasColumnType("integer"); + + b.Property("ProfileHumanoidProfileId") + .HasColumnType("integer"); + + b.HasKey("JobId"); + + b.HasIndex("ProfileHumanoidProfileId"); + + b.ToTable("Job"); + }); + + modelBuilder.Entity("Content.Server.Database.Prefs", b => + { + b.Property("PrefsId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("PrefsId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Antags") + .HasForeignKey("HumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.HasOne("Content.Server.Database.Prefs", "Prefs") + .WithMany("HumanoidProfiles") + .HasForeignKey("PrefsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileHumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs new file mode 100644 index 0000000000..23d595a79b --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20200706172726_Antags.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Content.Server.Database.Migrations.Postgres +{ + public partial class Antags : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Antag", + columns: table => new + { + AntagId = table.Column(nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + HumanoidProfileId = table.Column(nullable: false), + AntagName = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Antag", x => x.AntagId); + table.ForeignKey( + name: "FK_Antag_HumanoidProfile_HumanoidProfileId", + column: x => x.HumanoidProfileId, + principalTable: "HumanoidProfile", + principalColumn: "HumanoidProfileId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Antag_HumanoidProfileId_AntagName", + table: "Antag", + columns: new[] { "HumanoidProfileId", "AntagName" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Antag"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs index bfd4851099..0343886635 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresPreferencesDbContextModelSnapshot.cs @@ -18,6 +18,28 @@ namespace Content.Server.Database.Migrations.Postgres .HasAnnotation("ProductVersion", "3.1.4") .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("AntagId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text"); + + b.Property("HumanoidProfileId") + .HasColumnType("integer"); + + b.HasKey("AntagId"); + + b.HasIndex("HumanoidProfileId", "AntagName") + .IsUnique(); + + b.ToTable("Antag"); + }); + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => { b.Property("HumanoidProfileId") @@ -129,6 +151,15 @@ namespace Content.Server.Database.Migrations.Postgres b.ToTable("Preferences"); }); + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Antags") + .HasForeignKey("HumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => { b.HasOne("Content.Server.Database.Prefs", "Prefs") diff --git a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs new file mode 100644 index 0000000000..00fe13a785 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.Designer.cs @@ -0,0 +1,178 @@ +// +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqlitePreferencesDbContext))] + [Migration("20200706172741_Antags")] + partial class Antags + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("AntagId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HumanoidProfileId") + .HasColumnType("INTEGER"); + + b.HasKey("AntagId"); + + b.HasIndex("HumanoidProfileId", "AntagName") + .IsUnique(); + + b.ToTable("Antag"); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.Property("HumanoidProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Age") + .HasColumnType("INTEGER"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER"); + + b.Property("PrefsId") + .HasColumnType("INTEGER"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnType("INTEGER"); + + b.Property("SlotName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("HumanoidProfileId"); + + b.HasIndex("PrefsId"); + + b.HasIndex("Slot", "PrefsId") + .IsUnique(); + + b.ToTable("HumanoidProfile"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("JobId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("ProfileHumanoidProfileId") + .HasColumnType("INTEGER"); + + b.HasKey("JobId"); + + b.HasIndex("ProfileHumanoidProfileId"); + + b.ToTable("Job"); + }); + + modelBuilder.Entity("Content.Server.Database.Prefs", b => + { + b.Property("PrefsId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("PrefsId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Antags") + .HasForeignKey("HumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => + { + b.HasOne("Content.Server.Database.Prefs", "Prefs") + .WithMany("HumanoidProfiles") + .HasForeignKey("PrefsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileHumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs new file mode 100644 index 0000000000..f2c1185b00 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20200706172741_Antags.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + public partial class Antags : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Antag", + columns: table => new + { + AntagId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + HumanoidProfileId = table.Column(nullable: false), + AntagName = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Antag", x => x.AntagId); + table.ForeignKey( + name: "FK_Antag_HumanoidProfile_HumanoidProfileId", + column: x => x.HumanoidProfileId, + principalTable: "HumanoidProfile", + principalColumn: "HumanoidProfileId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Antag_HumanoidProfileId_AntagName", + table: "Antag", + columns: new[] { "HumanoidProfileId", "AntagName" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Antag"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs index bf6e21f02a..60b7c39f14 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqlitePreferencesDbContextModelSnapshot.cs @@ -15,6 +15,27 @@ namespace Content.Server.Database.Migrations modelBuilder .HasAnnotation("ProductVersion", "3.1.4"); + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("AntagId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("HumanoidProfileId") + .HasColumnType("INTEGER"); + + b.HasKey("AntagId"); + + b.HasIndex("HumanoidProfileId", "AntagName") + .IsUnique(); + + b.ToTable("Antag"); + }); + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => { b.Property("HumanoidProfileId") @@ -123,6 +144,15 @@ namespace Content.Server.Database.Migrations b.ToTable("Preferences"); }); + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.HumanoidProfile", "Profile") + .WithMany("Antags") + .HasForeignKey("HumanoidProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Content.Server.Database.HumanoidProfile", b => { b.HasOne("Content.Server.Database.Prefs", "Prefs") diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index e3c62ffd13..a28d1dcb8f 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -64,6 +64,10 @@ namespace Content.Server.Database modelBuilder.Entity() .HasIndex(p => new {p.Slot, p.PrefsId}) .IsUnique(); + + modelBuilder.Entity() + .HasIndex(p => new {p.HumanoidProfileId , p.AntagName}) + .IsUnique(); } } @@ -90,6 +94,7 @@ namespace Content.Server.Database public string EyeColor { get; set; } = null!; public string SkinColor { get; set; } = null!; public List Jobs { get; } = new List(); + public List Antags { get; } = new List(); public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; } public int PrefsId { get; set; } @@ -114,6 +119,15 @@ namespace Content.Server.Database High = 3 } + public class Antag + { + public int AntagId { get; set; } + public HumanoidProfile Profile { get; set; } = null!; + public int HumanoidProfileId { get; set; } + + public string AntagName { get; set; } = null!; + } + public enum DbPreferenceUnavailableMode { // These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared. diff --git a/Content.Server.Database/PrefsDb.cs b/Content.Server.Database/PrefsDb.cs index a1c3de32cd..73e98b0216 100644 --- a/Content.Server.Database/PrefsDb.cs +++ b/Content.Server.Database/PrefsDb.cs @@ -26,8 +26,8 @@ namespace Content.Server.Database { return await _prefsCtx .Preferences - .Include(p => p.HumanoidProfiles) - .ThenInclude(h => h.Jobs) + .Include(p => p.HumanoidProfiles).ThenInclude(h => h.Jobs) + .Include(p => p.HumanoidProfiles).ThenInclude(h => h.Antags) .SingleOrDefaultAsync(p => p.Username == username); } @@ -72,6 +72,7 @@ namespace Content.Server.Database { return await _prefsCtx.HumanoidProfile .Include(p => p.Jobs) + .Include(a => a.Antags) .Join(_prefsCtx.Preferences, profile => new {profile.Slot, profile.PrefsId}, prefs => new {Slot = prefs.SelectedCharacterSlot, prefs.PrefsId}, diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index 5b4fdadf2d..0f9642184a 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Robust.Server.Interfaces.Player; +using Content.Shared.Preferences; namespace Content.Server.GameTicking { @@ -11,5 +12,6 @@ namespace Content.Server.GameTicking public abstract bool Start(IReadOnlyList readyPlayers, bool force = false); public virtual string ModeTitle => "Sandbox"; public virtual string Description => "Secret!"; + public Dictionary readyProfiles; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs index b59e7d7ae5..17def7d31a 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -1,17 +1,23 @@ -using System; -using System.Collections.Generic; -using Content.Server.GameTicking.GameRules; +using Content.Server.GameTicking.GameRules; +using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs.Roles; using Content.Server.Players; -using Content.Server.Sandbox; -using NFluidsynth; +using Content.Shared.Antags; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; +using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Logger = Robust.Shared.Log.Logger; +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Log; +using System.Threading.Tasks; +using Content.Shared.Preferences; + + namespace Content.Server.GameTicking.GamePresets { @@ -21,11 +27,14 @@ namespace Content.Server.GameTicking.GamePresets [Dependency] private readonly IChatManager _chatManager; [Dependency] private readonly IGameTicker _gameTicker; [Dependency] private readonly IRobustRandom _random; + [Dependency] private IPrototypeManager _prototypeManager; #pragma warning restore 649 public int MinPlayers { get; set; } = 5; public int MinTraitors { get; set; } = 2; public int PlayersPerTraitor { get; set; } = 5; + private static string TraitorID = "SuspicionTraitor"; + private static string InnocentID = "SuspicionInnocent"; public override bool Start(IReadOnlyList readyPlayers, bool force = false) { @@ -36,20 +45,48 @@ namespace Content.Server.GameTicking.GamePresets } var list = new List(readyPlayers); + var prefList = new List(); + + foreach (var player in list) + { + if (!readyProfiles.ContainsKey(player.Name)) + { + continue; + } + var profile = readyProfiles[player.Name]; + if (profile.AntagPreferences.Contains(_prototypeManager.Index(TraitorID).Name)) + { + prefList.Add(player); + } + } + var numTraitors = Math.Clamp(readyPlayers.Count % PlayersPerTraitor, MinTraitors, readyPlayers.Count); for (var i = 0; i < numTraitors; i++) { - var traitor = _random.PickAndTake(list); + IPlayerSession traitor; + if(prefList.Count() == 0) + { + traitor = _random.PickAndTake(list); + Logger.InfoS("preset", "Insufficient preferred traitors, picking at random."); + } + else + { + traitor = _random.PickAndTake(prefList); + list.Remove(traitor); + Logger.InfoS("preset", "Selected a preferred traitor."); + } var mind = traitor.Data.ContentData().Mind; - mind.AddRole(new SuspicionTraitorRole(mind)); + var antagPrototype = _prototypeManager.Index(TraitorID); + mind.AddRole(new SuspicionTraitorRole(mind, antagPrototype)); } foreach (var player in list) { var mind = player.Data.ContentData().Mind; - mind.AddRole(new SuspicionInnocentRole(mind)); + var antagPrototype = _prototypeManager.Index(InnocentID); + mind.AddRole(new SuspicionInnocentRole(mind, antagPrototype)); } _gameTicker.AddGameRule(); diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 91b16f3aaf..50f236072d 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading; using Content.Server.GameObjects; using Content.Server.GameObjects.Components.Mobs; @@ -38,8 +39,6 @@ namespace Content.Server.GameTicking.GameRules public override void Added() { - _chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!"); - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _onMobDamageStateChanged); Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); @@ -86,7 +85,6 @@ namespace Content.Server.GameTicking.GameRules { continue; } - if (playerSession.ContentData().Mind.HasRole()) traitorsAlive++; else diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 4d89eb2351..759914db40 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -267,12 +267,12 @@ namespace Content.Server.GameTicking } // Time to start the preset. - var preset = MakeGamePreset(); + var preset = MakeGamePreset(profiles); if (!preset.Start(assignedJobs.Keys.ToList(), force)) { SetStartPreset(_configurationManager.GetCVar("game.fallbackpreset")); - var newPreset = MakeGamePreset(); + var newPreset = MakeGamePreset(profiles); _chatManager.DispatchServerAnnouncement( $"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}..."); if (!newPreset.Start(readyPlayers, force)) @@ -306,7 +306,7 @@ namespace Content.Server.GameTicking //Tell every client the round has ended. var roundEndMessage = _netManager.CreateNetMessage(); - roundEndMessage.GamemodeTitle = MakeGamePreset().ModeTitle; + roundEndMessage.GamemodeTitle = MakeGamePreset(null).ModeTitle; //Get the timespan of the round. roundEndMessage.RoundDuration = IoCManager.Resolve().RealTime.Subtract(_roundStartTimeSpan); @@ -318,13 +318,13 @@ namespace Content.Server.GameTicking var mind = ply.ContentData().Mind; if (mind != null) { - var antag = mind.AllRoles.Any(role => role.Antag); + var antag = mind.AllRoles.Any(role => role.Antagonist); var playerEndRoundInfo = new RoundEndPlayerInfo() { PlayerOOCName = ply.Name, PlayerICName = mind.CurrentEntity.Name, Role = antag - ? mind.AllRoles.First(role => role.Antag).Name + ? mind.AllRoles.First(role => role.Antagonist).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"), Antag = antag }; @@ -805,7 +805,7 @@ namespace Content.Server.GameTicking var mindComponent = mob.GetComponent(); if (mindComponent.HasMind) //Redundancy checks. { - if (mindComponent.Mind.AllRoles.Any(role => role.Antag)) //Give antags a new uplinkaccount. + if (mindComponent.Mind.AllRoles.Any(role => role.Antagonist)) //Give antags a new uplinkaccount. { var uplinkAccount = new UplinkAccount(mob.Uid, @@ -883,8 +883,8 @@ namespace Content.Server.GameTicking private string GetInfoText() { - var gmTitle = MakeGamePreset().ModeTitle; - var desc = MakeGamePreset().Description; + var gmTitle = MakeGamePreset(null).ModeTitle; + var desc = MakeGamePreset(null).Description; return _localization.GetString(@"Hi and welcome to [color=white]Space Station 14![/color] The current game mode is: [color=white]{0}[/color]. @@ -898,9 +898,11 @@ The current game mode is: [color=white]{0}[/color]. _netManager.ServerSendToMany(infoMsg, _playersInLobby.Keys.Select(p => p.ConnectedClient).ToList()); } - private GamePreset MakeGamePreset() + private GamePreset MakeGamePreset(Dictionary readyProfiles) { - return _dynamicTypeFactory.CreateInstance(_presetType ?? typeof(PresetSandbox)); + var preset = _dynamicTypeFactory.CreateInstance(_presetType ?? typeof(PresetSandbox)); + preset.readyProfiles = readyProfiles; + return preset; } #pragma warning disable 649 diff --git a/Content.Server/Mobs/Role.cs b/Content.Server/Mobs/Role.cs index f78b579f11..d1980956a5 100644 --- a/Content.Server/Mobs/Role.cs +++ b/Content.Server/Mobs/Role.cs @@ -26,7 +26,7 @@ namespace Content.Server.Mobs /// /// Whether this role should be considered antagonistic or not. /// - public abstract bool Antag { get; } + public abstract bool Antagonist { get; } protected Role(Mind mind) { diff --git a/Content.Server/Mobs/Roles/Job.cs b/Content.Server/Mobs/Roles/Job.cs index 680a91ff20..33db5a65dc 100644 --- a/Content.Server/Mobs/Roles/Job.cs +++ b/Content.Server/Mobs/Roles/Job.cs @@ -11,7 +11,7 @@ namespace Content.Server.Mobs.Roles public JobPrototype Prototype { get; } public override string Name { get; } - public override bool Antag => false; + public override bool Antagonist => false; public string StartingGear => Prototype.StartingGear; diff --git a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs index 177a8d3bbd..4c2f16f6a9 100644 --- a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs +++ b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs @@ -2,24 +2,32 @@ using Content.Server.GameObjects; using Content.Server.Interfaces.Chat; using Robust.Shared.IoC; using Robust.Shared.Utility; +using Content.Shared.Antags; namespace Content.Server.Mobs.Roles { public class SuspicionInnocentRole : Role { - public SuspicionInnocentRole(Mind mind) : base(mind) + public AntagPrototype Prototype { get; } + + public SuspicionInnocentRole(Mind mind, AntagPrototype antagPrototype) : base(mind) { + Prototype = antagPrototype; + Name = antagPrototype.Name; + Antagonist = antagPrototype.Antagonist; } - public override string Name => "Innocent"; - public override bool Antag => false; + public override string Name { get; } + public string Objective => Prototype.Objective; + public override bool Antagonist { get; } public override void Greet() { base.Greet(); var chat = IoCManager.Resolve(); - chat.DispatchServerMessage(Mind.Session, "You're an innocent!"); + chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!"); + chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}"); } } } diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs index 65c1e10306..5d19854454 100644 --- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs @@ -1,25 +1,33 @@ -using Content.Server.GameObjects; +using Content.Server.GameObjects; using Content.Server.Interfaces.Chat; using Robust.Shared.IoC; using Robust.Shared.Utility; +using Content.Shared.Antags; namespace Content.Server.Mobs.Roles { public sealed class SuspicionTraitorRole : Role { - public SuspicionTraitorRole(Mind mind) : base(mind) + public AntagPrototype Prototype { get; } + + public SuspicionTraitorRole(Mind mind, AntagPrototype antagPrototype) : base(mind) { + Prototype = antagPrototype; + Name = antagPrototype.Name; + Antagonist = antagPrototype.Antagonist; } - public override string Name => "Traitor"; - public override bool Antag => true; + public override string Name { get; } + public string Objective => Prototype.Objective; + public override bool Antagonist { get; } public override void Greet() { base.Greet(); var chat = IoCManager.Resolve(); - chat.DispatchServerMessage(Mind.Session, "You're a traitor!"); + chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!"); + chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}"); } } } diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index 7e199ce153..56b673df12 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -56,7 +56,7 @@ namespace Content.Server.PDA var entity = _entityManager.GetEntity(acc.AccountHolder); if (entity.TryGetComponent(out MindComponent mindComponent)) { - if (mindComponent.Mind.AllRoles.Any(role => !role.Antag)) + if (mindComponent.Mind.AllRoles.Any(role => !role.Antagonist)) { return false; } diff --git a/Content.Server/Preferences/PreferencesDatabase.cs b/Content.Server/Preferences/PreferencesDatabase.cs index b29ff4a255..6c6c4719d0 100644 --- a/Content.Server/Preferences/PreferencesDatabase.cs +++ b/Content.Server/Preferences/PreferencesDatabase.cs @@ -107,6 +107,10 @@ namespace Content.Server.Preferences .Where(j => j.Value != JobPriority.Never) .Select(j => new Job {JobName = j.Key, Priority = (DbJobPriority) j.Value}) ); + entity.Antags.AddRange( + humanoid.AntagPreferences + .Select(a => new Antag {AntagName = a}) + ); await _prefsDb.SaveCharacterSlotAsync(username, entity); } finally @@ -140,6 +144,7 @@ namespace Content.Server.Preferences private static HumanoidCharacterProfile ConvertProfiles(HumanoidProfile profile) { var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority); + var antags = profile.Antags.Select(a => a.AntagName); return new HumanoidCharacterProfile( profile.CharacterName, profile.Age, @@ -154,7 +159,8 @@ namespace Content.Server.Preferences Color.FromHex(profile.SkinColor) ), jobs, - (PreferenceUnavailableMode) profile.PreferenceUnavailable + (PreferenceUnavailableMode) profile.PreferenceUnavailable, + antags.ToList() ); } } diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 5b061d5a3f..db068e358d 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Shared.Antags; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Preferences @@ -9,6 +12,7 @@ namespace Content.Shared.Preferences public class HumanoidCharacterProfile : ICharacterProfile { private readonly Dictionary _jobPriorities; + private readonly List _antagPreferences; public static int MinimumAge = 18; public static int MaximumAge = 90; @@ -18,7 +22,8 @@ namespace Content.Shared.Preferences Sex sex, HumanoidCharacterAppearance appearance, Dictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable) + PreferenceUnavailableMode preferenceUnavailable, + List antagPreferences) { Name = name; Age = age; @@ -26,6 +31,7 @@ namespace Content.Shared.Preferences Appearance = appearance; _jobPriorities = jobPriorities; PreferenceUnavailable = preferenceUnavailable; + _antagPreferences = antagPreferences; } public HumanoidCharacterProfile( @@ -34,9 +40,10 @@ namespace Content.Shared.Preferences Sex sex, HumanoidCharacterAppearance appearance, IReadOnlyDictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable) + PreferenceUnavailableMode preferenceUnavailable, + IReadOnlyList antagPreferences) : this(name, age, sex, appearance, new Dictionary(jobPriorities), - preferenceUnavailable) + preferenceUnavailable, new List(antagPreferences)) { } @@ -46,7 +53,7 @@ namespace Content.Shared.Preferences new Dictionary { {SharedGameTicker.OverflowJob, JobPriority.High} - }, PreferenceUnavailableMode.StayInLobby); + }, PreferenceUnavailableMode.StayInLobby, new List()); } public string Name { get; } @@ -55,26 +62,27 @@ namespace Content.Shared.Preferences public ICharacterAppearance CharacterAppearance => Appearance; public HumanoidCharacterAppearance Appearance { get; } public IReadOnlyDictionary JobPriorities => _jobPriorities; + public IReadOnlyList AntagPreferences => _antagPreferences; public PreferenceUnavailableMode PreferenceUnavailable { get; } public HumanoidCharacterProfile WithName(string name) { - return new HumanoidCharacterProfile(name, Age, Sex, Appearance, _jobPriorities, PreferenceUnavailable); + return new HumanoidCharacterProfile(name, Age, Sex, Appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithAge(int age) { - return new HumanoidCharacterProfile(Name, age, Sex, Appearance, _jobPriorities, PreferenceUnavailable); + return new HumanoidCharacterProfile(Name, age, Sex, Appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithSex(Sex sex) { - return new HumanoidCharacterProfile(Name, Age, sex, Appearance, _jobPriorities, PreferenceUnavailable); + return new HumanoidCharacterProfile(Name, Age, sex, Appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) { - return new HumanoidCharacterProfile(Name, Age, Sex, appearance, _jobPriorities, PreferenceUnavailable); + return new HumanoidCharacterProfile(Name, Age, Sex, appearance, _jobPriorities, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithJobPriorities(IReadOnlyDictionary jobPriorities) @@ -85,7 +93,8 @@ namespace Content.Shared.Preferences Sex, Appearance, new Dictionary(jobPriorities), - PreferenceUnavailable); + PreferenceUnavailable, + _antagPreferences); } public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) @@ -100,12 +109,44 @@ namespace Content.Shared.Preferences dictionary[jobId] = priority; } - return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, dictionary, PreferenceUnavailable); + return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, dictionary, PreferenceUnavailable, _antagPreferences); } public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode) { - return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, _jobPriorities, mode); + return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, _jobPriorities, mode, _antagPreferences); + } + + public HumanoidCharacterProfile WithAntagPreferences(IReadOnlyList antagPreferences) + { + return new HumanoidCharacterProfile( + Name, + Age, + Sex, + Appearance, + _jobPriorities, + PreferenceUnavailable, + new List(antagPreferences)); + } + + public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) + { + var list = new List(_antagPreferences); + if(pref) + { + if(!list.Contains(antagId)) + { + list.Add(antagId); + } + } + else + { + if(list.Contains(antagId)) + { + list.Remove(antagId); + } + } + return new HumanoidCharacterProfile(Name, Age, Sex, Appearance, _jobPriorities, PreferenceUnavailable, list); } public string Summary => @@ -119,6 +160,7 @@ namespace Content.Shared.Preferences if (Sex != other.Sex) return false; if (PreferenceUnavailable != other.PreferenceUnavailable) return false; if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false; + if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false; return Appearance.MemberwiseEquals(other.Appearance); } } diff --git a/Content.Shared/Roles/AntagPrototype.cs b/Content.Shared/Roles/AntagPrototype.cs new file mode 100644 index 0000000000..a253d13a1a --- /dev/null +++ b/Content.Shared/Roles/AntagPrototype.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Antags +{ + /// + /// Describes information for a single antag. + /// + [Prototype("antag")] + public class AntagPrototype : IPrototype, IIndexedPrototype + { + public string ID { get; private set; } + + /// + /// The name of this antag as displayed to players. + /// + public string Name { get; private set; } + + /// + /// The antag's objective, displayed at round-start to the player. + /// + public string Objective { get; private set; } + + /// + /// Whether or not the antag role is one of the bad guys. + /// + public bool Antagonist { get; private set; } + + /// + /// Whether or not the player can set the antag role in antag preferences. + /// + public bool SetPreference { get; private set; } + + public void LoadFrom(YamlMappingNode mapping) + { + ID = mapping.GetNode("id").AsString(); + Name = Loc.GetString(mapping.GetNode("name").ToString()); + Objective = mapping.GetNode("objective").ToString(); + Antagonist = mapping.GetNode("antagonist").AsBool(); + SetPreference = mapping.GetNode("setPreference").AsBool(); + } + } +} diff --git a/Content.Shared/Jobs/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs similarity index 100% rename from Content.Shared/Jobs/JobPrototype.cs rename to Content.Shared/Roles/JobPrototype.cs diff --git a/Content.Shared/Jobs/StartingGearPrototype.cs b/Content.Shared/Roles/StartingGearPrototype.cs similarity index 100% rename from Content.Shared/Jobs/StartingGearPrototype.cs rename to Content.Shared/Roles/StartingGearPrototype.cs diff --git a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs b/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs index b0a087a5db..1aa4353c10 100644 --- a/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs +++ b/Content.Tests/Server/Preferences/PreferencesDatabaseTests.cs @@ -35,7 +35,8 @@ namespace Content.Tests.Server.Preferences { {SharedGameTicker.OverflowJob, JobPriority.High} }, - PreferenceUnavailableMode.StayInLobby + PreferenceUnavailableMode.StayInLobby, + new List{} ); } diff --git a/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionInnocent.yml b/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionInnocent.yml new file mode 100644 index 0000000000..92c73b550f --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionInnocent.yml @@ -0,0 +1,6 @@ +- type: antag + id: SuspicionInnocent + name: "innocent" + antagonist: false + setPreference: false + objective: "Discover and eliminate all traitors." diff --git a/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionTraitor.yml b/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionTraitor.yml new file mode 100644 index 0000000000..1d2168232c --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/Suspicion/SuspicionTraitor.yml @@ -0,0 +1,6 @@ +- type: antag + id: SuspicionTraitor + name: "traitor" + antagonist: true + setPreference: true + objective: "Kill the innocents." diff --git a/Resources/Prototypes/Jobs/Cargo/CargoTechnician.yml b/Resources/Prototypes/Roles/Jobs/Cargo/CargoTechnician.yml similarity index 100% rename from Resources/Prototypes/Jobs/Cargo/CargoTechnician.yml rename to Resources/Prototypes/Roles/Jobs/Cargo/CargoTechnician.yml diff --git a/Resources/Prototypes/Jobs/Civilian/Assistant.yml b/Resources/Prototypes/Roles/Jobs/Civilian/Assistant.yml similarity index 100% rename from Resources/Prototypes/Jobs/Civilian/Assistant.yml rename to Resources/Prototypes/Roles/Jobs/Civilian/Assistant.yml diff --git a/Resources/Prototypes/Jobs/Civilian/Bartender.yml b/Resources/Prototypes/Roles/Jobs/Civilian/Bartender.yml similarity index 100% rename from Resources/Prototypes/Jobs/Civilian/Bartender.yml rename to Resources/Prototypes/Roles/Jobs/Civilian/Bartender.yml diff --git a/Resources/Prototypes/Jobs/Civilian/Chef.yml b/Resources/Prototypes/Roles/Jobs/Civilian/Chef.yml similarity index 100% rename from Resources/Prototypes/Jobs/Civilian/Chef.yml rename to Resources/Prototypes/Roles/Jobs/Civilian/Chef.yml diff --git a/Resources/Prototypes/Jobs/Civilian/Clown.yml b/Resources/Prototypes/Roles/Jobs/Civilian/Clown.yml similarity index 100% rename from Resources/Prototypes/Jobs/Civilian/Clown.yml rename to Resources/Prototypes/Roles/Jobs/Civilian/Clown.yml diff --git a/Resources/Prototypes/Jobs/Civilian/Janitor.yml b/Resources/Prototypes/Roles/Jobs/Civilian/Janitor.yml similarity index 100% rename from Resources/Prototypes/Jobs/Civilian/Janitor.yml rename to Resources/Prototypes/Roles/Jobs/Civilian/Janitor.yml diff --git a/Resources/Prototypes/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml similarity index 100% rename from Resources/Prototypes/Jobs/Command/captain.yml rename to Resources/Prototypes/Roles/Jobs/Command/captain.yml diff --git a/Resources/Prototypes/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml similarity index 100% rename from Resources/Prototypes/Jobs/Command/head_of_personnel.yml rename to Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml diff --git a/Resources/Prototypes/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml similarity index 100% rename from Resources/Prototypes/Jobs/Engineering/chief_engineer.yml rename to Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml diff --git a/Resources/Prototypes/Jobs/Engineering/station_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml similarity index 100% rename from Resources/Prototypes/Jobs/Engineering/station_engineer.yml rename to Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml diff --git a/Resources/Prototypes/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml similarity index 100% rename from Resources/Prototypes/Jobs/Medical/chief_medical_officer.yml rename to Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml diff --git a/Resources/Prototypes/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml similarity index 100% rename from Resources/Prototypes/Jobs/Medical/medical_doctor.yml rename to Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml diff --git a/Resources/Prototypes/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml similarity index 100% rename from Resources/Prototypes/Jobs/Science/research_director.yml rename to Resources/Prototypes/Roles/Jobs/Science/research_director.yml diff --git a/Resources/Prototypes/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml similarity index 100% rename from Resources/Prototypes/Jobs/Science/scientist.yml rename to Resources/Prototypes/Roles/Jobs/Science/scientist.yml diff --git a/Resources/Prototypes/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml similarity index 100% rename from Resources/Prototypes/Jobs/Security/head_of_security.yml rename to Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml diff --git a/Resources/Prototypes/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml similarity index 100% rename from Resources/Prototypes/Jobs/Security/security_officer.yml rename to Resources/Prototypes/Roles/Jobs/Security/security_officer.yml