diff --git a/Content.Server/Administration/Managers/BanManager.Notification.cs b/Content.Server/Administration/Managers/BanManager.Notification.cs index e9bfa628841..ff84887f00d 100644 --- a/Content.Server/Administration/Managers/BanManager.Notification.cs +++ b/Content.Server/Administration/Managers/BanManager.Notification.cs @@ -1,6 +1,4 @@ -using System.Text.Json; using System.Text.Json.Serialization; -using Content.Server.Database; namespace Content.Server.Administration.Managers; @@ -30,36 +28,15 @@ public sealed partial class BanManager private TimeSpan _banNotificationRateLimitStart; private int _banNotificationRateLimitCount; - private void OnDatabaseNotification(DatabaseNotification notification) + private bool OnDatabaseNotificationEarlyFilter() { - if (notification.Channel != BanNotificationChannel) - return; - - if (notification.Payload == null) - { - _sawmill.Error("Got ban notification with null payload!"); - return; - } - - BanNotificationData data; - try - { - data = JsonSerializer.Deserialize(notification.Payload) - ?? throw new JsonException("Content is null"); - } - catch (JsonException e) - { - _sawmill.Error($"Got invalid JSON in ban notification: {e}"); - return; - } - if (!CheckBanRateLimit()) { _sawmill.Verbose("Not processing ban notification due to rate limit"); - return; + return false; } - _taskManager.RunOnMainThread(() => ProcessBanNotification(data)); + return true; } private async void ProcessBanNotification(BanNotificationData data) diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 0adad5363ab..85cfe1336a5 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -75,7 +75,12 @@ public void Initialize() { _netManager.RegisterNetMessage(); - _db.SubscribeToNotifications(OnDatabaseNotification); + _db.SubscribeToJsonNotification( + _taskManager, + _sawmill, + BanNotificationChannel, + ProcessBanNotification, + OnDatabaseNotificationEarlyFilter); _userDbData.AddOnLoadPlayer(CachePlayerData); _userDbData.AddOnPlayerDisconnect(ClearPlayerData); diff --git a/Content.Server/Administration/Managers/MultiServerKickManager.cs b/Content.Server/Administration/Managers/MultiServerKickManager.cs new file mode 100644 index 00000000000..abc8bb1f2d3 --- /dev/null +++ b/Content.Server/Administration/Managers/MultiServerKickManager.cs @@ -0,0 +1,114 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Content.Server.Database; +using Content.Shared.CCVar; +using Robust.Server.Player; +using Robust.Shared.Asynchronous; +using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Network; +using Robust.Shared.Player; + +namespace Content.Server.Administration.Managers; + +/// +/// Handles kicking people that connect to multiple servers on the same DB at once. +/// +/// +public sealed class MultiServerKickManager +{ + public const string NotificationChannel = "multi_server_kick"; + + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly IServerDbManager _dbManager = null!; + [Dependency] private readonly ILogManager _logManager = null!; + [Dependency] private readonly IConfigurationManager _cfg = null!; + [Dependency] private readonly IAdminManager _adminManager = null!; + [Dependency] private readonly ITaskManager _taskManager = null!; + [Dependency] private readonly IServerNetManager _netManager = null!; + [Dependency] private readonly ILocalizationManager _loc = null!; + [Dependency] private readonly ServerDbEntryManager _serverDbEntry = null!; + + private ISawmill _sawmill = null!; + private bool _allowed; + + public void Initialize() + { + _sawmill = _logManager.GetSawmill("multi_server_kick"); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + _cfg.OnValueChanged(CCVars.AdminAllowMultiServerPlay, b => _allowed = b, true); + + _dbManager.SubscribeToJsonNotification( + _taskManager, + _sawmill, + NotificationChannel, + OnNotification, + OnNotificationEarlyFilter + ); + } + + // ReSharper disable once AsyncVoidMethod + private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (_allowed) + return; + + if (e.NewStatus != SessionStatus.InGame) + return; + + // Send notification to other servers so they can kick this player that just connected. + try + { + await _dbManager.SendNotification(new DatabaseNotification + { + Channel = NotificationChannel, + Payload = JsonSerializer.Serialize(new NotificationData + { + PlayerId = e.Session.UserId, + ServerId = (await _serverDbEntry.ServerEntity).Id, + }), + }); + } + catch (Exception ex) + { + _sawmill.Error($"Failed to send notification for multi server kick: {ex}"); + } + } + + private bool OnNotificationEarlyFilter() + { + if (_allowed) + { + _sawmill.Verbose("Received notification for player join, but multi server play is allowed on this server. Ignoring"); + return false; + } + + return true; + } + + // ReSharper disable once AsyncVoidMethod + private async void OnNotification(NotificationData notification) + { + if (!_playerManager.TryGetSessionById(new NetUserId(notification.PlayerId), out var player)) + return; + + if (notification.ServerId == (await _serverDbEntry.ServerEntity).Id) + return; + + if (_adminManager.IsAdmin(player, includeDeAdmin: true)) + return; + + _sawmill.Info($"Kicking {player} for connecting to another server. Multi-server play is not allowed."); + _netManager.DisconnectChannel(player.Channel, _loc.GetString("multi-server-kick-reason")); + } + + private sealed class NotificationData + { + [JsonPropertyName("player_id")] + public Guid PlayerId { get; set; } + + [JsonPropertyName("server_id")] + public int ServerId { get; set; } + } +} diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 4b4241fc1fb..5941962940d 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -85,13 +85,14 @@ public override void Initialize() Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true); Subs.CVar(_config, CCVars.PanicBunkerMinOverallMinutes, OnPanicBunkerMinOverallMinutesChanged, true); - SubscribeLocalEvent(OnIdentityChanged); SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnRoleEvent); SubscribeLocalEvent(OnRoleEvent); SubscribeLocalEvent(OnRoundRestartCleanup); + SubscribeLocalEvent(OnPlayerRenamed); + SubscribeLocalEvent(OnIdentityChanged); IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors } @@ -149,12 +150,9 @@ public void UpdatePlayerList(ICommonSession player) return value ?? null; } - private void OnIdentityChanged(ref IdentityChangedEvent ev) + private void OnIdentityChanged(Entity ent, ref IdentityChangedEvent ev) { - if (!TryComp(ev.CharacterEntity, out var actor)) - return; - - UpdatePlayerList(actor.PlayerSession); + UpdatePlayerList(ent.Comp.PlayerSession); } private void OnRoleEvent(RoleEvent ev) diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index b4c85ec2008..af0fcf846d7 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -1809,6 +1809,8 @@ await db.DbContext.IPIntelCache #endregion + public abstract Task SendNotification(DatabaseNotification notification); + // SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc. // Normalize DateTimes here so they're always Utc. Thanks. protected abstract DateTime NormalizeDatabaseTime(DateTime time); diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 9fee2c021bd..5ddb3a590cb 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -350,6 +350,15 @@ Task AddConnectionLogAsync( /// The notification to trigger void InjectTestNotification(DatabaseNotification notification); + /// + /// Send a notification to all other servers connected to the same database. + /// + /// + /// The local server will receive the sent notification itself again. + /// + /// The notification to send. + Task SendNotification(DatabaseNotification notification); + #endregion } @@ -1045,6 +1054,12 @@ public void InjectTestNotification(DatabaseNotification notification) HandleDatabaseNotification(notification); } + public Task SendNotification(DatabaseNotification notification) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.SendNotification(notification)); + } + private async void HandleDatabaseNotification(DatabaseNotification notification) { lock (_notificationHandlers) diff --git a/Content.Server/Database/ServerDbManagerExt.cs b/Content.Server/Database/ServerDbManagerExt.cs new file mode 100644 index 00000000000..fad7a7234ec --- /dev/null +++ b/Content.Server/Database/ServerDbManagerExt.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using Robust.Shared.Asynchronous; + +namespace Content.Server.Database; + +public static class ServerDbManagerExt +{ + /// + /// Subscribe to a database notification on a specific channel, formatted as JSON. + /// + /// The database manager to subscribe on. + /// The task manager used to run the main callback on the main thread. + /// Sawmill to log any errors to. + /// + /// The notification channel to listen on. Only notifications on this channel will be handled. + /// + /// + /// The action to run on the notification data. + /// This runs on the main thread. + /// + /// + /// An early filter callback that runs before the JSON message is deserialized. + /// Return false to not handle the notification. + /// This does not run on the main thread. + /// + /// + /// A filter callback that runs after the JSON message is deserialized. + /// Return false to not handle the notification. + /// This does not run on the main thread. + /// + /// The type of JSON data to deserialize. + public static void SubscribeToJsonNotification( + this IServerDbManager dbManager, + ITaskManager taskManager, + ISawmill sawmill, + string channel, + Action action, + Func? earlyFilter = null, + Func? filter = null) + { + dbManager.SubscribeToNotifications(notification => + { + if (notification.Channel != channel) + return; + + if (notification.Payload == null) + { + sawmill.Error($"Got {channel} notification with null payload!"); + return; + } + + if (earlyFilter != null && !earlyFilter()) + return; + + TData data; + try + { + data = JsonSerializer.Deserialize(notification.Payload) + ?? throw new JsonException("Content is null"); + } + catch (JsonException e) + { + sawmill.Error($"Got invalid JSON in {channel} notification: {e}"); + return; + } + + if (filter != null && !filter(data)) + return; + + taskManager.RunOnMainThread(() => + { + action(data); + }); + }); + } +} diff --git a/Content.Server/Database/ServerDbPostgres.Notifications.cs b/Content.Server/Database/ServerDbPostgres.Notifications.cs index 69cf2c7d775..91db2d100f8 100644 --- a/Content.Server/Database/ServerDbPostgres.Notifications.cs +++ b/Content.Server/Database/ServerDbPostgres.Notifications.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.Administration.Managers; +using Microsoft.EntityFrameworkCore; using Npgsql; namespace Content.Server.Database; @@ -17,6 +18,7 @@ public sealed partial class ServerDbPostgres private static readonly string[] NotificationChannels = [ BanManager.BanNotificationChannel, + MultiServerKickManager.NotificationChannel, ]; private static readonly TimeSpan ReconnectWaitIncrease = TimeSpan.FromSeconds(10); @@ -111,6 +113,14 @@ private void OnNotification(object _, NpgsqlNotificationEventArgs notification) }); } + public override async Task SendNotification(DatabaseNotification notification) + { + await using var db = await GetDbImpl(); + + await db.PgDbContext.Database.ExecuteSqlAsync( + $"SELECT pg_notify({notification.Channel}, {notification.Payload})"); + } + public override void Shutdown() { _notificationTokenSource.Cancel(); diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index 6ec90c3332f..c3109ec6e66 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -537,6 +537,12 @@ public override async Task AddAdminMessage(AdminMessage message) return await base.AddAdminMessage(message); } + public override Task SendNotification(DatabaseNotification notification) + { + // Notifications not implemented on SQLite. + return Task.CompletedTask; + } + protected override DateTime NormalizeDatabaseTime(DateTime time) { DebugTools.Assert(time.Kind == DateTimeKind.Unspecified); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 64f40a319cb..7e77d23c707 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -171,6 +171,7 @@ public override void PostInit() IoCManager.Resolve().GetEntitySystem().PostInitialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().PostInit(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index babb03f3c9a..df6f33ffdf2 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -3,6 +3,7 @@ using System.Numerics; using Content.Server._Sunrise.Station; using Content.Server.Administration.Managers; +using Content.Server.Administration.Systems; using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Shuttles.Components; @@ -34,6 +35,7 @@ public sealed partial class GameTicker { [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly AdminSystem _admin = default!; [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; [Dependency] private readonly NewLifeSystem _newLifeSystem = default!; // Sunrise-NewLife @@ -261,6 +263,7 @@ private void SpawnPlayer(ICommonSession player, _roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId); var jobName = _jobs.MindTryGetJobName(newMind); + _admin.UpdatePlayerList(player); if (lateJoin && !silent) { diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 0899474ab83..ad8e7580f1b 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -78,6 +78,7 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); // Sunrise-Hub } diff --git a/Content.Shared/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs index 65e83c3f08c..7e49fb9030d 100644 --- a/Content.Shared/Access/Components/IdCardComponent.cs +++ b/Content.Shared/Access/Components/IdCardComponent.cs @@ -22,6 +22,8 @@ public sealed partial class IdCardComponent : Component [Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)] public LocId? JobTitle; + [DataField] + [AutoNetworkedField] private string? _jobTitle; [Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWriteExecute)] diff --git a/Content.Shared/CCVar/CCVars.Admin.cs b/Content.Shared/CCVar/CCVars.Admin.cs index cbc728f94d2..67025087ca2 100644 --- a/Content.Shared/CCVar/CCVars.Admin.cs +++ b/Content.Shared/CCVar/CCVars.Admin.cs @@ -182,4 +182,11 @@ public sealed partial class CCVars public static readonly CVarDef BanHardwareIds = CVarDef.Create("ban.hardware_ids", true, CVar.SERVERONLY); + + /// + /// If true, players are allowed to connect to multiple game servers at once. + /// If false, they will be kicked from the first when connecting to another. + /// + public static readonly CVarDef AdminAllowMultiServerPlay = + CVarDef.Create("admin.allow_multi_server_play", true, CVar.SERVERONLY); } diff --git a/Content.Shared/Contraband/ContrabandComponent.cs b/Content.Shared/Contraband/ContrabandComponent.cs index a4ce652f2f5..f758d91f0cd 100644 --- a/Content.Shared/Contraband/ContrabandComponent.cs +++ b/Content.Shared/Contraband/ContrabandComponent.cs @@ -24,5 +24,13 @@ public sealed partial class ContrabandComponent : Component /// [DataField] [AutoNetworkedField] - public HashSet>? AllowedDepartments = ["Security"]; + public HashSet> AllowedDepartments = new(); + + /// + /// Which jobs is this item restricted to? + /// If empty, no jobs are allowed to use this beyond the allowed departments. + /// + [DataField] + [AutoNetworkedField] + public HashSet> AllowedJobs = new(); } diff --git a/Content.Shared/Contraband/ContrabandSeverityPrototype.cs b/Content.Shared/Contraband/ContrabandSeverityPrototype.cs index c1ab4b8292e..094275a6fd3 100644 --- a/Content.Shared/Contraband/ContrabandSeverityPrototype.cs +++ b/Content.Shared/Contraband/ContrabandSeverityPrototype.cs @@ -19,8 +19,8 @@ public sealed partial class ContrabandSeverityPrototype : IPrototype public LocId ExamineText; /// - /// When examining the contraband, should this take into account the viewer's departments? + /// When examining the contraband, should this take into account the viewer's departments and job? /// [DataField] - public bool ShowDepartments; + public bool ShowDepartmentsAndJobs; } diff --git a/Content.Shared/Contraband/ContrabandSystem.cs b/Content.Shared/Contraband/ContrabandSystem.cs index e6931f2860a..811ea535675 100644 --- a/Content.Shared/Contraband/ContrabandSystem.cs +++ b/Content.Shared/Contraband/ContrabandSystem.cs @@ -40,6 +40,7 @@ public void CopyDetails(EntityUid uid, ContrabandComponent other, ContrabandComp contraband.Severity = other.Severity; contraband.AllowedDepartments = other.AllowedDepartments; + contraband.AllowedJobs = other.AllowedJobs; Dirty(uid, contraband); } @@ -54,11 +55,15 @@ private void OnExamined(Entity ent, ref ExaminedEvent args) using (args.PushGroup(nameof(ContrabandComponent))) { + // TODO shouldn't department prototypes have a localized name instead of just using the ID for this? + var localizedDepartments = ent.Comp.AllowedDepartments.Select(p => Loc.GetString($"department-{p.Id}")); + var localizedJobs = ent.Comp.AllowedJobs.Select(p => _proto.Index(p).LocalizedName); + var severity = _proto.Index(ent.Comp.Severity); - if (severity.ShowDepartments && ent.Comp is { AllowedDepartments: not null }) + if (severity.ShowDepartmentsAndJobs) { - // TODO shouldn't department prototypes have a localized name instead of just using the ID for this? - var list = ContentLocalizationManager.FormatList(ent.Comp.AllowedDepartments.Select(p => Loc.GetString($"department-{p.Id}")).ToList()); + //creating a combined list of jobs and departments for the restricted text + var list = ContentLocalizationManager.FormatList(localizedDepartments.Concat(localizedJobs).ToList()); // department restricted text args.PushMarkup(Loc.GetString("contraband-examine-text-Restricted-department", ("departments", list))); @@ -69,23 +74,30 @@ private void OnExamined(Entity ent, ref ExaminedEvent args) } // text based on ID card - List>? departments = null; + List> departments = new(); + var jobId = ""; + if (_id.TryFindIdCard(args.Examiner, out var id)) { departments = id.Comp.JobDepartments; + if (id.Comp.LocalizedJobTitle is not null) + { + jobId = id.Comp.LocalizedJobTitle; + } } - // either its fully restricted, you have no departments, or your departments dont intersect with the restricted departments - if (ent.Comp.AllowedDepartments is null - || departments is null - || !departments.Intersect(ent.Comp.AllowedDepartments).Any()) + // for the jobs we compare the localized string in case you use an agent ID or custom job name that is not a prototype + if (departments.Intersect(ent.Comp.AllowedDepartments).Any() + || localizedJobs.Contains(jobId)) + { + // you are allowed to use this! + args.PushMarkup(Loc.GetString("contraband-examine-text-in-the-clear")); + } + else { + // straight to jail! args.PushMarkup(Loc.GetString("contraband-examine-text-avoid-carrying-around")); - return; } - - // otherwise fine to use :tm: - args.PushMarkup(Loc.GetString("contraband-examine-text-in-the-clear")); } } } diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 3e29fcedd36..440a5d567aa 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -728,5 +728,14 @@ Entries: id: 90 time: '2025-01-17T07:24:50.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33782 +- author: PJB3005 + changes: + - message: Introduced an option to automatically disconnect players from their current + server if they join another one within the same server community. Admins are + always exempt. + type: Add + id: 91 + time: '2025-01-21T23:23:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34563 Name: Admin Order: 1 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e9cfa5a912b..d1cbbc880c2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,32 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: The captain now have special late join message. - type: Tweak - id: 7335 - time: '2024-09-09T19:57:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31991 -- author: Thinbug0 - changes: - - message: Teal gloves can now be found at the ClothesMate! - type: Add - id: 7336 - time: '2024-09-09T21:47:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31865 -- author: chillyconmor - changes: - - message: Space Ninjas now have a new intro song. - type: Add - id: 7337 - time: '2024-09-09T22:12:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31055 -- author: DieselMohawk - changes: - - message: Made Trooper Uniform accessible for Security Officers in loadouts - type: Fix - id: 7338 - time: '2024-09-09T23:59:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32019 - author: IProduceWidgets changes: - message: Visitors now can have the correct Id cards and PDA! @@ -3892,3 +3864,43 @@ id: 7834 time: '2025-01-20T01:24:40.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/34527 +- author: Compilatron144 + changes: + - message: Raised Plasma Station's population limit + type: Tweak + id: 7835 + time: '2025-01-21T04:56:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34549 +- author: sporkyz + changes: + - message: Galoshes are now restricted to janitor. + type: Tweak + - message: Double barrel shotguns, basic armor vests, beanbag/flare shotgun shells, + and bandoliers are now allowed for Bartenders, not just Security. + type: Tweak + - message: Lawyers now have access to the Security headset they spawn with, as well + as the encryption key inside. + type: Fix + - message: Sawn-off shotguns are now properly considered contraband instead of being + unrestricted. + type: Fix + id: 7836 + time: '2025-01-21T09:51:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33385 +- author: K-Dynamic + changes: + - message: Electrified doors and windoors now visibly spark when touched. + type: Tweak + - message: New tips have been added about opening electrified doors by throwing + your PDA or ID, and resetting doors that are electrified by AI. + type: Add + id: 7837 + time: '2025-01-21T10:39:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34502 +- author: Nimfar11 + changes: + - message: Adds Blueprint double emergency tank. + type: Add + id: 7838 + time: '2025-01-21T16:08:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34232 diff --git a/Resources/Locale/en-US/_strings/administration/multi-server-kick.ftl b/Resources/Locale/en-US/_strings/administration/multi-server-kick.ftl new file mode 100644 index 00000000000..2aa3c4ed35f --- /dev/null +++ b/Resources/Locale/en-US/_strings/administration/multi-server-kick.ftl @@ -0,0 +1 @@ +multi-server-kick-reason = Connected to different server in this community. diff --git a/Resources/Locale/en-US/_strings/tips.ftl b/Resources/Locale/en-US/_strings/tips.ftl index 154ac8e18bb..dcadd15b1b0 100644 --- a/Resources/Locale/en-US/_strings/tips.ftl +++ b/Resources/Locale/en-US/_strings/tips.ftl @@ -133,3 +133,5 @@ tips-dataset-132 = By right clicking on a player, and then clicking the heart ic tips-dataset-133 = Monkeys and kobolds have a rare chance to be sentient. Ook! tips-dataset-134 = You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them. tips-dataset-135 = Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands. +tips-dataset-136 = If you're trapped behind an electrified door, disable the APC or throw your ID at the door to avoid getting shocked! +tips-dataset-137 = If the AI electrifies a door and you have insulated gloves, snip and mend the power wire to reset their electrification! diff --git a/Resources/Maps/plasma.yml b/Resources/Maps/plasma.yml index e2be97d6f4c..b5b2c2ee6c3 100644 --- a/Resources/Maps/plasma.yml +++ b/Resources/Maps/plasma.yml @@ -2210,6 +2210,8 @@ entities: 713: -32,-30 714: -31,-19 - node: + cleanable: True + zIndex: 1 color: '#FFFFFFFF' id: Dirt decals: @@ -2226,11 +2228,6 @@ entities: 6708: -71,-37 6709: -70,-36 6710: -68,-36 - - node: - cleanable: True - color: '#FFFFFFFF' - id: Dirt - decals: 5206: -56,-54 5207: -55,-55 5208: -55,-54 @@ -2293,6 +2290,8 @@ entities: 7242: -21,19 7243: -22,20 - node: + cleanable: True + zIndex: 1 color: '#FFFFFFFF' id: DirtHeavy decals: @@ -2305,11 +2304,6 @@ entities: 6083: -21,5 6084: -21,4 6085: -22,4 - - node: - cleanable: True - color: '#FFFFFFFF' - id: DirtHeavy - decals: 329: -54,-11 330: -50,-11 1572: -65,-34 @@ -2365,11 +2359,14 @@ entities: 4063: -98,-18 - node: cleanable: True + zIndex: 1 color: '#FFFFFFFF' id: DirtHeavyMonotile decals: 1579: -66,-34 - node: + cleanable: True + zIndex: 1 color: '#FFFFFFFF' id: DirtLight decals: @@ -2389,11 +2386,6 @@ entities: 6713: -71,-32 6714: -71,-31 6715: -71,-30 - - node: - cleanable: True - color: '#FFFFFFFF' - id: DirtLight - decals: 1580: -64,-33 1581: -65,-33 1582: -64,-32 @@ -2490,6 +2482,8 @@ entities: 6921: -153,-44 6922: -145,-45 - node: + cleanable: True + zIndex: 1 color: '#FFFFFFFF' id: DirtMedium decals: @@ -2652,11 +2646,6 @@ entities: 7221: -121,34 7222: -122,34 7223: -122,33 - - node: - cleanable: True - color: '#FFFFFFFF' - id: DirtMedium - decals: 331: -54,-10 332: -51,-10 333: -52,-10 diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml index b710d69fabf..b57f2052d9b 100644 --- a/Resources/Prototypes/Datasets/tips.yml +++ b/Resources/Prototypes/Datasets/tips.yml @@ -2,4 +2,4 @@ id: Tips values: prefix: tips-dataset- - count: 135 + count: 137 diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml index 1c368684241..b83eaf3138a 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml @@ -577,7 +577,7 @@ # Belts without visualizers - type: entity - parent: [ClothingBeltAmmoProviderBase, BaseRestrictedContraband] + parent: [ClothingBeltAmmoProviderBase, BaseSecurityBartenderContraband] id: ClothingBeltBandolier name: bandolier description: A bandolier for holding shotgun ammunition. diff --git a/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml b/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml index 817f83ec927..ca9d0e89a1c 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/headsets.yml @@ -226,7 +226,7 @@ - EncryptionKeyCommon - type: entity - parent: [ClothingHeadset, BaseRestrictedContraband] + parent: [ClothingHeadset, BaseSecurityLawyerContraband] id: ClothingHeadsetSecurity name: security headset description: This is used by your elite security force. diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index f135e7e8c30..a4dea9a0337 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -1,11 +1,12 @@ # Numbers for armor here largely taken from /tg/. # NOTE: Half of the kind of armor you're probably thinking of is in vests.yml. These should probably be merged some day. -#Basic armor vest +#Basic armor vest for inheritance - type: entity parent: [ClothingOuterBaseMedium, AllowSuitStorageClothing, BaseRestrictedContraband] - id: ClothingOuterArmorBasic + id: ClothingOuterArmorBase name: armor vest + abstract: true description: A standard Type I armored vest that provides decent protection against most types of damage. components: - type: Sprite @@ -22,6 +23,11 @@ - type: ExplosionResistance damageCoefficient: 0.90 +#Standard armor vest, allowed for security and bartenders +- type: entity + parent: [ BaseSecurityBartenderContraband, ClothingOuterArmorBase] + id: ClothingOuterArmorBasic + #Alternate / slim basic armor vest - type: entity parent: ClothingOuterArmorBasic @@ -58,7 +64,7 @@ - type: GroupExamine - type: entity - parent: ClothingOuterArmorBasic + parent: ClothingOuterArmorBase id: ClothingOuterArmorBulletproof name: bulletproof vest description: A Type III heavy bulletproof vest that excels in protecting the wearer against traditional projectile weaponry and explosives to a minor extent. @@ -78,7 +84,7 @@ damageCoefficient: 0.80 - type: entity - parent: ClothingOuterArmorBasic + parent: ClothingOuterArmorBase id: ClothingOuterArmorReflective name: reflective vest description: An armored vest with advanced shielding to protect against energy weapons. diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml index e0e698ca016..328fb6c00a8 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml @@ -10,7 +10,7 @@ sprite: Clothing/OuterClothing/Coats/bomber.rsi - type: entity - parent: [ClothingOuterStorageBase, AllowSuitStorageClothing, ClothingOuterArmorBasic] + parent: [ClothingOuterStorageBase, AllowSuitStorageClothing, ClothingOuterArmorBase] id: ClothingOuterCoatDetective name: detective trenchcoat description: An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business. diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 994e86b548c..accd24dd3cc 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -42,7 +42,7 @@ #Detective's vest - type: entity - parent: [ClothingOuterArmorBasic, BaseRestrictedContraband] + parent: [ClothingOuterArmorBase, BaseRestrictedContraband] id: ClothingOuterVestDetective name: detective's vest description: A hard-boiled private investigator's armored vest. diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index b1cb4772c9f..74b61ed4534 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -104,7 +104,7 @@ sprite: Clothing/Shoes/Specific/cult.rsi - type: entity - parent: ClothingShoesBase + parent: [ ClothingShoesBase, BaseJanitorContraband ] id: ClothingShoesGaloshes name: galoshes description: Rubber boots. diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml index 943b9065603..6f753fa413a 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml @@ -222,6 +222,7 @@ id: SalvageEquipmentRare table: !type:GroupSelector children: + - id: BlueprintDoubleEmergencyTank - id: FultonBeacon - id: Fulton amount: !type:RangeNumberSelector diff --git a/Resources/Prototypes/Entities/Objects/Devices/encryption_keys.yml b/Resources/Prototypes/Entities/Objects/Devices/encryption_keys.yml index 7393532654d..2d23885bcac 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/encryption_keys.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/encryption_keys.yml @@ -170,7 +170,7 @@ - state: robotics_label - type: entity - parent: [ EncryptionKey, BaseSecurityContraband ] + parent: [ EncryptionKey, BaseSecurityLawyerContraband ] id: EncryptionKeySecurity name: security encryption key description: An encryption key used by security. diff --git a/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml b/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml index 43cbdc2431f..64aa91c3736 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/blueprint.yml @@ -37,3 +37,14 @@ - type: Blueprint providedRecipes: - SeismicCharge + +- type: entity + parent: BaseBlueprint + id: BlueprintDoubleEmergencyTank + name: double emergency tank blueprint + description: A blueprint with a schematic of a double emergency tank. It can be inserted into an autolathe. + components: + - type: Blueprint + providedRecipes: + - DoubleEmergencyOxygenTank + - DoubleEmergencyNitrogenTank diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/shotgun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/shotgun.yml index d375a2feea3..5f6ac78bf76 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/shotgun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/shotgun.yml @@ -50,7 +50,7 @@ - type: entity id: ShellShotgunBeanbag name: shell (.50 beanbag) - parent: BaseShellHitscanShotgun + parent: [ BaseShellHitscanShotgun, BaseSecurityBartenderContraband ] # Sunrise-Edit components: - type: Tag tags: @@ -83,7 +83,7 @@ - type: entity id: ShellShotgunFlare name: shell (.50 flare) - parent: BaseShellShotgun + parent: [ BaseShellShotgun, BaseSecurityBartenderContraband ] components: - type: Tag tags: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml index 5105c8451f0..5e6b0ef8da5 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml @@ -114,7 +114,7 @@ - type: entity name: double-barreled shotgun - parent: [BaseWeaponShotgun, BaseGunWieldable, BaseMinorContraband] + parent: [BaseWeaponShotgun, BaseGunWieldable, BaseSecurityBartenderContraband] id: WeaponShotgunDoubleBarreled description: An immortal classic. Uses .50 shotgun shells. components: @@ -203,7 +203,7 @@ - type: entity name: sawn-off shotgun - parent: BaseWeaponShotgun + parent: [ BaseWeaponShotgun, BaseSecurityBartenderContraband ] id: WeaponShotgunSawn description: Groovy! Uses .50 shotgun shells. components: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index 078dbd12d84..8cbc6bf7eed 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -203,7 +203,6 @@ doAfterDuration: 4.0 - type: Contraband severity: Syndicate - allowedDepartments: null - type: Sprite sprite: Objects/Weapons/Melee/e_dagger.rsi layers: diff --git a/Resources/Prototypes/Entities/Objects/base_contraband.yml b/Resources/Prototypes/Entities/Objects/base_contraband.yml index 6f44767c9f1..84df2f8b37e 100644 --- a/Resources/Prototypes/Entities/Objects/base_contraband.yml +++ b/Resources/Prototypes/Entities/Objects/base_contraband.yml @@ -5,8 +5,6 @@ components: - type: Contraband severity: Syndicate - # no one should be carrying this around visibly! - allowedDepartments: null # minor contraband not departmentally restricted -- improvised weapons etc - type: entity @@ -15,8 +13,6 @@ components: - type: Contraband severity: Minor - # according to space law no dept is authorized to have - allowedDepartments: null # major contraband, for things like guns or weaponry that don't belong to any department and aren't syndicate specific - type: entity @@ -25,7 +21,6 @@ components: - type: Contraband severity: Major - allowedDepartments: null # minor contraband by default restricted to security only - type: entity @@ -157,6 +152,33 @@ - type: Contraband allowedDepartments: [ Medical, Science ] +# contraband restricted by job by some degree +- type: entity + id: BaseSecurityBartenderContraband + parent: BaseRestrictedContraband + abstract: true + components: + - type: Contraband + allowedDepartments: [ Security ] + allowedJobs: [ Bartender ] + +- type: entity + id: BaseSecurityLawyerContraband + parent: BaseRestrictedContraband + abstract: true + components: + - type: Contraband + allowedDepartments: [ Security ] + allowedJobs: [ Lawyer ] + +- type: entity + id: BaseJanitorContraband + parent: BaseRestrictedContraband + abstract: true + components: + - type: Contraband + allowedJobs: [ Janitor ] + # for ~objective items - type: entity id: BaseGrandTheftContraband diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 92b14f8655d..8bb9605500a 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -30,11 +30,16 @@ shader: unshaded - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: ["enum.ElectrifiedLayers.HUD"] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: ["enum.ElectrifiedLayers.Sparks"] - type: AnimationPlayer - type: Physics - type: Fixtures @@ -77,6 +82,12 @@ - type: NavMapDoor - type: DoorBolt - type: Appearance + - type: GenericVisualizer + visuals: + enum.ElectrifiedVisuals.ShowSparks: + enum.ElectrifiedLayers.Sparks: + True: { visible: True } + False: { visible: False } - type: WiresVisuals - type: ElectrocutionHUDVisuals - type: ApcPowerReceiver diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml index fb7e0c4e0a4..a85c39148a0 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml @@ -27,11 +27,16 @@ shader: unshaded - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: ["enum.ElectrifiedLayers.HUD"] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: ["enum.ElectrifiedLayers.Sparks"] - type: AnimationPlayer - type: Physics - type: Fixtures @@ -71,6 +76,12 @@ - type: AccessReader containerAccessProvider: board - type: Appearance + - type: GenericVisualizer + visuals: + enum.ElectrifiedVisuals.ShowSparks: + enum.ElectrifiedLayers.Sparks: + True: { visible: True } + False: { visible: False } - type: WiresVisuals - type: ElectrocutionHUDVisuals - type: ApcPowerReceiver diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index 81ef89997d8..10062d7803e 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -43,11 +43,16 @@ map: ["enum.DoorVisualLayers.BaseEmergencyAccess"] - state: panel_open map: ["enum.WiresVisualLayers.MaintenancePanel"] - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: ["enum.ElectrifiedLayers.HUD"] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: ["enum.ElectrifiedLayers.Sparks"] - type: AnimationPlayer - type: ApcPowerReceiver - type: ExtensionCableReceiver @@ -142,6 +147,12 @@ enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Appearance + - type: GenericVisualizer + visuals: + enum.ElectrifiedVisuals.ShowSparks: + enum.ElectrifiedLayers.Sparks: + True: { visible: True } + False: { visible: False } - type: WiresVisuals - type: ElectrocutionHUDVisuals - type: Airtight @@ -183,11 +194,16 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: [ "enum.ElectrifiedLayers.HUD" ] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: [ "enum.ElectrifiedLayers.Sparks" ] - type: Damageable damageModifierSet: RGlass - type: Destructible @@ -249,11 +265,16 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: [ "enum.ElectrifiedLayers.HUD" ] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: [ "enum.ElectrifiedLayers.Sparks" ] - type: Destructible thresholds: - trigger: @@ -310,11 +331,16 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false - map: ["enum.ElectrifiedLayers.HUD"] + map: [ "enum.ElectrifiedLayers.HUD" ] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: [ "enum.ElectrifiedLayers.Sparks" ] - type: Destructible thresholds: - trigger: @@ -376,11 +402,16 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: [ "enum.ElectrifiedLayers.HUD" ] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: [ "enum.ElectrifiedLayers.Sparks" ] - type: Destructible thresholds: - trigger: @@ -437,11 +468,16 @@ - state: panel_open map: [ "enum.WiresVisualLayers.MaintenancePanel" ] visible: false - - state: electrified + - state: electrified_ai sprite: Interface/Misc/ai_hud.rsi shader: unshaded visible: false map: [ "enum.ElectrifiedLayers.HUD" ] + - state: electrified + sprite: Effects/electricity.rsi + shader: unshaded + visible: false + map: [ "enum.ElectrifiedLayers.Sparks" ] - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Maps/plasma.yml b/Resources/Prototypes/Maps/plasma.yml index 1369e5a42db..85e8661200c 100644 --- a/Resources/Prototypes/Maps/plasma.yml +++ b/Resources/Prototypes/Maps/plasma.yml @@ -2,8 +2,7 @@ id: Plasma mapName: 'Plasma' mapPath: /Maps/plasma.yml - minPlayers: 20 - maxPlayers: 60 + minPlayers: 30 stations: Plasma: stationProto: StandardNanotrasenStation @@ -18,10 +17,16 @@ - type: StationCargoShuttle path: /Maps/Shuttles/cargo_plasma.yml - type: StationJobs - availableJobs: - #service + availableJobs: #Total of 66 jobs roundstart, max of 93 inc. latejoins and trainees. + #command - 7 Captain: [ 1, 1 ] HeadOfPersonnel: [ 1, 1 ] + ChiefEngineer: [ 1, 1 ] + ChiefMedicalOfficer: [ 1, 1 ] + ResearchDirector: [ 1, 1 ] + HeadOfSecurity: [ 1, 1 ] + Quartermaster: [ 1, 1 ] + #service - 18-21 Bartender: [ 2, 2 ] Botanist: [ 2, 3 ] Chef: [ 2, 2 ] @@ -30,37 +35,32 @@ Librarian: [ 1, 1 ] ServiceWorker: [ 2, 2 ] Reporter: [ 2, 3 ] - #engineering - ChiefEngineer: [ 1, 1 ] + Clown: [ 1, 2 ] # This might be fun + Mime: [ 1, 1 ] + Musician: [ 1, 1 ] + #engineering - 8-12 AtmosphericTechnician: [ 4, 4 ] StationEngineer: [ 4, 4 ] TechnicalAssistant: [ 4, 4 ] - #medical - ChiefMedicalOfficer: [ 1, 1 ] + #medical - 9-13 Chemist: [ 2, 2 ] MedicalDoctor: [ 4, 4 ] Paramedic: [ 2, 2 ] MedicalIntern: [ 4, 4 ] Psychologist: [ 1, 1 ] - #science - ResearchDirector: [ 1, 1 ] + #science - 7-13 Scientist: [ 4, 4 ] ResearchAssistant: [ 4, 4 ] StationAi: [ 1, 1 ] Borg: [ 2, 4 ] - #security - HeadOfSecurity: [ 1, 1 ] + #security - 9-17 Warden: [ 1, 1 ] SecurityOfficer: [ 6, 8 ] Detective: [ 1, 2 ] SecurityCadet: [ 4, 4 ] Lawyer: [ 1, 2 ] - #supply - Quartermaster: [ 1, 1 ] + #supply - 8-10 SalvageSpecialist: [ 4, 4 ] CargoTechnician: [ 4, 6 ] - #civilian + #civilian - the tiders yearn for the mines Passenger: [ -1, -1 ] - Clown: [ 1, 1 ] - Mime: [ 1, 1 ] - Musician: [ 1, 1 ] diff --git a/Resources/Prototypes/Recipes/Lathes/misc.yml b/Resources/Prototypes/Recipes/Lathes/misc.yml index 665da813701..7fde52bbd23 100644 --- a/Resources/Prototypes/Recipes/Lathes/misc.yml +++ b/Resources/Prototypes/Recipes/Lathes/misc.yml @@ -119,6 +119,20 @@ materials: Steel: 300 +- type: latheRecipe + id: DoubleEmergencyOxygenTank + result: DoubleEmergencyOxygenTank + completetime: 4 + materials: + Steel: 250 + +- type: latheRecipe + id: DoubleEmergencyNitrogenTank + result: DoubleEmergencyNitrogenTank + completetime: 4 + materials: + Steel: 250 + - type: latheRecipe id: ClothingShoesBootsMagSci result: ClothingShoesBootsMagSci diff --git a/Resources/Prototypes/contraband_severities.yml b/Resources/Prototypes/contraband_severities.yml index 54f5cd62ef6..c103af5e0a6 100644 --- a/Resources/Prototypes/contraband_severities.yml +++ b/Resources/Prototypes/contraband_severities.yml @@ -14,7 +14,7 @@ - type: contrabandSeverity id: Restricted examineText: contraband-examine-text-Restricted - showDepartments: true + showDepartmentsAndJobs: true # Having this as a regular crew member is considered grand theft. (nuke disk, captain's gear, objective items, etc) - type: contrabandSeverity diff --git a/Resources/Prototypes/wizardsDenWhitelists.yml b/Resources/Prototypes/wizardsDenWhitelists.yml index c21d01e5a89..8b8420622af 100644 --- a/Resources/Prototypes/wizardsDenWhitelists.yml +++ b/Resources/Prototypes/wizardsDenWhitelists.yml @@ -12,20 +12,20 @@ range: 30 # 30 days action: Deny includeSecret: false - - !type:ConditionNotesPlaytimeRange # Deny for >=2 medium severity notes in the last 14 days + - !type:ConditionNotesPlaytimeRange # Deny for >=3 medium severity notes in the last 90 days includeExpired: false minimumSeverity: 2 # Medium - minimumNotes: 1 - range: 14 # 14 Days - action: Deny - includeSecret: false - - !type:ConditionNotesPlaytimeRange # Deny for >=3 low severity notes in the last 14 days - includeExpired: false - minimumSeverity: 1 # Low minimumNotes: 3 - range: 14 # 14 Days + range: 90 # 90 Days action: Deny includeSecret: false +# - !type:ConditionNotesPlaytimeRange # Deny for >=3 low severity notes in the last 14 days +# includeExpired: false +# minimumSeverity: 1 # Low +# minimumNotes: 3 +# range: 14 # 14 Days +# action: Deny +# includeSecret: false - !type:ConditionManualWhitelistMembership # Allow whitelisted players action: Allow - !type:ConditionPlayerCount # Allow when <= 15 players are online diff --git a/Resources/Textures/Interface/Misc/ai_hud.rsi/electrified.png b/Resources/Textures/Interface/Misc/ai_hud.rsi/electrified_ai.png similarity index 100% rename from Resources/Textures/Interface/Misc/ai_hud.rsi/electrified.png rename to Resources/Textures/Interface/Misc/ai_hud.rsi/electrified_ai.png diff --git a/Resources/Textures/Interface/Misc/ai_hud.rsi/meta.json b/Resources/Textures/Interface/Misc/ai_hud.rsi/meta.json index 7f1e67ac4d9..3df9d296a0f 100644 --- a/Resources/Textures/Interface/Misc/ai_hud.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/ai_hud.rsi/meta.json @@ -8,7 +8,7 @@ }, "states": [ { - "name": "electrified", + "name": "electrified_ai", "delays": [ [ 0.2, @@ -34,4 +34,4 @@ ] } ] -} \ No newline at end of file +}