This commit is contained in:
ShadowCommander
2022-02-21 14:11:39 -08:00
committed by GitHub
parent fd8d9d2953
commit 4825142210
25 changed files with 3429 additions and 130 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
using System;
using System.Net;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
public partial class RoleBans : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "server_role_unban",
columns: table => new
{
role_unban_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ban_id = table.Column<int>(type: "integer", nullable: false),
unbanning_admin = table.Column<Guid>(type: "uuid", nullable: true),
unban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id);
table.ForeignKey(
name: "FK_server_role_unban_server_ban_ban_id",
column: x => x.ban_id,
principalTable: "server_ban",
principalColumn: "server_ban_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "server_role_ban",
columns: table => new
{
server_role_ban_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
user_id = table.Column<Guid>(type: "uuid", nullable: true),
address = table.Column<ValueTuple<IPAddress, int>>(type: "inet", nullable: true),
hwid = table.Column<byte[]>(type: "bytea", nullable: true),
ban_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
expiration_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
reason = table.Column<string>(type: "text", nullable: false),
banning_admin = table.Column<Guid>(type: "uuid", nullable: true),
unban_id = table.Column<int>(type: "integer", nullable: true),
role_id = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id);
table.CheckConstraint("CK_server_role_ban_AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
table.ForeignKey(
name: "FK_server_role_ban_server_role_unban__unban_id",
column: x => x.unban_id,
principalTable: "server_role_unban",
principalColumn: "role_unban_id");
});
migrationBuilder.CreateIndex(
name: "IX_server_role_ban__unban_id",
table: "server_role_ban",
column: "unban_id");
migrationBuilder.CreateIndex(
name: "IX_server_role_unban_ban_id",
table: "server_role_unban",
column: "ban_id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "server_role_ban");
migrationBuilder.DropTable(
name: "server_role_unban");
}
}
}

View File

@@ -646,6 +646,94 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("server_ban_hit", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("server_role_ban_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<ValueTuple<IPAddress, int>?>("Address")
.HasColumnType("inet")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("uuid")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text")
.HasColumnName("reason");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text")
.HasColumnName("role_id");
b.Property<int?>("UnbanId")
.HasColumnType("integer")
.HasColumnName("unban_id");
b.Property<Guid?>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_server_role_ban");
b.HasIndex("UnbanId")
.HasDatabaseName("IX_server_role_ban__unban_id");
b.ToTable("server_role_ban", (string)null);
b.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("role_unban_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("BanId")
.HasColumnType("integer")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("uuid")
.HasColumnName("unbanning_admin");
b.HasKey("Id")
.HasName("PK_server_role_unban");
b.HasIndex("BanId")
.HasDatabaseName("IX_server_role_unban_ban_id");
b.ToTable("server_role_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.Property<int>("Id")
@@ -842,6 +930,28 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Connection");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban")
.WithMany()
.HasForeignKey("UnbanId")
.HasConstraintName("FK_server_role_ban_server_role_unban__unban_id");
b.Navigation("Unban");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithMany()
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_role_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")

View File

@@ -0,0 +1,990 @@
// <auto-generated />
using System;
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
[DbContext(typeof(SqliteServerDbContext))]
[Migration("20220214060745_RoleBans")]
partial class RoleBans
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.0");
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<int?>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Title")
.HasColumnType("TEXT")
.HasColumnName("title");
b.HasKey("UserId")
.HasName("PK_admin");
b.HasIndex("AdminRankId")
.HasDatabaseName("IX_admin_admin_rank_id");
b.ToTable("admin", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_flag_id");
b.Property<Guid>("AdminId")
.HasColumnType("TEXT")
.HasColumnName("admin_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("flag");
b.Property<bool>("Negative")
.HasColumnType("INTEGER")
.HasColumnName("negative");
b.HasKey("Id")
.HasName("PK_admin_flag");
b.HasIndex("AdminId")
.HasDatabaseName("IX_admin_flag_admin_id");
b.HasIndex("Flag", "AdminId")
.IsUnique();
b.ToTable("admin_flag", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_log_id");
b.Property<int>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<DateTime>("Date")
.HasColumnType("TEXT")
.HasColumnName("date");
b.Property<sbyte>("Impact")
.HasColumnType("INTEGER")
.HasColumnName("impact");
b.Property<string>("Json")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("json");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("message");
b.Property<int>("Type")
.HasColumnType("INTEGER")
.HasColumnName("type");
b.HasKey("Id", "RoundId")
.HasName("PK_admin_log");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_log_round_id");
b.HasIndex("Type")
.HasDatabaseName("IX_admin_log_type");
b.ToTable("admin_log", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b =>
{
b.Property<int>("Uid")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("uid");
b.Property<int?>("AdminLogId")
.HasColumnType("INTEGER")
.HasColumnName("admin_log_id");
b.Property<int?>("AdminLogRoundId")
.HasColumnType("INTEGER")
.HasColumnName("admin_log_round_id");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Uid")
.HasName("PK_admin_log_entity");
b.HasIndex("AdminLogId", "AdminLogRoundId")
.HasDatabaseName("IX_admin_log_entity_admin_log_id_admin_log_round_id");
b.ToTable("admin_log_entity", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
{
b.Property<Guid>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<int>("LogId")
.HasColumnType("INTEGER")
.HasColumnName("log_id");
b.Property<int>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.HasKey("PlayerUserId", "LogId", "RoundId")
.HasName("PK_admin_log_player");
b.HasIndex("LogId", "RoundId");
b.ToTable("admin_log_player", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Id")
.HasName("PK_admin_rank");
b.ToTable("admin_rank", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_flag_id");
b.Property<int>("AdminRankId")
.HasColumnType("INTEGER")
.HasColumnName("admin_rank_id");
b.Property<string>("Flag")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("flag");
b.HasKey("Id")
.HasName("PK_admin_rank_flag");
b.HasIndex("AdminRankId")
.HasDatabaseName("IX_admin_rank_flag_admin_rank_id");
b.HasIndex("Flag", "AdminRankId")
.IsUnique();
b.ToTable("admin_rank_flag", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("antag_id");
b.Property<string>("AntagName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("antag_name");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id")
.HasName("PK_antag");
b.HasIndex("ProfileId", "AntagName")
.IsUnique();
b.ToTable("antag", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AssignedUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("assigned_user_id_id");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("user_name");
b.HasKey("Id")
.HasName("PK_assigned_user_id");
b.HasIndex("UserId")
.IsUnique();
b.HasIndex("UserName")
.IsUnique();
b.ToTable("assigned_user_id", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("connection_log_id");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<byte?>("Denied")
.HasColumnType("INTEGER")
.HasColumnName("denied");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<DateTime>("Time")
.HasColumnType("TEXT")
.HasColumnName("time");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("user_name");
b.HasKey("Id")
.HasName("PK_connection_log");
b.HasIndex("UserId");
b.ToTable("connection_log", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("job_id");
b.Property<string>("JobName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("job_name");
b.Property<int>("Priority")
.HasColumnType("INTEGER")
.HasColumnName("priority");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id")
.HasName("PK_job");
b.HasIndex("ProfileId")
.HasDatabaseName("IX_job_profile_id");
b.HasIndex("ProfileId", "JobName")
.IsUnique();
b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority")
.IsUnique()
.HasFilter("priority = 3");
b.ToTable("job", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Player", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("player_id");
b.Property<DateTime>("FirstSeenTime")
.HasColumnType("TEXT")
.HasColumnName("first_seen_time");
b.Property<string>("LastSeenAddress")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_address");
b.Property<byte[]>("LastSeenHWId")
.HasColumnType("BLOB")
.HasColumnName("last_seen_hwid");
b.Property<DateTime>("LastSeenTime")
.HasColumnType("TEXT")
.HasColumnName("last_seen_time");
b.Property<string>("LastSeenUserName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_seen_user_name");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_player");
b.HasAlternateKey("UserId")
.HasName("ak_player_user_id");
b.HasIndex("LastSeenUserName");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("player", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<string>("AdminOOCColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("admin_ooc_color");
b.Property<int>("SelectedCharacterSlot")
.HasColumnType("INTEGER")
.HasColumnName("selected_character_slot");
b.Property<Guid>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_preference");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("preference", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.Property<int>("Age")
.HasColumnType("INTEGER")
.HasColumnName("age");
b.Property<string>("Backpack")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("backpack");
b.Property<string>("CharacterName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("char_name");
b.Property<string>("Clothing")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("clothing");
b.Property<string>("EyeColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("eye_color");
b.Property<string>("FacialHairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_color");
b.Property<string>("FacialHairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("facial_hair_name");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("gender");
b.Property<string>("HairColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_color");
b.Property<string>("HairName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("hair_name");
b.Property<int>("PreferenceId")
.HasColumnType("INTEGER")
.HasColumnName("preference_id");
b.Property<int>("PreferenceUnavailable")
.HasColumnType("INTEGER")
.HasColumnName("pref_unavailable");
b.Property<string>("Sex")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("sex");
b.Property<string>("SkinColor")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("skin_color");
b.Property<int>("Slot")
.HasColumnType("INTEGER")
.HasColumnName("slot");
b.Property<string>("Species")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("species");
b.HasKey("Id")
.HasName("PK_profile");
b.HasIndex("PreferenceId")
.HasDatabaseName("IX_profile_preference_id");
b.HasIndex("Slot", "PreferenceId")
.IsUnique();
b.ToTable("profile", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.HasKey("Id")
.HasName("PK_round");
b.ToTable("round", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("server_ban_id");
b.Property<string>("Address")
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("TEXT")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_server_ban");
b.HasIndex("Address");
b.HasIndex("UserId");
b.ToTable("server_ban", (string)null);
b.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("server_ban_hit_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<int>("ConnectionId")
.HasColumnType("INTEGER")
.HasColumnName("connection_id");
b.HasKey("Id")
.HasName("PK_server_ban_hit");
b.HasIndex("BanId")
.HasDatabaseName("IX_server_ban_hit_ban_id");
b.HasIndex("ConnectionId")
.HasDatabaseName("IX_server_ban_hit_connection_id");
b.ToTable("server_ban_hit", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("server_role_ban_id");
b.Property<string>("Address")
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("TEXT")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("role_id");
b.Property<int?>("UnbanId")
.HasColumnType("INTEGER")
.HasColumnName("unban_id");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_server_role_ban");
b.HasIndex("UnbanId")
.HasDatabaseName("IX_server_role_ban__unban_id");
b.ToTable("server_role_ban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("role_unban_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("TEXT")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("unbanning_admin");
b.HasKey("Id")
.HasName("PK_server_role_unban");
b.HasIndex("BanId")
.HasDatabaseName("IX_server_role_unban_ban_id");
b.ToTable("server_role_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("unban_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("TEXT")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("unbanning_admin");
b.HasKey("Id")
.HasName("PK_server_unban");
b.HasIndex("BanId")
.IsUnique();
b.ToTable("server_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Whitelist", b =>
{
b.Property<Guid>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("UserId")
.HasName("PK_whitelist");
b.ToTable("whitelist", (string)null);
});
modelBuilder.Entity("PlayerRound", b =>
{
b.Property<int>("PlayersId")
.HasColumnType("INTEGER")
.HasColumnName("players_id");
b.Property<int>("RoundsId")
.HasColumnType("INTEGER")
.HasColumnName("rounds_id");
b.HasKey("PlayersId", "RoundsId")
.HasName("PK_player_round");
b.HasIndex("RoundsId")
.HasDatabaseName("IX_player_round_rounds_id");
b.ToTable("player_round", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "AdminRank")
.WithMany("Admins")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_admin_rank_admin_rank_id");
b.Navigation("AdminRank");
});
modelBuilder.Entity("Content.Server.Database.AdminFlag", b =>
{
b.HasOne("Content.Server.Database.Admin", "Admin")
.WithMany("Flags")
.HasForeignKey("AdminId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_admin_flag_admin_admin_id");
b.Navigation("Admin");
});
modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
{
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany("AdminLogs")
.HasForeignKey("RoundId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_admin_log_round_round_id");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.AdminLogEntity", b =>
{
b.HasOne("Content.Server.Database.AdminLog", null)
.WithMany("Entities")
.HasForeignKey("AdminLogId", "AdminLogRoundId")
.HasConstraintName("FK_admin_log_entity_admin_log_admin_log_id_admin_log_round_id");
});
modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b =>
{
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminLogs")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_admin_log_player_player_player_user_id");
b.HasOne("Content.Server.Database.AdminLog", "Log")
.WithMany("Players")
.HasForeignKey("LogId", "RoundId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_admin_log_player_admin_log_log_id_round_id");
b.Navigation("Log");
b.Navigation("Player");
});
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
{
b.HasOne("Content.Server.Database.AdminRank", "Rank")
.WithMany("Flags")
.HasForeignKey("AdminRankId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id");
b.Navigation("Rank");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Antags")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_antag_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Job", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Jobs")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_job_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.HasOne("Content.Server.Database.Preference", "Preference")
.WithMany("Profiles")
.HasForeignKey("PreferenceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_profile_preference_preference_id");
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithMany("BanHits")
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_ban_hit_server_ban_ban_id");
b.HasOne("Content.Server.Database.ConnectionLog", "Connection")
.WithMany("BanHits")
.HasForeignKey("ConnectionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_ban_hit_connection_log_connection_id");
b.Navigation("Ban");
b.Navigation("Connection");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban")
.WithMany()
.HasForeignKey("UnbanId")
.HasConstraintName("FK_server_role_ban_server_role_unban__unban_id");
b.Navigation("Unban");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithMany()
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_role_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithOne("Unban")
.HasForeignKey("Content.Server.Database.ServerUnban", "BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("PlayerRound", b =>
{
b.HasOne("Content.Server.Database.Player", null)
.WithMany()
.HasForeignKey("PlayersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_player_round_player_players_id");
b.HasOne("Content.Server.Database.Round", null)
.WithMany()
.HasForeignKey("RoundsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_player_round_round_rounds_id");
});
modelBuilder.Entity("Content.Server.Database.Admin", b =>
{
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.AdminLog", b =>
{
b.Navigation("Entities");
b.Navigation("Players");
});
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
{
b.Navigation("Admins");
b.Navigation("Flags");
});
modelBuilder.Entity("Content.Server.Database.ConnectionLog", b =>
{
b.Navigation("BanHits");
});
modelBuilder.Entity("Content.Server.Database.Player", b =>
{
b.Navigation("AdminLogs");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>
{
b.Navigation("Profiles");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.Navigation("Antags");
b.Navigation("Jobs");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.Navigation("AdminLogs");
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Navigation("BanHits");
b.Navigation("Unban");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class RoleBans : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "server_role_unban",
columns: table => new
{
role_unban_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ban_id = table.Column<int>(type: "INTEGER", nullable: false),
unbanning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
unban_time = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server_role_unban", x => x.role_unban_id);
table.ForeignKey(
name: "FK_server_role_unban_server_ban_ban_id",
column: x => x.ban_id,
principalTable: "server_ban",
principalColumn: "server_ban_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "server_role_ban",
columns: table => new
{
server_role_ban_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
user_id = table.Column<Guid>(type: "TEXT", nullable: true),
address = table.Column<string>(type: "TEXT", nullable: true),
hwid = table.Column<byte[]>(type: "BLOB", nullable: true),
ban_time = table.Column<DateTime>(type: "TEXT", nullable: false),
expiration_time = table.Column<DateTime>(type: "TEXT", nullable: true),
reason = table.Column<string>(type: "TEXT", nullable: false),
banning_admin = table.Column<Guid>(type: "TEXT", nullable: true),
unban_id = table.Column<int>(type: "INTEGER", nullable: true),
role_id = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server_role_ban", x => x.server_role_ban_id);
table.ForeignKey(
name: "FK_server_role_ban_server_role_unban__unban_id",
column: x => x.unban_id,
principalTable: "server_role_unban",
principalColumn: "role_unban_id");
});
migrationBuilder.CreateIndex(
name: "IX_server_role_ban__unban_id",
table: "server_role_ban",
column: "unban_id");
migrationBuilder.CreateIndex(
name: "IX_server_role_unban_ban_id",
table: "server_role_unban",
column: "ban_id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "server_role_ban");
migrationBuilder.DropTable(
name: "server_role_unban");
}
}
}

View File

@@ -603,6 +603,88 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("server_ban_hit", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("server_role_ban_id");
b.Property<string>("Address")
.HasColumnType("TEXT")
.HasColumnName("address");
b.Property<DateTime>("BanTime")
.HasColumnType("TEXT")
.HasColumnName("ban_time");
b.Property<Guid?>("BanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("banning_admin");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<byte[]>("HWId")
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("role_id");
b.Property<int?>("UnbanId")
.HasColumnType("INTEGER")
.HasColumnName("unban_id");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("PK_server_role_ban");
b.HasIndex("UnbanId")
.HasDatabaseName("IX_server_role_ban__unban_id");
b.ToTable("server_role_ban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("role_unban_id");
b.Property<int>("BanId")
.HasColumnType("INTEGER")
.HasColumnName("ban_id");
b.Property<DateTime>("UnbanTime")
.HasColumnType("TEXT")
.HasColumnName("unban_time");
b.Property<Guid?>("UnbanningAdmin")
.HasColumnType("TEXT")
.HasColumnName("unbanning_admin");
b.HasKey("Id")
.HasName("PK_server_role_unban");
b.HasIndex("BanId")
.HasDatabaseName("IX_server_role_unban_ban_id");
b.ToTable("server_role_unban", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.Property<int>("Id")
@@ -797,6 +879,28 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Connection");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.HasOne("Content.Server.Database.ServerRoleUnban", "Unban")
.WithMany()
.HasForeignKey("UnbanId")
.HasConstraintName("FK_server_role_ban_server_role_unban__unban_id");
b.Navigation("Unban");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
.WithMany()
.HasForeignKey("BanId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_server_role_unban_server_ban_ban_id");
b.Navigation("Ban");
});
modelBuilder.Entity("Content.Server.Database.ServerUnban", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")

View File

@@ -29,6 +29,8 @@ namespace Content.Server.Database
public DbSet<ServerUnban> Unban { get; set; } = default!;
public DbSet<ConnectionLog> ConnectionLog { get; set; } = default!;
public DbSet<ServerBanHit> ServerBanHit { get; set; } = default!;
public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -387,4 +389,37 @@ namespace Content.Server.Database
public ServerBan Ban { get; set; } = null!;
public ConnectionLog Connection { get; set; } = null!;
}
[Table("server_role_ban")]
public sealed class ServerRoleBan
{
public int Id { get; set; }
public Guid? UserId { get; set; }
[Column(TypeName = "inet")] public (IPAddress, int)? Address { get; set; }
public byte[]? HWId { get; set; }
public DateTime BanTime { get; set; }
public DateTime? ExpirationTime { get; set; }
public string Reason { get; set; } = null!;
public Guid? BanningAdmin { get; set; }
public ServerRoleUnban? Unban { get; set; }
public string RoleId { get; set; } = null!;
}
[Table("server_role_unban")]
public sealed class ServerRoleUnban
{
[Column("role_unban_id")] public int Id { get; set; }
public int BanId { get; set; }
public ServerBan Ban { get; set; } = null!;
public Guid? UnbanningAdmin { get; set; }
public DateTime UnbanTime { get; set; }
}
}

View File

@@ -41,14 +41,15 @@ namespace Content.Server.Database
{
base.OnModelCreating(modelBuilder);
// ReSharper disable once CommentTypo
// ReSharper disable once StringLiteralTypo
// ReSharper disable StringLiteralTypo
// Enforce that an address cannot be IPv6-mapped IPv4.
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
modelBuilder.Entity<ServerBan>()
.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
// ReSharper disable once StringLiteralTypo
modelBuilder.Entity<ServerRoleBan>()
.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
modelBuilder.Entity<Player>()
.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4",
"NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address");
@@ -56,6 +57,7 @@ namespace Content.Server.Database
modelBuilder.Entity<ConnectionLog>()
.HasCheckConstraint("AddressNotIPv6MappedIPv4",
"NOT inet '::ffff:0.0.0.0/96' >>= address");
// ReSharper restore StringLiteralTypo
foreach(var entity in modelBuilder.Model.GetEntityTypes())
{

View File

@@ -58,6 +58,12 @@ namespace Content.Server.Database
.HasColumnType("TEXT")
.HasConversion(ipMaskConverter);
modelBuilder
.Entity<ServerRoleBan>()
.Property(e => e.Address)
.HasColumnType("TEXT")
.HasConversion(ipMaskConverter);
var jsonConverter = new ValueConverter<JsonDocument, string>(
v => JsonDocumentToString(v),
v => StringToJsonDocument(v));

View File

@@ -0,0 +1,49 @@
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class JobBanCommand : IConsoleCommand
{
public string Command => "jobban";
public string Description => "Bans a player from a job";
public string Help => $"Usage: {Command} <name or user ID> <job> <reason> [duration in minutes, leave out or 0 for permanent ban]";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
string target;
string job;
string reason;
uint minutes;
switch (args.Length)
{
case 3:
target = args[0];
job = args[1];
reason = args[2];
minutes = 0;
break;
case 4:
target = args[0];
job = args[1];
reason = args[2];
if (!uint.TryParse(args[3], out minutes))
{
shell.WriteLine($"{args[3]} is not a valid amount of minutes.\n{Help}");
return;
}
break;
default:
shell.WriteLine($"Invalid amount of arguments.");
shell.WriteLine(Help);
return;
}
IoCManager.Resolve<RoleBanManager>().CreateJobBan(shell, target, job, reason, minutes);
}
}

View File

@@ -0,0 +1,84 @@
using System.Text;
using Content.Server.Database;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class RoleBanListCommand : IConsoleCommand
{
public string Command => "rolebanlist";
public string Description => "Lists the user's role bans";
public string Help => "Usage: <name or user ID> [include unbanned]";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1 && args.Length != 2)
{
shell.WriteLine($"Invalid amount of args. {Help}");
return;
}
var includeUnbanned = true;
if (args.Length == 2 && !bool.TryParse(args[1], out includeUnbanned))
{
shell.WriteLine($"Argument two ({args[1]}) is not a boolean.");
return;
}
var dbMan = IoCManager.Resolve<IServerDbManager>();
var target = args[0];
var locator = IoCManager.Resolve<IPlayerLocator>();
var located = await locator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError("Unable to find a player with that name or id.");
return;
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
var targetAddress = located.LastAddress;
var bans = await dbMan.GetServerRoleBansAsync(targetAddress, targetUid, targetHWid, includeUnbanned);
if (bans.Count == 0)
{
shell.WriteLine("That user has no bans in their record.");
return;
}
var bansString = new StringBuilder("Bans in record:\n");
foreach (var ban in bans)
{
bansString
.Append("Ban ID: ")
.Append(ban.Id)
.Append("\n")
.Append("Banned on ")
.Append(ban.BanTime);
if (ban.ExpirationTime != null)
{
bansString
.Append(" until ")
.Append(ban.ExpirationTime.Value);
}
bansString
.Append(".")
.Append("\n");
bansString
.Append("Reason: ")
.Append(ban.Reason)
.Append('\n');
}
shell.WriteLine(bansString.ToString());
}
}

View File

@@ -0,0 +1,183 @@
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Managers;
public sealed class RoleBanManager
{
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
private const string JobPrefix = "Job:";
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
public void Initialize()
{
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected
|| _cachedRoleBans.ContainsKey(e.Session.UserId))
return;
var netChannel = e.Session.ConnectedClient;
await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, netChannel.UserData.HWId);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{
if (banDef.UserId != null)
{
if (!_cachedRoleBans.TryGetValue(banDef.UserId.Value, out var roleBans))
{
roleBans = new HashSet<ServerRoleBanDef>();
_cachedRoleBans.Add(banDef.UserId.Value, roleBans);
}
if (!roleBans.Contains(banDef))
roleBans.Add(banDef);
}
await _db.AddServerRoleBanAsync(banDef);
return true;
}
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{
return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null;
}
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
{
var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false);
var userRoleBans = new HashSet<ServerRoleBanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
_cachedRoleBans[userId] = userRoleBans;
}
public void Restart()
{
// Clear out players that have disconnected.
var toRemove = new List<NetUserId>();
foreach (var player in _cachedRoleBans.Keys)
{
if (!_playerManager.TryGetSessionById(player, out _))
toRemove.Add(player);
}
foreach (var player in toRemove)
{
_cachedRoleBans.Remove(player);
}
// Check for expired bans
foreach (var (_, roleBans) in _cachedRoleBans)
{
roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime);
}
}
#region Job Bans
public async void CreateJobBan(IConsoleShell shell, string target, string job, string reason, uint minutes)
{
if (!_prototypeManager.TryIndex(job, out JobPrototype? _))
{
shell.WriteLine($"Job {job} does not exist.");
return;
}
job = string.Concat(JobPrefix, job);
CreateRoleBan(shell, target, job, reason, minutes);
}
public HashSet<string>? GetJobBans(NetUserId playerUserId)
{
if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
return null;
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix))
.Select(ban => ban.Role[JobPrefix.Length..])
.ToHashSet();
}
#endregion
#region Commands
private async void CreateRoleBan(IConsoleShell shell, string target, string role, string reason, uint minutes)
{
var located = await _playerLocator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError("Unable to find a player with that name.");
return;
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
var targetAddress = located.LastAddress;
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes);
}
(IPAddress, int)? addressRange = null;
if (targetAddress != null)
{
if (targetAddress.IsIPv4MappedToIPv6)
targetAddress = targetAddress.MapToIPv4();
// Ban /64 for IPv4, /32 for IPv4.
var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32;
addressRange = (targetAddress, cidr);
}
var player = shell.Player as IPlayerSession;
var banDef = new ServerRoleBanDef(
null,
targetUid,
addressRange,
targetHWid,
DateTimeOffset.Now,
expires,
reason,
player?.UserId,
null,
role);
if (!await AddRoleBan(banDef))
{
shell.WriteLine($"{target} already has a role ban for {role}");
return;
}
var response = new StringBuilder($"Role banned {target} with reason \"{reason}\"");
response.Append(expires == null ?
" permanently."
: $" until {expires}");
shell.WriteLine(response.ToString());
}
#endregion
}

View File

@@ -306,6 +306,37 @@ namespace Content.Server.Database
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
#endregion
#region Role Bans
/*
* ROLE BANS
*/
/// <summary>
/// Looks up a role ban by id.
/// This will return a pardoned role ban as well.
/// </summary>
/// <param name="id">The role ban id to look for.</param>
/// <returns>The role ban with the given id or null if none exist.</returns>
public abstract Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
/// <summary>
/// Looks up an user's role ban history.
/// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
/// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
/// </summary>
/// <param name="address">The IP address of the user.</param>
/// <param name="userId">The NetUserId of the user.</param>
/// <param name="hwId">The Hardware Id of the user.</param>
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
/// <returns>The user's role ban history.</returns>
public abstract Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned);
public abstract Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban);
#endregion
#region Player Records
/*
* PLAYER RECORDS

View File

@@ -90,6 +90,35 @@ namespace Content.Server.Database
Task AddServerUnbanAsync(ServerUnbanDef serverBan);
#endregion
#region Role Bans
/// <summary>
/// Looks up a role ban by id.
/// This will return a pardoned role ban as well.
/// </summary>
/// <param name="id">The role ban id to look for.</param>
/// <returns>The role ban with the given id or null if none exist.</returns>
Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id);
/// <summary>
/// Looks up an user's role ban history.
/// This will return pardoned role bans based on the <see cref="includeUnbanned"/> bool.
/// Requires one of <see cref="address"/>, <see cref="userId"/>, or <see cref="hwId"/> to not be null.
/// </summary>
/// <param name="address">The IP address of the user.</param>
/// <param name="userId">The NetUserId of the user.</param>
/// <param name="hwId">The Hardware Id of the user.</param>
/// <param name="includeUnbanned">Whether expired and pardoned bans are included.</param>
/// <returns>The user's role ban history.</returns>
Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned = true);
Task AddServerRoleBanAsync(ServerRoleBanDef serverBan);
Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan);
#endregion
#region Player Records
Task UpdatePlayerRecordAsync(
NetUserId userId,
@@ -264,6 +293,32 @@ namespace Content.Server.Database
return _db.AddServerUnbanAsync(serverUnban);
}
#region Role Ban
public Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
{
return _db.GetServerRoleBanAsync(id);
}
public Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned = true)
{
return _db.GetServerRoleBansAsync(address, userId, hwId, includeUnbanned);
}
public Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
{
return _db.AddServerRoleBanAsync(serverRoleBan);
}
public Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
{
return _db.AddServerRoleUnbanAsync(serverRoleUnban);
}
#endregion
public Task UpdatePlayerRecordAsync(
NetUserId userId,
string userName,

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Data;
using System.Linq;
using System.Net;
@@ -34,6 +32,7 @@ namespace Content.Server.Database
});
}
#region Ban
public override async Task<ServerBanDef?> GetServerBanAsync(int id)
{
await using var db = await GetDbImpl();
@@ -52,9 +51,9 @@ namespace Content.Server.Database
NetUserId? userId,
ImmutableArray<byte>? hwId)
{
if (address == null && userId == null)
if (address == null && userId == null && hwId == null)
{
throw new ArgumentException("Address and userId cannot both be null");
throw new ArgumentException("Address, userId, and hwId cannot all be null");
}
await using var db = await GetDbImpl();
@@ -73,7 +72,7 @@ namespace Content.Server.Database
{
if (address == null && userId == null && hwId == null)
{
throw new ArgumentException("Address and userId cannot both be null");
throw new ArgumentException("Address, userId, and hwId cannot all be null");
}
await using var db = await GetDbImpl();
@@ -225,6 +224,191 @@ namespace Content.Server.Database
await db.PgDbContext.SaveChangesAsync();
}
#endregion
#region Role Ban
public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
{
await using var db = await GetDbImpl();
var query = db.PgDbContext.RoleBan
.Include(p => p.Unban)
.Where(p => p.Id == id);
var ban = await query.SingleOrDefaultAsync();
return ConvertRoleBan(ban);
}
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned)
{
if (address == null && userId == null && hwId == null)
{
throw new ArgumentException("Address, userId, and hwId cannot all be null");
}
await using var db = await GetDbImpl();
var query = MakeRoleBanLookupQuery(address, userId, hwId, db, includeUnbanned)
.OrderByDescending(b => b.BanTime);
return await QueryRoleBans(query);
}
private static async Task<List<ServerRoleBanDef>> QueryRoleBans(IQueryable<ServerRoleBan> query)
{
var queryRoleBans = await query.ToArrayAsync();
var bans = new List<ServerRoleBanDef>(queryRoleBans.Length);
foreach (var ban in queryRoleBans)
{
var banDef = ConvertRoleBan(ban);
if (banDef != null)
{
bans.Add(banDef);
}
}
return bans;
}
private static IQueryable<ServerRoleBan> MakeRoleBanLookupQuery(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
DbGuardImpl db,
bool includeUnbanned)
{
IQueryable<ServerRoleBan>? query = null;
if (userId is { } uid)
{
var newQ = db.PgDbContext.RoleBan
.Include(p => p.Unban)
.Where(b => b.UserId == uid.UserId);
query = query == null ? newQ : query.Union(newQ);
}
if (address != null)
{
var newQ = db.PgDbContext.RoleBan
.Include(p => p.Unban)
.Where(b => b.Address != null && EF.Functions.ContainsOrEqual(b.Address.Value, address));
query = query == null ? newQ : query.Union(newQ);
}
if (hwId != null)
{
var newQ = db.PgDbContext.RoleBan
.Include(p => p.Unban)
.Where(b => b.HWId!.SequenceEqual(hwId.Value.ToArray()));
query = query == null ? newQ : query.Union(newQ);
}
if (!includeUnbanned)
{
query = query?.Where(p =>
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now));
}
query = query!.Distinct();
return query;
}
private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
{
if (ban == null)
{
return null;
}
NetUserId? uid = null;
if (ban.UserId is {} guid)
{
uid = new NetUserId(guid);
}
NetUserId? aUid = null;
if (ban.BanningAdmin is {} aGuid)
{
aUid = new NetUserId(aGuid);
}
var unbanDef = ConvertRoleUnban(ban.Unban);
return new ServerRoleBanDef(
ban.Id,
uid,
ban.Address,
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime,
ban.ExpirationTime,
ban.Reason,
aUid,
unbanDef,
ban.RoleId);
}
private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
{
if (unban == null)
{
return null;
}
NetUserId? aUid = null;
if (unban.UnbanningAdmin is {} aGuid)
{
aUid = new NetUserId(aGuid);
}
return new ServerRoleUnbanDef(
unban.Id,
aUid,
unban.UnbanTime);
}
public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan)
{
await using var db = await GetDbImpl();
db.PgDbContext.RoleBan.Add(new ServerRoleBan
{
Address = serverRoleBan.Address,
HWId = serverRoleBan.HWId?.ToArray(),
Reason = serverRoleBan.Reason,
BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
BanTime = serverRoleBan.BanTime.UtcDateTime,
ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime,
UserId = serverRoleBan.UserId?.UserId,
RoleId = serverRoleBan.Role,
});
await db.PgDbContext.SaveChangesAsync();
}
public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban)
{
await using var db = await GetDbImpl();
db.PgDbContext.RoleUnban.Add(new ServerRoleUnban
{
BanId = serverRoleUnban.BanId,
UnbanningAdmin = serverRoleUnban.UnbanningAdmin?.UserId,
UnbanTime = serverRoleUnban.UnbanTime.UtcDateTime
});
await db.PgDbContext.SaveChangesAsync();
}
#endregion
protected override PlayerRecord MakePlayerRecord(Player record)
{

View File

@@ -46,6 +46,7 @@ namespace Content.Server.Database
}
}
#region Ban
public override async Task<ServerBanDef?> GetServerBanAsync(int id)
{
await using var db = await GetDbImpl();
@@ -159,6 +160,162 @@ namespace Content.Server.Database
await db.SqliteDbContext.SaveChangesAsync();
}
#endregion
#region Role Ban
public override async Task<ServerRoleBanDef?> GetServerRoleBanAsync(int id)
{
await using var db = await GetDbImpl();
var ban = await db.SqliteDbContext.RoleBan
.Include(p => p.Unban)
.Where(p => p.Id == id)
.SingleOrDefaultAsync();
return ConvertRoleBan(ban);
}
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned)
{
await using var db = await GetDbImpl();
// SQLite can't do the net masking stuff we need to match IP address ranges.
// So just pull down the whole list into memory.
var queryBans = await GetAllRoleBans(db.SqliteDbContext, includeUnbanned);
return queryBans
.Where(b => BanMatches(b, address, userId, hwId))
.Select(ConvertRoleBan)
.ToList()!;
}
private static async Task<List<ServerRoleBan>> GetAllRoleBans(
SqliteServerDbContext db,
bool includeUnbanned)
{
IQueryable<ServerRoleBan> query = db.RoleBan.Include(p => p.Unban);
if (!includeUnbanned)
{
query = query.Where(p =>
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
}
return await query.ToListAsync();
}
private static bool BanMatches(
ServerRoleBan ban,
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId)
{
if (address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value))
{
return true;
}
if (userId is { } id && ban.UserId == id.UserId)
{
return true;
}
if (hwId is { } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId))
{
return true;
}
return false;
}
public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverBan)
{
await using var db = await GetDbImpl();
db.SqliteDbContext.RoleBan.Add(new ServerRoleBan
{
Address = serverBan.Address,
Reason = serverBan.Reason,
BanningAdmin = serverBan.BanningAdmin?.UserId,
HWId = serverBan.HWId?.ToArray(),
BanTime = serverBan.BanTime.UtcDateTime,
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
UserId = serverBan.UserId?.UserId,
RoleId = serverBan.Role,
});
await db.SqliteDbContext.SaveChangesAsync();
}
public override async Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverUnban)
{
await using var db = await GetDbImpl();
db.SqliteDbContext.RoleUnban.Add(new ServerRoleUnban
{
BanId = serverUnban.BanId,
UnbanningAdmin = serverUnban.UnbanningAdmin?.UserId,
UnbanTime = serverUnban.UnbanTime.UtcDateTime
});
await db.SqliteDbContext.SaveChangesAsync();
}
private static ServerRoleBanDef? ConvertRoleBan(ServerRoleBan? ban)
{
if (ban == null)
{
return null;
}
NetUserId? uid = null;
if (ban.UserId is { } guid)
{
uid = new NetUserId(guid);
}
NetUserId? aUid = null;
if (ban.BanningAdmin is { } aGuid)
{
aUid = new NetUserId(aGuid);
}
var unban = ConvertRoleUnban(ban.Unban);
return new ServerRoleBanDef(
ban.Id,
uid,
ban.Address,
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime,
ban.ExpirationTime,
ban.Reason,
aUid,
unban,
ban.RoleId);
}
private static ServerRoleUnbanDef? ConvertRoleUnban(ServerRoleUnban? unban)
{
if (unban == null)
{
return null;
}
NetUserId? aUid = null;
if (unban.UnbanningAdmin is { } aGuid)
{
aUid = new NetUserId(aGuid);
}
return new ServerRoleUnbanDef(
unban.Id,
aUid,
unban.UnbanTime);
}
#endregion
protected override PlayerRecord MakePlayerRecord(Player record)
{

View File

@@ -0,0 +1,56 @@
using System.Collections.Immutable;
using System.Net;
using Robust.Shared.Network;
namespace Content.Server.Database;
public sealed class ServerRoleBanDef
{
public int? Id { get; }
public NetUserId? UserId { get; }
public (IPAddress address, int cidrMask)? Address { get; }
public ImmutableArray<byte>? HWId { get; }
public DateTimeOffset BanTime { get; }
public DateTimeOffset? ExpirationTime { get; }
public string Reason { get; }
public NetUserId? BanningAdmin { get; }
public ServerRoleUnbanDef? Unban { get; }
public string Role { get; }
public ServerRoleBanDef(
int? id,
NetUserId? userId,
(IPAddress, int)? address,
ImmutableArray<byte>? hwId,
DateTimeOffset banTime,
DateTimeOffset? expirationTime,
string reason,
NetUserId? banningAdmin,
ServerRoleUnbanDef? unban,
string role)
{
if (userId == null && address == null && hwId == null)
{
throw new ArgumentException("Must have at least one of banned user, banned address or hardware ID");
}
if (address is {} addr && addr.Item1.IsIPv4MappedToIPv6)
{
// Fix IPv6-mapped IPv4 addresses
// So that IPv4 addresses are consistent between separate-socket and dual-stack socket modes.
address = (addr.Item1.MapToIPv4(), addr.Item2 - 96);
}
Id = id;
UserId = userId;
Address = address;
HWId = hwId;
BanTime = banTime;
ExpirationTime = expirationTime;
Reason = reason;
BanningAdmin = banningAdmin;
Unban = unban;
Role = role;
}
}

View File

@@ -0,0 +1,19 @@
using Robust.Shared.Network;
namespace Content.Server.Database;
public sealed class ServerRoleUnbanDef
{
public int BanId { get; }
public NetUserId? UnbanningAdmin { get; }
public DateTimeOffset UnbanTime { get; }
public ServerRoleUnbanDef(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
{
BanId = banId;
UnbanningAdmin = unbanningAdmin;
UnbanTime = unbanTime;
}
}

View File

@@ -126,6 +126,7 @@ namespace Content.Server.Entry
IoCManager.Resolve<IGameMapManager>().Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
IoCManager.Resolve<IBqlQueryManager>().DoAutoRegistrations();
IoCManager.Resolve<RoleBanManager>().Initialize();
}
}

View File

@@ -7,6 +7,7 @@ using Content.Shared.CCVar;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.MobState.Components;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -18,6 +19,56 @@ namespace Content.Server.GameTicking
private GamePresetPrototype? _preset;
private bool StartPreset(IPlayerSession[] origReadyPlayers, bool force)
{
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
RaiseLocalEvent(startAttempt);
if (!startAttempt.Cancelled)
return true;
var presetTitle = _preset != null ? Loc.GetString(_preset.ModeTitle) : string.Empty;
void FailedPresetRestart()
{
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
("failedGameMode", presetTitle)));
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
}
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
var oldPreset = _preset;
ClearGameRules();
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
AddGamePresetRules();
StartGamePresetRules();
startAttempt.Uncancel();
RaiseLocalEvent(startAttempt);
_chatManager.DispatchServerAnnouncement(
Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback",
("failedGameMode", presetTitle),
("fallbackMode", Loc.GetString(_preset!.ModeTitle))));
if (startAttempt.Cancelled)
{
FailedPresetRestart();
}
RefreshLateJoinAllowed();
}
else
{
FailedPresetRestart();
return false;
}
return true;
}
private void InitializeGamePreset()
{
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset));

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -7,12 +6,10 @@ using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Station;
using Robust.Server.Player;
using Robust.Shared.Localization;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
@@ -25,17 +22,18 @@ namespace Content.Server.GameTicking
[ViewVariables]
private readonly Dictionary<string, int> _spawnedPositions = new();
private Dictionary<IPlayerSession, (string, StationId)> AssignJobs(List<IPlayerSession> available,
private Dictionary<IPlayerSession, (string, StationId)> AssignJobs(List<IPlayerSession> availablePlayers,
Dictionary<NetUserId, HumanoidCharacterProfile> profiles)
{
var assigned = new Dictionary<IPlayerSession, (string, StationId)>();
List<(IPlayerSession, List<string>)> GetPlayersJobCandidates(bool heads, JobPriority i)
{
return available.Select(player =>
return availablePlayers.Select(player =>
{
var profile = profiles[player.UserId];
var roleBans = _roleBanManager.GetJobBans(player.UserId);
var availableJobs = profile.JobPriorities
.Where(j =>
{
@@ -53,6 +51,7 @@ namespace Content.Server.GameTicking
return priority == i;
})
.Where(p => roleBans != null && !roleBans.Contains(p.Key))
.Select(j => j.Key)
.ToList();
@@ -85,7 +84,7 @@ namespace Content.Server.GameTicking
}
}
available.RemoveAll(a => assigned.ContainsKey(a));
availablePlayers.RemoveAll(a => assigned.ContainsKey(a));
}
// Current strategy is to fill each station one by one.
@@ -106,14 +105,17 @@ namespace Content.Server.GameTicking
return assigned;
}
private string? PickBestAvailableJob(HumanoidCharacterProfile profile, StationId station)
private string? PickBestAvailableJob(IPlayerSession playerSession, HumanoidCharacterProfile profile,
StationId station)
{
var available = _stationSystem.StationInfo[station].JobList;
bool TryPick(JobPriority priority, [NotNullWhen(true)] out string? jobId)
{
var roleBans = _roleBanManager.GetJobBans(playerSession.UserId);
var filtered = profile.JobPriorities
.Where(p => p.Value == priority)
.Where(p => roleBans != null && !roleBans.Contains(p.Key))
.Select(p => p.Key)
.ToList();

View File

@@ -69,7 +69,7 @@ namespace Content.Server.GameTicking
[ViewVariables]
public int RoundId { get; private set; }
private void PreRoundSetup()
private void LoadMaps()
{
AddGamePresetRules();
@@ -192,6 +192,14 @@ namespace Content.Server.GameTicking
StartGamePresetRules();
RoundLengthMetric.Set(0);
var playerIds = _playersInLobby.Keys.Select(player => player.UserId.UserId).ToArray();
RoundId = await _db.AddNewRound(playerIds);
var startingEvent = new RoundStartingEvent();
RaiseLocalEvent(startingEvent);
List<IPlayerSession> readyPlayers;
if (LobbyEnabled)
{
@@ -203,13 +211,13 @@ namespace Content.Server.GameTicking
readyPlayers = _playersInLobby.Keys.ToList();
}
RoundLengthMetric.Set(0);
var playerIds = _playersInLobby.Keys.Select(player => player.UserId.UserId).ToArray();
RoundId = await _db.AddNewRound(playerIds);
var startingEvent = new RoundStartingEvent();
RaiseLocalEvent(startingEvent);
readyPlayers.RemoveAll(p =>
{
if (_roleBanManager.GetRoleBans(p.UserId) != null)
return false;
Logger.ErrorS("RoleBans", $"Role bans for player {p} {p.UserId} have not been loaded yet.");
return true;
});
// Get the profiles for each player for easier lookup.
var profiles = _prefsManager.GetSelectedProfilesForPlayers(
@@ -227,106 +235,13 @@ namespace Content.Server.GameTicking
var origReadyPlayers = readyPlayers.ToArray();
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
RaiseLocalEvent(startAttempt);
var presetTitle = _preset != null ? Loc.GetString(_preset.ModeTitle) : string.Empty;
void FailedPresetRestart()
{
SendServerMessage(Loc.GetString("game-ticker-start-round-cannot-start-game-mode-restart",
("failedGameMode", presetTitle)));
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
}
if (startAttempt.Cancelled)
{
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
var oldPreset = _preset;
ClearGameRules();
SetGamePreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
AddGamePresetRules();
StartGamePresetRules();
startAttempt.Uncancel();
RaiseLocalEvent(startAttempt);
_chatManager.DispatchServerAnnouncement(
Loc.GetString("game-ticker-start-round-cannot-start-game-mode-fallback",
("failedGameMode", presetTitle),
("fallbackMode", Loc.GetString(_preset!.ModeTitle))));
if (startAttempt.Cancelled)
{
FailedPresetRestart();
}
RefreshLateJoinAllowed();
}
else
{
FailedPresetRestart();
return;
}
}
if (!StartPreset(origReadyPlayers, force))
return;
// MapInitialize *before* spawning players, our codebase is too shit to do it afterwards...
_mapManager.DoMapInitialize(DefaultMap);
// Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard)
RaiseLocalEvent(new RulePlayerSpawningEvent(readyPlayers, profiles, force));
var assignedJobs = AssignJobs(readyPlayers, profiles);
// For players without jobs, give them the overflow job if they have that set...
foreach (var player in origReadyPlayers)
{
if (assignedJobs.ContainsKey(player))
{
continue;
}
var profile = profiles[player.UserId];
if (profile.PreferenceUnavailable == PreferenceUnavailableMode.SpawnAsOverflow)
{
// Pick a random station
var stations = _stationSystem.StationInfo.Keys.ToList();
_robustRandom.Shuffle(stations);
if (stations.Count == 0)
{
assignedJobs.Add(player, (FallbackOverflowJob, StationId.Invalid));
continue;
}
foreach (var station in stations)
{
// Pick a random overflow job from that station
var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone();
_robustRandom.Shuffle(overflows);
// Stations with no overflow slots should simply get skipped over.
if (overflows.Count == 0)
continue;
// If the overflow exists, put them in as it.
assignedJobs.Add(player, (overflows[0], stations[0]));
}
}
}
// Spawn everybody in!
foreach (var (player, (job, station)) in assignedJobs)
{
SpawnPlayer(player, profiles[player.UserId], station, job, false);
}
RefreshLateJoinAllowed();
// Allow rules to add roles to players who have been spawned in. (For example, on-station traitors)
RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.ToArray(), profiles, force));
SpawnPlayers(readyPlayers, origReadyPlayers, profiles, force);
_roundStartDateTime = DateTime.UtcNow;
RunLevel = GameRunLevel.InRound;
@@ -458,7 +373,7 @@ namespace Content.Server.GameTicking
RunLevel = GameRunLevel.PreRoundLobby;
LobbySong = _robustRandom.Pick(_lobbyMusicCollection.PickFiles).ToString();
ResettingCleanup();
PreRoundSetup();
LoadMaps();
if (!LobbyEnabled)
{
@@ -508,6 +423,8 @@ namespace Content.Server.GameTicking
_mapManager.Restart();
_roleBanManager.Restart();
// Clear up any game rules.
ClearGameRules();

View File

@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Content.Server.Access.Systems;
@@ -22,13 +20,10 @@ using Content.Shared.Roles;
using Content.Shared.Species;
using Content.Shared.Station;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
@@ -48,6 +43,71 @@ namespace Content.Server.GameTicking
// Mainly to avoid allocations.
private readonly List<EntityCoordinates> _possiblePositions = new();
private void SpawnPlayers(List<IPlayerSession> readyPlayers, IPlayerSession[] origReadyPlayers,
Dictionary<NetUserId, HumanoidCharacterProfile> profiles, bool force)
{
// Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard)
RaiseLocalEvent(new RulePlayerSpawningEvent(readyPlayers, profiles, force));
var assignedJobs = AssignJobs(readyPlayers, profiles);
AssignOverflowJobs(assignedJobs, origReadyPlayers, profiles);
// Spawn everybody in!
foreach (var (player, (job, station)) in assignedJobs)
{
SpawnPlayer(player, profiles[player.UserId], station, job, false);
}
RefreshLateJoinAllowed();
// Allow rules to add roles to players who have been spawned in. (For example, on-station traitors)
RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.ToArray(), profiles, force));
}
private void AssignOverflowJobs(IDictionary<IPlayerSession, (string, StationId)> assignedJobs,
IPlayerSession[] origReadyPlayers, IReadOnlyDictionary<NetUserId, HumanoidCharacterProfile> profiles)
{
// For players without jobs, give them the overflow job if they have that set...
foreach (var player in origReadyPlayers)
{
if (assignedJobs.ContainsKey(player))
{
continue;
}
var profile = profiles[player.UserId];
if (profile.PreferenceUnavailable != PreferenceUnavailableMode.SpawnAsOverflow)
continue;
// Pick a random station
var stations = _stationSystem.StationInfo.Keys.ToList();
if (stations.Count == 0)
{
assignedJobs.Add(player, (FallbackOverflowJob, StationId.Invalid));
continue;
}
_robustRandom.Shuffle(stations);
foreach (var station in stations)
{
// Pick a random overflow job from that station
var overflows = _stationSystem.StationInfo[station].MapPrototype.OverflowJobs.Clone();
_robustRandom.Shuffle(overflows);
// Stations with no overflow slots should simply get skipped over.
if (overflows.Count == 0)
continue;
// If the overflow exists, put them in as it.
assignedJobs.Add(player, (overflows[0], stations[0]));
break;
}
}
}
private void SpawnPlayer(IPlayerSession player, StationId station, string? jobId = null, bool lateJoin = true)
{
var character = GetPlayerProfile(player);
@@ -90,7 +150,7 @@ namespace Content.Server.GameTicking
}
// Pick best job best on prefs.
jobId ??= PickBestAvailableJob(character, station);
jobId ??= PickBestAvailableJob(player, character, station);
// If no job available, stay in lobby, or if no lobby spawn as observer
if (jobId is null)
{

View File

@@ -1,4 +1,5 @@
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.CharacterAppearance.Systems;
using Content.Server.Chat.Managers;
using Content.Server.Ghost;
@@ -16,14 +17,12 @@ using Robust.Shared.Configuration;
#if EXCEPTION_TOLERANCE
using Robust.Shared.Exceptions;
#endif
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
@@ -99,5 +98,6 @@ namespace Content.Server.GameTicking
[Dependency] private readonly PDASystem _pdaSystem = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly GhostSystem _ghosts = default!;
[Dependency] private readonly RoleBanManager _roleBanManager = default!;
}
}

View File

@@ -53,6 +53,7 @@ namespace Content.Server.IoC
IoCManager.Register<IGameMapManager, GameMapManager>();
IoCManager.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
IoCManager.Register<RulesManager, RulesManager>();
IoCManager.Register<RoleBanManager, RoleBanManager>();
}
}
}