diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml index 241c7bfe95a3..7a468acf0697 100644 --- a/.github/workflows/test-packaging.yml +++ b/.github/workflows/test-packaging.yml @@ -53,10 +53,11 @@ jobs: cd RobustToolbox/ git submodule update --init --recursive - - name: Setup .NET Core + # ubuntu-latest has .NET 10 + - name: Setup .NET Core # Starlight start uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.x + dotnet-version: 10.0.x # Starlight end - name: Install dependencies run: dotnet restore @@ -65,7 +66,13 @@ jobs: run: dotnet build Content.Packaging --configuration Release --no-restore /m - name: Package server - run: dotnet run --project Content.Packaging server --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64 + run: dotnet run --project Content.Packaging server --log-build --platform win-x64 --platform win-arm64 --platform linux-x64 --platform linux-arm64 --platform osx-x64 --platform osx-arm64 - name: Package client - run: dotnet run --project Content.Packaging client --no-wipe-release + run: dotnet run --project Content.Packaging client --log-build --no-wipe-release + + - uses: actions/upload-artifact@v4 + with: + name: binlogs + path: release/*.binlog + retention-days: 7 diff --git a/Content.Client/Access/Systems/JobStatusSystem.Starlight.cs b/Content.Client/Access/Systems/JobStatusSystem.Starlight.cs new file mode 100644 index 000000000000..6d5de870aadc --- /dev/null +++ b/Content.Client/Access/Systems/JobStatusSystem.Starlight.cs @@ -0,0 +1,28 @@ +using Content.Shared.Access.Systems; +using Content.Shared.Medical.SuitSensor; +using Content.Shared.Medical.SuitSensors; +using Content.Shared.Silicons.StationAi; + +namespace Content.Client.Access.Systems; + +public sealed partial class JobStatusSystem : SharedJobStatusSystem +{ + // Show job icons if entity is in camera view (only relevant for AI viewers) OR they have active suit sensors. + private bool CanSeeJobStatus(EntityUid uid) + { + if (_player.LocalEntity is not { } localEnt + || !HasComp(localEnt) + || !_vision.IsOutsideCameraViewCached(uid)) + { + return true; + } + + foreach (var sensor in EntityQuery(true)) + { + if (sensor.User == uid && sensor.Mode == SuitSensorMode.SensorCords) + return true; + } + + return false; + } +} diff --git a/Content.Client/Access/Systems/JobStatusSystem.cs b/Content.Client/Access/Systems/JobStatusSystem.cs new file mode 100644 index 000000000000..616a0b557dbc --- /dev/null +++ b/Content.Client/Access/Systems/JobStatusSystem.cs @@ -0,0 +1,45 @@ +using Content.Client.Overlays; +using Content.Shared.Access.Systems; +using Content.Shared.Silicons.StationAi; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Client.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client.Access.Systems; + +public sealed partial class JobStatusSystem : SharedJobStatusSystem +{ + [Dependency] private ShowJobIconsSystem _showJobIcons = default!; + [Dependency] private ShowCrewIconsSystem _showCrewIcons = default!; + [Dependency] private IPrototypeManager _prototype = default!; + [Dependency] private StationAiVisionSystem _vision = default!; + [Dependency] private IPlayerManager _player = default!; + + private static readonly ProtoId CrewBorderIcon = "CrewBorderIcon"; + private static readonly ProtoId CrewUncertainBorderIcon = "CrewUncertainBorderIcon"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + } + + // show the status icons if the player has the correponding HUDs + private void OnGetStatusIconsEvent(Entity ent, ref GetStatusIconsEvent ev) + { + var canSeeJobStatus = CanSeeJobStatus(ent); // Starlight + + if (_showJobIcons.IsActive && canSeeJobStatus && ent.Comp.JobStatusIcon != null) // Starlight + ev.StatusIcons.Add(_prototype.Index(ent.Comp.JobStatusIcon)); + + if (_showCrewIcons.IsActive && canSeeJobStatus) // Starlight + { + if (_showCrewIcons.UncertainCrewBorder) + ev.StatusIcons.Add(_prototype.Index(CrewUncertainBorderIcon)); + else if (ent.Comp.IsCrew) + ev.StatusIcons.Add(_prototype.Index(CrewBorderIcon)); + } + } +} diff --git a/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs b/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs index 4b0cc8d3bd0f..0c812b547fb7 100644 --- a/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs +++ b/Content.Client/Atmos/EntitySystems/AtmosPipeAppearanceSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Client.SubFloor; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; @@ -30,7 +31,11 @@ private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentI var numberOfPipeLayers = GetNumberOfPipeLayers(uid, out _); // Starlight START - _sprite.LayerMapTryGet((uid, sprite), PipeVisualLayers.Pipe, out var pipeIndex, false); + // Prefer inserting the generated connections directly after the main pipe layer. If a prototype does not + // have one, fall back to appending them like LayerMapReserve would instead of using an invalid layer index. + var insertionIndex = sprite.AllLayers.Count(); + if (_sprite.LayerMapTryGet((uid, sprite), PipeVisualLayers.Pipe, out var pipeIndex, false)) + insertionIndex = pipeIndex + 1; // Starlight END foreach (var layerKey in Enum.GetValues()) @@ -40,10 +45,10 @@ private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentI var layerName = layerKey.ToString() + i.ToString(); // Starlight START - // The generated layer should go directly after the main pipe layer, not at the end, hence the pipeIndex+1. + // The generated layer should go directly after the main pipe layer when it exists, not at the end. if (!_sprite.LayerMapTryGet((uid, sprite), layerName, out var layer, false)) { - layer = pipeIndex + 1; + layer = insertionIndex; _sprite.AddBlankLayer((uid, sprite), layer); _sprite.LayerMapSet((uid, sprite), layerName, layer); } diff --git a/Content.Client/Lobby/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs index 78e93c8db8f9..6eada163fc1f 100644 --- a/Content.Client/Lobby/ClientPreferencesManager.cs +++ b/Content.Client/Lobby/ClientPreferencesManager.cs @@ -58,7 +58,7 @@ public void SetCharacterEnable(int slot, bool enable = true) if (characterProfile is not HumanoidCharacterProfile profile) return; - var characters = new Dictionary(Preferences.Characters) + var characters = new Dictionary(Preferences.Characters) { [slot] = new HumanoidCharacterProfile(profile) {Enabled = enable}, }; @@ -72,11 +72,11 @@ public void SetCharacterEnable(int slot, bool enable = true) _netManager.ClientSendMessage(msg); } - public void UpdateCharacter(ICharacterProfile profile, int slot) + public void UpdateCharacter(HumanoidCharacterProfile profile, int slot) { var collection = IoCManager.Instance!; profile.EnsureValid(_playerManager.LocalSession!, collection); - var characters = new Dictionary(Preferences.Characters) {[slot] = profile}; + var characters = new Dictionary(Preferences.Characters) {[slot] = profile}; Preferences = new PlayerPreferences(characters, Preferences.AdminOOCColor, Preferences.ConstructionFavorites, Preferences.JobPriorities); var msg = new MsgUpdateCharacter { @@ -86,9 +86,9 @@ public void UpdateCharacter(ICharacterProfile profile, int slot) _netManager.ClientSendMessage(msg); } - public void CreateCharacter(ICharacterProfile profile) + public void CreateCharacter(HumanoidCharacterProfile profile) { - var characters = new Dictionary(Preferences.Characters); + var characters = new Dictionary(Preferences.Characters); var lowest = Enumerable.Range(0, Settings.MaxCharacterSlots) .Except(characters.Keys) .FirstOrNull(); @@ -105,7 +105,7 @@ public void CreateCharacter(ICharacterProfile profile) UpdateCharacter(profile, l); } - public void DeleteCharacter(ICharacterProfile profile) + public void DeleteCharacter(HumanoidCharacterProfile profile) { DeleteCharacter(Preferences.IndexOfCharacter(profile)); } diff --git a/Content.Client/Lobby/IClientPreferencesManager.cs b/Content.Client/Lobby/IClientPreferencesManager.cs index 17b2269d529c..0780af50d744 100644 --- a/Content.Client/Lobby/IClientPreferencesManager.cs +++ b/Content.Client/Lobby/IClientPreferencesManager.cs @@ -15,9 +15,9 @@ public interface IClientPreferencesManager PlayerPreferences? Preferences { get; } void Initialize(); void SetCharacterEnable(int slot, bool enable); - void UpdateCharacter(ICharacterProfile profile, int slot); - void CreateCharacter(ICharacterProfile profile); - void DeleteCharacter(ICharacterProfile profile); + void UpdateCharacter(HumanoidCharacterProfile profile, int slot); + void CreateCharacter(HumanoidCharacterProfile profile); + void DeleteCharacter(HumanoidCharacterProfile profile); void DeleteCharacter(int slot); void UpdateConstructionFavorites(List> favorites); void UpdateJobPriorities(Dictionary, JobPriority> jobPriorities); diff --git a/Content.Client/Lobby/UI/ProfileEditorControls/ProfilePreviewSpriteView.cs b/Content.Client/Lobby/UI/ProfileEditorControls/ProfilePreviewSpriteView.cs index 0117149988fb..bec76ccc6250 100644 --- a/Content.Client/Lobby/UI/ProfileEditorControls/ProfilePreviewSpriteView.cs +++ b/Content.Client/Lobby/UI/ProfileEditorControls/ProfilePreviewSpriteView.cs @@ -74,19 +74,12 @@ public void Initialize(IClientPreferencesManager prefMan, /// If false, render the dummy without clothes /// Starlight: Optional antag prototype override to preview /// Throws if something other than is passed in - public void LoadPreview(ICharacterProfile profile, JobPrototype? jobOverride = null, bool showClothes = true, ProtoId? antagOverride = null) // Starlight edit: Antag Loadouts + public void LoadPreview(HumanoidCharacterProfile profile, JobPrototype? jobOverride = null, bool showClothes = true, ProtoId? antagOverride = null) // Starlight edit: Antag Loadouts { EntMan.DeleteEntity(PreviewDummy); PreviewDummy = EntityUid.Invalid; - switch (profile) - { - case HumanoidCharacterProfile humanoid: - LoadHumanoidEntity(humanoid, jobOverride, showClothes, antagOverride); // Starlight edit: Antag Loadouts - break; - default: - throw new ArgumentException("Only humanoid profiles are implemented in ProfilePreviewSpriteView"); - } + LoadHumanoidEntity(profile, jobOverride, showClothes, antagOverride); // Starlight edit: Antag Loadouts FullDescription = ConstructFullDescription(); @@ -101,16 +94,9 @@ public void LoadPreview(ICharacterProfile profile, JobPrototype? jobOverride = n /// /// /// - public void ReloadProfilePreview(ICharacterProfile profile) + public void ReloadProfilePreview(HumanoidCharacterProfile profile) { - switch (profile) - { - case HumanoidCharacterProfile humanoid: - ReloadHumanoidEntity(humanoid); - break; - default: - throw new ArgumentException("Only humanoid profiles are implemented in ProfilePreviewSpriteView"); - } + ReloadHumanoidEntity(profile); } private string ConstructFullDescription() diff --git a/Content.Client/Overlays/EquipmentHudSystem.cs b/Content.Client/Overlays/EquipmentHudSystem.cs index fa93ce2c9e34..0502c674048b 100644 --- a/Content.Client/Overlays/EquipmentHudSystem.cs +++ b/Content.Client/Overlays/EquipmentHudSystem.cs @@ -15,7 +15,7 @@ public abstract partial class EquipmentHudSystem : EntitySystem where T : ICo [Dependency] private IPlayerManager _player = default!; [ViewVariables] - protected bool IsActive; + public bool IsActive { get; private set; } protected virtual SlotFlags TargetSlots => ~SlotFlags.POCKET; public override void Initialize() diff --git a/Content.Client/Overlays/ShowCrewIconsSystem.cs b/Content.Client/Overlays/ShowCrewIconsSystem.cs new file mode 100644 index 000000000000..a03c0a29bf2b --- /dev/null +++ b/Content.Client/Overlays/ShowCrewIconsSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Overlays; + +namespace Content.Client.Overlays; + +// The GetStatusIconsEvent subscription is handled in JobStatusSystem +public sealed class ShowCrewIconsSystem : EquipmentHudSystem +{ + public bool UncertainCrewBorder = false; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleState); + } + + protected override void UpdateInternal(RefreshEquipmentHudEvent component) + { + base.UpdateInternal(component); + + UncertainCrewBorder = false; + foreach (var comp in component.Components) + { + if (comp.UncertainCrewBorder) + UncertainCrewBorder = true; + } + } + + private void OnHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + RefreshOverlay(); + } +} diff --git a/Content.Client/Overlays/ShowJobIconsSystem.cs b/Content.Client/Overlays/ShowJobIconsSystem.cs index 992f1455cc5c..e9669c71b234 100644 --- a/Content.Client/Overlays/ShowJobIconsSystem.cs +++ b/Content.Client/Overlays/ShowJobIconsSystem.cs @@ -1,98 +1,6 @@ -using Content.Shared._Starlight.StatusIcon; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; using Content.Shared.Overlays; -using Content.Shared.PDA; -using Content.Shared.StatusIcon; -using Content.Shared.StatusIcon.Components; -using Robust.Shared.Prototypes; -using Robust.Client.Player; -using Content.Shared.Silicons.StationAi; -using Content.Shared.Medical.SuitSensors; -using Content.Shared.Medical.SuitSensor; namespace Content.Client.Overlays; -public sealed partial class ShowJobIconsSystem : EquipmentHudSystem -{ - [Dependency] private IPrototypeManager _prototype = default!; - [Dependency] private AccessReaderSystem _accessReader = default!; - - #region Starlight - [Dependency] private StationAiVisionSystem _vision = default!; - [Dependency] private IPlayerManager _player = default!; - #endregion - - private static readonly ProtoId JobIconForNoId = "JobIconNoId"; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetStatusIconsEvent); - } - - private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent ev) - { - if (!IsActive) - return; - - var iconId = JobIconForNoId; - - // Starlight Start - if (TryComp(uid, out var fixedIcon) && _prototype.Resolve(fixedIcon.Job, out var job)) - { - iconId = job.Icon; - } - else if (_accessReader.FindAccessItemsInventory(uid, out var items)) - // Starlight End - { - foreach (var item in items) - { - // ID Card - if (TryComp(item, out var id)) - { - iconId = id.JobIcon; - break; - } - - // PDA - if (TryComp(item, out var pda) - && pda.ContainedId != null - && TryComp(pda.ContainedId, out id)) - { - iconId = id.JobIcon; - break; - } - } - } - - // Starlight - start - // Show job icons if entity is in camera view (only relevant for AI viewers) OR they have active suit sensors. - - // First, determine if the local viewer is an AI-style viewer. Only then consult the AI vision system. - if (_player.LocalEntity is EntityUid localEnt - && TryComp(localEnt, out StationAiOverlayComponent? _) - && _vision.IsOutsideCameraViewCached(uid)) - { - var suitSensorsActive = false; - // Iterate all suit sensors and check if any are assigned to this user and active. - foreach (var sensor in EntityQuery(true)) - { - if (sensor.User == uid && sensor.Mode == SuitSensorMode.SensorCords) - { - suitSensorsActive = true; - break; - } - } - - if(!suitSensorsActive) return; - } - // Starlight - end - - if (_prototype.Resolve(iconId, out var iconPrototype)) - ev.StatusIcons.Add(iconPrototype); - else - Log.Error($"Invalid job icon prototype: {iconPrototype}"); - } -} +// The GetStatusIconsEvent subscription is handled in JobStatusSystem +public sealed class ShowJobIconsSystem : EquipmentHudSystem; diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index f6f1c71a0287..8ec5876866be 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -101,7 +101,7 @@ protected override void Draw(in OverlayDrawArgs args) countL++; } yOffset = (bounds.Height + sprite.Offset.Y) / 2f - (float)(accOffsetL - proto.Offset) / EyeManager.PixelsPerMeter; - xOffset = -(bounds.Width + sprite.Offset.X) / 2f; + xOffset = -(bounds.Width + sprite.Offset.X) / 2f + (float)proto.OffsetHorizontal / EyeManager.PixelsPerMeter; } else @@ -114,7 +114,7 @@ protected override void Draw(in OverlayDrawArgs args) countR++; } yOffset = (bounds.Height + sprite.Offset.Y) / 2f - (float)(accOffsetR - proto.Offset) / EyeManager.PixelsPerMeter; - xOffset = (bounds.Width + sprite.Offset.X) / 2f - (float)texture.Width / EyeManager.PixelsPerMeter; + xOffset = (bounds.Width + sprite.Offset.X) / 2f - (float)(texture.Width - proto.OffsetHorizontal) / EyeManager.PixelsPerMeter; } diff --git a/Content.Packaging/ClientPackaging.cs b/Content.Packaging/ClientPackaging.cs index 21215d3bcbfc..f17ebf5d20fb 100644 --- a/Content.Packaging/ClientPackaging.cs +++ b/Content.Packaging/ClientPackaging.cs @@ -13,13 +13,13 @@ public static class ClientPackaging /// /// Be advised this can be called from server packaging during a HybridACZ build. /// - public static async Task PackageClient(bool skipBuild, string configuration, IPackageLogger logger) + public static async Task PackageClient(bool skipBuild, bool logBuild, string configuration, IPackageLogger logger) { logger.Info("Building client..."); if (!skipBuild) { - await ProcessHelpers.RunCheck(new ProcessStartInfo + var startInfo = new ProcessStartInfo { FileName = "dotnet", ArgumentList = @@ -33,7 +33,15 @@ await ProcessHelpers.RunCheck(new ProcessStartInfo "/p:FullRelease=true", "/m" } - }); + }; + + if (logBuild) + { + startInfo.ArgumentList.Add($"/bl:{Path.Combine("release", "client.binlog")}"); + startInfo.ArgumentList.Add("/p:ReportAnalyzer=true"); + } + + await ProcessHelpers.RunCheck(startInfo); } logger.Info("Packaging client..."); diff --git a/Content.Packaging/CommandLineArgs.cs b/Content.Packaging/CommandLineArgs.cs index 23f661921e02..0f273b096e86 100644 --- a/Content.Packaging/CommandLineArgs.cs +++ b/Content.Packaging/CommandLineArgs.cs @@ -36,6 +36,11 @@ public sealed class CommandLineArgs /// public string Configuration { get; set; } + /// + /// Log builds with MSBuild binlog. Logs get saved to release/ + /// + public bool LogBuild { get; set; } + // CommandLineArgs, 3rd of her name. public static bool TryParse(IReadOnlyList args, [NotNullWhen(true)] out CommandLineArgs? parsed) { @@ -44,6 +49,7 @@ public static bool TryParse(IReadOnlyList args, [NotNullWhen(true)] out var skipBuild = false; var wipeRelease = true; var hybridAcz = false; + var logBuild = false; var configuration = "Release"; List? platforms = null; @@ -84,6 +90,10 @@ public static bool TryParse(IReadOnlyList args, [NotNullWhen(true)] out { hybridAcz = true; } + else if (arg == "--log-build") + { + logBuild = true; + } else if (arg == "--platform") { if (!enumerator.MoveNext()) @@ -122,7 +132,7 @@ public static bool TryParse(IReadOnlyList args, [NotNullWhen(true)] out return false; } - parsed = new CommandLineArgs(client.Value, skipBuild, wipeRelease, hybridAcz, platforms, configuration); + parsed = new CommandLineArgs(client.Value, skipBuild, wipeRelease, hybridAcz, logBuild, platforms, configuration); return true; } @@ -132,11 +142,12 @@ private static void PrintHelp() Usage: Content.Packaging [client/server] [options] Options: - --skip-build Should we skip building the project and use what's already there. - --no-wipe-release Don't wipe the release folder before creating files. - --hybrid-acz Use HybridACZ for server builds. - --platform Platform for server builds. Default will output several x64 targets. - --configuration Configuration to use for building the server (Release, Debug, Tools). Default is Release. + --skip-build Should we skip building the project and use what's already there. + --no-wipe-release Don't wipe the release folder before creating files. + --hybrid-acz Use HybridACZ for server builds. + --platform Platform for server builds. Default will output several x64 targets. + --configuration Configuration to use for building the server (Release, Debug, Tools). Default is Release. + --log-build Log builds with MSBuild binlog. Logs get saved to release/ "); } @@ -145,6 +156,7 @@ private CommandLineArgs( bool skipBuild, bool wipeRelease, bool hybridAcz, + bool logBuild, List? platforms, string configuration) { @@ -154,5 +166,6 @@ private CommandLineArgs( HybridAcz = hybridAcz; Platforms = platforms; Configuration = configuration; + LogBuild = logBuild; } } diff --git a/Content.Packaging/Program.cs b/Content.Packaging/Program.cs index 9457e9dacc9b..25fc550a2fdb 100644 --- a/Content.Packaging/Program.cs +++ b/Content.Packaging/Program.cs @@ -22,11 +22,11 @@ if (parsed.Client) { - await ClientPackaging.PackageClient(parsed.SkipBuild, parsed.Configuration, logger); + await ClientPackaging.PackageClient(parsed.SkipBuild, parsed.LogBuild, parsed.Configuration, logger); } else { - await ServerPackaging.PackageServer(parsed.SkipBuild, parsed.HybridAcz, logger, parsed.Configuration, parsed.Platforms); + await ServerPackaging.PackageServer(parsed.SkipBuild, parsed.HybridAcz, parsed.LogBuild, logger, parsed.Configuration, parsed.Platforms); } void WipeBin() diff --git a/Content.Packaging/ServerPackaging.cs b/Content.Packaging/ServerPackaging.cs index 4c7e45e80542..16f23c3b8542 100644 --- a/Content.Packaging/ServerPackaging.cs +++ b/Content.Packaging/ServerPackaging.cs @@ -61,7 +61,7 @@ public static class ServerPackaging "zh-Hant" }; - public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageLogger logger, string configuration, List? platforms = null) + public static async Task PackageServer(bool skipBuild, bool hybridAcz, bool logBuild, IPackageLogger logger, string configuration, List? platforms = null) { if (platforms == null) { @@ -74,7 +74,7 @@ public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageL // Rather than hosting the client ZIP on the watchdog or on a separate server, // Hybrid ACZ uses the ACZ hosting functionality to host it as part of the status host, // which means that features such as automatic UPnP forwarding still work properly. - await ClientPackaging.PackageClient(skipBuild, configuration, logger); + await ClientPackaging.PackageClient(skipBuild, logBuild, configuration, logger); } // Good variable naming right here. @@ -83,17 +83,22 @@ public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageL if (!platforms.Contains(platform.Rid)) continue; - await BuildPlatform(platform, skipBuild, hybridAcz, configuration, logger); + await BuildPlatform(platform, skipBuild, hybridAcz, logBuild, configuration, logger); } } - private static async Task BuildPlatform(PlatformReg platform, bool skipBuild, bool hybridAcz, string configuration, IPackageLogger logger) + private static async Task BuildPlatform(PlatformReg platform, + bool skipBuild, + bool hybridAcz, + bool logBuild, + string configuration, + IPackageLogger logger) { logger.Info($"Building project for {platform.TargetOs}..."); if (!skipBuild) { - await ProcessHelpers.RunCheck(new ProcessStartInfo + var startInfo = new ProcessStartInfo { FileName = "dotnet", ArgumentList = @@ -108,7 +113,15 @@ await ProcessHelpers.RunCheck(new ProcessStartInfo "/p:FullRelease=true", "/m" } - }); + }; + + if (logBuild) + { + startInfo.ArgumentList.Add($"/bl:{Path.Combine("release", $"server-{platform.Rid}.binlog")}"); + startInfo.ArgumentList.Add("/p:ReportAnalyzer=true"); + } + + await ProcessHelpers.RunCheck(startInfo); await PublishClientServer(platform.Rid, platform.TargetOs, configuration); } diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index a3ed4ef1c67e..0a52293ffd2c 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -28,6 +28,7 @@ public sealed partial class AgentIDCardSystem : SharedAgentIdCardSystem [Dependency] private ChameleonClothingSystem _chameleon = default!; [Dependency] private ChameleonControllerSystem _chamController = default!; [Dependency] private LockSystem _lock = default!; + [Dependency] private SharedJobStatusSystem _jobStatus = default!; [Dependency] private SharedNanoChatSystem _nanoChat = default!; // CD public override void Initialize() @@ -202,6 +203,8 @@ private void OnJobIconChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDC if (TryFindJobProtoFromIcon(jobIcon, out var job)) _cardSystem.TryChangeJobDepartment(uid, job, idCard); + + _jobStatus.UpdateStatus(Transform(uid).ParentUid); } private bool TryFindJobProtoFromIcon(JobIconPrototype jobIcon, [NotNullWhen(true)] out JobPrototype? job) diff --git a/Content.Server/Access/Systems/JobStatusSystem.cs b/Content.Server/Access/Systems/JobStatusSystem.cs new file mode 100644 index 000000000000..d6d38fbe5396 --- /dev/null +++ b/Content.Server/Access/Systems/JobStatusSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Access.Systems; + +namespace Content.Server.Access.Systems; + +public sealed class JobStatusSystem : SharedJobStatusSystem; diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index 9a8ce1d36e6a..0913934d48a4 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -14,6 +14,8 @@ using Content.Shared.IdentityManagement; using Content.Shared.Security.Components; using System.Linq; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; using Content.Shared.Roles.Jobs; using Robust.Shared.Log; @@ -29,6 +31,7 @@ namespace Content.Server.CriminalRecords.Systems; public sealed partial class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleSystem { [Dependency] private AccessReaderSystem _access = default!; + [Dependency] private ISharedAdminLogManager _adminLogger = default!; [Dependency] private CriminalRecordsSystem _criminalRecords = default!; [Dependency] private PopupSystem _popup = default!; [Dependency] private RadioSystem _radio = default!; @@ -200,8 +203,12 @@ private void OnChangeStatus(Entity ent, ref Cri // this is impossible _ => "not-wanted" }; - _radio.SendRadioMessage(ent, Loc.GetString($"criminal-records-console-{statusString}", args), - ent.Comp.SecurityChannel, ent); + _radio.SendRadioMessage(ent, + Loc.GetString($"criminal-records-console-{statusString}", args), + ent.Comp.SecurityChannel, + ent); + + _adminLogger.Add(LogType.Identity, LogImpact.Low, $"{ToPrettyString(mob.Value):name} changed criminal status for {name} to \"{statusString}\""); UpdateUserInterface(ent); // Cosmatic Drift Record System-start diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index f07027d99212..6adae4ecd8ac 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -82,7 +82,7 @@ public ServerDbBase(ISawmill opsLog) : 0; // 🌟Starlight🌟 end - var profiles = new Dictionary(maxSlot); + var profiles = new Dictionary(maxSlot); foreach (var profile in prefs.Profiles) { profiles[profile.Slot] = ConvertProfiles(profile); @@ -97,23 +97,17 @@ public ServerDbBase(ISawmill opsLog) return new PlayerPreferences(profiles, Color.FromHex(prefs.AdminOOCColor), constructionFavorites, jobPriorities); } - public async Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot) + public async Task SaveCharacterSlotAsync(NetUserId userId, HumanoidCharacterProfile? humanoid, int slot) { await using var db = await GetDb(); - if (profile is null) + if (humanoid is null) { await DeleteCharacterSlot(db.DbContext, userId, slot); await db.DbContext.SaveChangesAsync(); return; } - if (profile is not HumanoidCharacterProfile humanoid) - { - // TODO: Handle other ICharacterProfile implementations properly - throw new NotImplementedException(); - } - var oldProfile = db.DbContext.Profile .Include(p => p.StarLightProfile) // Starlight .Include(p => p.Preference) @@ -185,7 +179,7 @@ private static async Task DeleteCharacterSlot(ServerDbContext db, NetUserId user db.Profile.Remove(profile); } - public async Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile) + public async Task InitPrefsAsync(NetUserId userId, HumanoidCharacterProfile defaultProfile) { await using var db = await GetDb(); @@ -213,7 +207,7 @@ public async Task InitPrefsAsync(NetUserId userId, ICharacter await db.DbContext.SaveChangesAsync(); return new PlayerPreferences( - new[] { new KeyValuePair(0, defaultProfile) }, + new[] { new KeyValuePair(0, defaultProfile) }, Color.FromHex(prefs.AdminOOCColor), [], priorities @@ -396,7 +390,7 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile) private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int slot, Profile? profile = null) { profile ??= new Profile(); - var appearance = (HumanoidCharacterAppearance)humanoid.CharacterAppearance; + var appearance =humanoid.Appearance; List markingStrings = new(); foreach (var marking in appearance.Markings) { diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index a7b292123df2..f606396cf93b 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -34,10 +34,10 @@ public interface IServerDbManager #region Preferences Task InitPrefsAsync( NetUserId userId, - ICharacterProfile defaultProfile, + HumanoidCharacterProfile defaultProfile, CancellationToken cancel); - Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot); + Task SaveCharacterSlotAsync(NetUserId userId, HumanoidCharacterProfile? profile, int slot); Task SaveJobPrioritiesAsync(NetUserId userId, Dictionary, JobPriority> newJobPriorities); @@ -466,14 +466,14 @@ public void Shutdown() public Task InitPrefsAsync( NetUserId userId, - ICharacterProfile defaultProfile, + HumanoidCharacterProfile defaultProfile, CancellationToken cancel) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.InitPrefsAsync(userId, defaultProfile)); } - public Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot) + public Task SaveCharacterSlotAsync(NetUserId userId, HumanoidCharacterProfile? profile, int slot) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.SaveCharacterSlotAsync(userId, profile, slot)); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 5f83288009d1..ab35f729dbc8 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -536,6 +536,9 @@ public void DamageFloorTile(TileRef tileRef, break; tileDef = newDef; + + if (newDef.Indestructible) + break; } if (tileDef.TileId == tileRef.Tile.TypeId) diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs index 3710190c2037..2c09f50c8643 100644 --- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs +++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs @@ -29,6 +29,12 @@ public sealed partial class HealthAnalyzerComponent : Component [DataField] public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1); + /// + /// If the last state of the health analyzer was active (e.g. they are in range of the patient). + /// + [DataField] + public bool IsAnalyzerActive = false; + /// /// How long it takes to scan someone. /// diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index c0f924c72bef..16f093829166 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -97,11 +97,12 @@ public override void Update(float frameTime) var patientCoordinates = Transform(patient).Coordinates; if (component.MaxScanRange != null && !_transformSystem.InRange(patientCoordinates, transform.Coordinates, component.MaxScanRange.Value)) { - //Range too far, disable updates - StopAnalyzingEntity((uid, component), patient); + //Range too far, disable updates until they are back in range + PauseAnalyzingEntity((uid, component), patient); continue; } + component.IsAnalyzerActive = true; UpdateScannedUser(uid, patient, true); } } @@ -244,6 +245,21 @@ public void StopAnalyzingEntity(Entity healthAnalyzer, UpdateScannedUser(healthAnalyzer, target, false); } + + /// + /// If the scanner is active, sends one last update and sets it to inactive. + /// + /// The health analyzer that's receiving the updates + /// The entity to analyze + private void PauseAnalyzingEntity(Entity healthAnalyzer, EntityUid target) + { + if (!healthAnalyzer.Comp.IsAnalyzerActive) + return; + + UpdateScannedUser(healthAnalyzer, target, false); + healthAnalyzer.Comp.IsAnalyzerActive = false; + } + /// /// Send an update for the target to the healthAnalyzer /// diff --git a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs index b7a7222b770f..c5493951f909 100644 --- a/Content.Server/Preferences/Managers/IServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/IServerPreferencesManager.cs @@ -23,7 +23,7 @@ public interface IServerPreferencesManager PlayerPreferences? GetPreferencesOrNull(NetUserId? userId); bool HavePreferencesLoaded(ICommonSession session); - Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile); + Task SetProfile(NetUserId userId, int slot, HumanoidCharacterProfile profile); Task SetConstructionFavorites(NetUserId userId, List> favorites); /// diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index ce3506652c33..9603348c28bc 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -64,7 +64,7 @@ private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) await SetProfile(userId, message.Slot, message.Profile); } - public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile) + public async Task SetProfile(NetUserId userId, int slot, HumanoidCharacterProfile profile) { if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded) { @@ -80,7 +80,7 @@ public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profi profile.EnsureValid(session, _dependencies); - var profiles = new Dictionary(curPrefs.Characters) + var profiles = new Dictionary(curPrefs.Characters) { [slot] = profile }; @@ -154,7 +154,7 @@ public async Task DeleteProfile(NetUserId userId, int slot) var curPrefs = prefsData.Prefs!; var session = _playerManager.GetSessionById(userId); - var arr = new Dictionary(curPrefs.Characters); + var arr = new Dictionary(curPrefs.Characters); arr.Remove(slot); prefsData.Prefs = new PlayerPreferences(arr, curPrefs.AdminOOCColor, curPrefs.ConstructionFavorites, curPrefs.JobPriorities); @@ -194,7 +194,7 @@ private async void HandleSetCharacterEnableMessage(MsgSetCharacterEnable message return; profile.Enabled = val; - var profiles = new Dictionary(curPrefs.Characters) + var profiles = new Dictionary(curPrefs.Characters) { [slot] = new HumanoidCharacterProfile(profile), }; @@ -259,7 +259,7 @@ public async Task LoadData(ICommonSession session, CancellationToken cancel) { PrefsLoaded = true, Prefs = new PlayerPreferences( - new[] {new KeyValuePair(0, HumanoidCharacterProfile.Random())}, + new[] {new KeyValuePair(0, HumanoidCharacterProfile.Random())}, Color.Transparent, [], new Dictionary, JobPriority>{{ SharedGameTicker.FallbackOverflowJob, JobPriority.High }}), @@ -416,7 +416,7 @@ private PlayerPreferences SanitizePreferences(ICommonSession session, PlayerPref return new PlayerPreferences(prefs.Characters.Select(p => { - return new KeyValuePair(p.Key, p.Value.Validated(session, collection)); + return new KeyValuePair(p.Key, p.Value.Validated(session, collection)); }), prefs.AdminOOCColor, prefs.ConstructionFavorites, priorities); } diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 22d3053d7178..f9b3af26cde0 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Popups; //Starlight +using Content.Shared.Overlays; using Content.Shared.Radio.Components; using Content.Shared.Roles; using Content.Shared.Roles.Components; @@ -41,6 +42,8 @@ public sealed partial class SiliconLawSystem : SharedSiliconLawSystem [Dependency] private TagSystem _tag = default!; // Starlight [Dependency] private SharedPopupSystem _popup = default!; // Starlight + private static readonly ProtoId DefaultCrewLawset = "Crewsimov"; + /// public override void Initialize() { @@ -360,6 +363,11 @@ protected override void OnUpdaterInsert(Entity ent, && TryComp(ent.Comp.Core.Value, out var holder) && holder.Slot.ContainerSlot?.ContainedEntity is { } update) { + if (TryComp(update, out var crewIconComp)) + { + crewIconComp.UncertainCrewBorder = DefaultCrewLawset != provider.Laws; + Dirty(update, crewIconComp); + } SetLaws(lawset.Laws, update, provider.LawUploadSound); // Components on lawboards TODO remove components provided by the old board when it is removed. if (provider.Components != null) diff --git a/Content.Server/Species/Systems/NymphSystem.cs b/Content.Server/Species/Systems/NymphSystem.cs index 2edeff76da66..b0624dbf1daa 100644 --- a/Content.Server/Species/Systems/NymphSystem.cs +++ b/Content.Server/Species/Systems/NymphSystem.cs @@ -37,7 +37,7 @@ private void OnRemovedFromPart(EntityUid uid, NymphComponent comp, ref OrganRemo _zombie.ZombifyEntity(nymph); // Move the mind if there is one and it's supposed to be transferred - if (comp.TransferMind == true && _mindSystem.TryGetMind(args.OldBody, out var mindId, out var mind)) // Starlight Edit: Target -> OldBody + if (comp.TransferMind && _mindSystem.TryGetMind(uid, out var mindId, out var mind)) _mindSystem.TransferTo(mindId, nymph, mind: mind); // Delete the old organ diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index be6d44889088..2c2ecbdb7900 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -29,7 +29,7 @@ protected override void Started(EntityUid uid, BasicStationEventSchedulerCompone GameRuleStartedEvent args) { // A little starting variance so schedulers dont all proc at once. - component.TimeUntilNextEvent = RobustRandom.NextFloat(component.MinimumTimeUntilFirstEvent, component.MinimumTimeUntilFirstEvent + 120); + component.TimeUntilNextEvent = RobustRandom.NextFloat(component.MinimumTimeUntilFirstEvent, component.MinimumTimeUntilFirstEvent + component.MaximumSpanUntilFirstEvent); } protected override void Ended(EntityUid uid, BasicStationEventSchedulerComponent component, GameRuleComponent gameRule, @@ -132,14 +132,15 @@ public sealed class StationEventCommand : ToolshedCommand // sim an event curTime += TimeSpan.FromSeconds(compMinMax.Next(_random)); - var available = _stationEvent.AvailableEvents(false, playerCount, curTime); - if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, available, out var selectedEvents)) + if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, + out var selectedEvents, + currentTime: curTime, + playerCount: playerCount)) { continue; // doesnt break because maybe the time is preventing events being available. } - var ev = _stationEvent.FindEvent(selectedEvents); - if (ev == null) + if (_stationEvent.FindEvent(selectedEvents) is not { } ev) continue; occurrences[ev] += 1; @@ -161,15 +162,14 @@ public sealed class StationEventCommand : ToolshedCommand if (!eventScheduler.TryGetComponent(out var basicScheduler, _compFac)) yield break; - var available = _stationEvent.AvailableEvents(); - if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, available, out var events)) + if (!_stationEvent.TryListLimitedEvents(basicScheduler.ScheduledGameRules, out var events)) yield break; var totalWeight = events.Sum(x => x.Value.Weight); // Well this shit definitely isnt correct now, and I see no way to make it correct. // Its probably *fine* but it wont be accurate if the EntityTableSelector does any subsetting. foreach (var (proto, comp) in events) // The only solution I see is to do a simulation, and we already have that, so...! { - yield return (proto.ID, comp.Weight / totalWeight); + yield return (proto.ID, comp.Weight * (float)basicScheduler.ScheduledGameRules.Prob / totalWeight); } } @@ -187,8 +187,10 @@ public sealed class StationEventCommand : ToolshedCommand var timemins = time * 60; var theoryTime = TimeSpan.Zero + TimeSpan.FromSeconds(timemins); - var available = _stationEvent.AvailableEvents(false, playerCount, theoryTime); - if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, available, out var untimedEvents)) + if (!_stationEvent.TryListLimitedEvents(basicScheduler.ScheduledGameRules, + out var untimedEvents, + currentTime: theoryTime, + playerCount: playerCount)) yield break; var events = untimedEvents.Where(pair => pair.Value.EarliestStart <= timemins).ToList(); @@ -197,7 +199,7 @@ public sealed class StationEventCommand : ToolshedCommand foreach (var (proto, comp) in events) { - yield return (proto.ID, comp.Weight / totalWeight); + yield return (proto.ID, comp.Weight * (float)basicScheduler.ScheduledGameRules.Prob / totalWeight); } } @@ -213,15 +215,14 @@ public float Prob([CommandArgument] EntProtoId eventSchedulerProto, [CommandArgu if (!eventScheduler.TryGetComponent(out var basicScheduler, _compFac)) return 0f; - var available = _stationEvent.AvailableEvents(); - if (!_stationEvent.TryBuildLimitedEvents(basicScheduler.ScheduledGameRules, available, out var events)) + if (!_stationEvent.TryListLimitedEvents(basicScheduler.ScheduledGameRules, out var events)) return 0f; var totalWeight = events.Sum(x => x.Value.Weight); // same subsetting issue as lsprob. var weight = 0f; if (events.TryFirstOrNull(p => p.Key.ID == eventId, out var pair)) { - weight = pair.Value.Value.Weight; + weight = pair.Value.Value.Weight * (float)basicScheduler.ScheduledGameRules.Prob; } return weight / totalWeight; diff --git a/Content.Server/StationEvents/Components/BasicStationEventSchedulerComponent.cs b/Content.Server/StationEvents/Components/BasicStationEventSchedulerComponent.cs index b777831856b2..5490ae514b61 100644 --- a/Content.Server/StationEvents/Components/BasicStationEventSchedulerComponent.cs +++ b/Content.Server/StationEvents/Components/BasicStationEventSchedulerComponent.cs @@ -13,6 +13,12 @@ public sealed partial class BasicStationEventSchedulerComponent : Component [DataField] public float MinimumTimeUntilFirstEvent = 200; + /// + /// How much additional time it may take for a GameRule to first start. + /// + [DataField] + public float MaximumSpanUntilFirstEvent = 120; + /// /// The minimum and maximum time between rule starts in seconds. /// diff --git a/Content.Server/StationEvents/EventManagerSystem.cs b/Content.Server/StationEvents/EventManagerSystem.cs index 0d46719d37ad..50f22223159c 100644 --- a/Content.Server/StationEvents/EventManagerSystem.cs +++ b/Content.Server/StationEvents/EventManagerSystem.cs @@ -25,29 +25,21 @@ public sealed partial class EventManagerSystem : EntitySystem public bool EventsEnabled { get; private set; } private void SetEnabled(bool value) => EventsEnabled = value; + public Dictionary? AllEventCache; + public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnPrototypesReloaded); + Subs.CVar(_configurationManager, CCVars.EventsEnabled, SetEnabled, true); } - /// - /// Randomly runs a valid event. - /// - [Obsolete("use overload taking EnityTableSelector instead or risk unexpected results")] - public void RunRandomEvent() + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) { - var randomEvent = PickRandomEvent(); - - if (randomEvent == null) - { - var errStr = Loc.GetString("station-event-system-run-random-event-no-valid-events"); - Log.Error(errStr); - return; - } - - GameTicker.AddGameRule(randomEvent); + if (args.WasModified()) + AllEventCache = GetAllEvents(); } /// @@ -55,17 +47,16 @@ public void RunRandomEvent() /// public void RunRandomEvent(EntityTableSelector limitedEventsTable) { - var availableEvents = AvailableEvents(); // handles the player counts and individual event restrictions. - // Putting this here only makes any sense in the context of the toolshed commands in BasicStationEventScheduler. Kill me. - - if (!TryBuildLimitedEvents(limitedEventsTable, availableEvents, out var limitedEvents)) + if (!TryBuildLimitedEvents(limitedEventsTable, out var limitedEvents)) { Log.Warning("Provided event table could not build dict!"); return; } - var randomLimitedEvent = FindEvent(limitedEvents); // this picks the event, It might be better to use the GetSpawns to do it, but that will be a major rebalancing fuck. - if (randomLimitedEvent == null) + // This picks the event. Arguably we should be doing this with GetSpawns but that would be a massive amount of YAML slop. + // Or you'd need a new table prototype which inherits from EntityTables with its own logic for events. + // It's a ton of effort that only results in Events being able to use GroupSelectors so not worth it unless you're insane. + if (FindEvent(limitedEvents) is not { } randomLimitedEvent) { Log.Warning("The selected random event is null!"); return; @@ -80,27 +71,51 @@ public void RunRandomEvent(EntityTableSelector limitedEventsTable) GameTicker.AddGameRule(randomLimitedEvent); } + /// + public bool TryListLimitedEvents( + EntityTableSelector limitedEventsTable, + out Dictionary limitedEvents, + TimeSpan? currentTime = null, + int? playerCount = null) + { + var selectedEvents = _entityTable.ListSpawns(limitedEventsTable); + + return TryBuildLimitedEvents(selectedEvents, out limitedEvents, currentTime, playerCount); + } + + /// + public bool TryBuildLimitedEvents( + EntityTableSelector limitedEventsTable, + out Dictionary limitedEvents, + TimeSpan? currentTime = null, + int? playerCount = null) + { + var selectedEvents = _entityTable.GetSpawns(limitedEventsTable); + + return TryBuildLimitedEvents(selectedEvents, out limitedEvents, currentTime, playerCount); + } + /// - /// Returns true if the provided EntityTableSelector gives at least one prototype with a StationEvent comp. + /// Builds a dictionary of valid event prototypes from a list of . + /// Dictionary output consists of the valid prototype as the key, and the as the value. /// + /// List of events we're selecting from. + /// Dictionary we're outputting. + /// Optional override for station time. + /// Optional override for playerCount. + /// Returns true if the provided EntProtoId list has at least one prototype with a StationEventComp that can successfully run! public bool TryBuildLimitedEvents( - EntityTableSelector limitedEventsTable, - Dictionary availableEvents, - out Dictionary limitedEvents - ) + IEnumerable selectedEvents, + out Dictionary limitedEvents, + TimeSpan? currentTime = null, + int? playerCount = null) { limitedEvents = new Dictionary(); - if (availableEvents.Count == 0) - { - Log.Warning("No events were available to run!"); - return false; - } - - var selectedEvents = _entityTable.GetSpawns(limitedEventsTable); + playerCount ??= _playerManager.PlayerCount; - if (selectedEvents.Any() != true) // This is here so if you fuck up the table it wont die. - return false; + // playerCount does a lock so we'll just keep the variable here + currentTime ??= GameTicker.RoundDuration(); foreach (var eventid in selectedEvents) { @@ -122,7 +137,7 @@ out Dictionary limitedEvents if (!eventproto.TryGetComponent(out var stationEvent, EntityManager.ComponentFactory)) continue; - if (!availableEvents.ContainsKey(eventproto)) + if (!CanRun(eventproto, stationEvent, playerCount.Value, currentTime.Value)) continue; limitedEvents.Add(eventproto, stationEvent); @@ -186,16 +201,13 @@ out Dictionary limitedEvents /// Override for round time, if using this to simulate events rather than in an actual round. /// public Dictionary AvailableEvents( - bool ignoreEarliestStart = false, int? playerCountOverride = null, TimeSpan? currentTimeOverride = null) { var playerCount = playerCountOverride ?? _playerManager.PlayerCount; // playerCount does a lock so we'll just keep the variable here - var currentTime = currentTimeOverride ?? (!ignoreEarliestStart - ? GameTicker.RoundDuration() - : TimeSpan.Zero); + var currentTime = currentTimeOverride ?? GameTicker.RoundDuration(); var result = new Dictionary(); @@ -210,7 +222,19 @@ public Dictionary AvailableEvents( return result; } + /// + /// Returns all events prototypes which exist. Prioritizes the cache. + /// + /// All event prototypes, and their event component. public Dictionary AllEvents() + { + return AllEventCache ?? GetAllEvents(); + } + + /// + /// Gets all event prototypes that exist. Private because you should be using the cache! + /// + private Dictionary GetAllEvents() { var allEvents = new Dictionary(); foreach (var prototype in _prototype.EnumeratePrototypes()) diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index cffaf2928def..a705f914ec45 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -11,6 +11,8 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Implants.Components; using Content.Shared.Mind; +using Content.Shared.Mindshield.Components; +using Content.Shared.NPC.Systems; using Content.Shared.PDA.Ringer; using Content.Shared.Store; using Content.Shared.Store.Components; @@ -51,6 +53,7 @@ public sealed partial class StoreSystem [Dependency] private UserInterfaceSystem _ui = default!; [Dependency] private RevSupplyRiftSystem _revSupplyRift = default!; // Starlight [Dependency] private LanguageSystem _languageSystem = default!; //Starlight + [Dependency] private NpcFactionSystem _npcFaction = default!; private void InitializeUi() { @@ -315,9 +318,23 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi resolvedName = resolvedName.Substring(0, resolvedName.IndexOf(" (")); } + var logImpact = LogImpact.Low; + var logExtraInfo = ""; + if (component.ExpectedFaction?.Count > 0 && !_npcFaction.IsMemberOfAny(buyer, component.ExpectedFaction)) + { + logImpact = LogImpact.High; + logExtraInfo = ", but was not from an expected faction"; + + if (HasComp(buyer)) + { + logImpact = LogImpact.Extreme; + logExtraInfo += " while also possessing a mindshield"; + } + } + _admin.Add(LogType.StorePurchase, - LogImpact.Low, - $"{ToPrettyString(buyer):player} purchased listing \"{resolvedName}\" from {ToPrettyString(uid)}"); // Starlight + logImpact, + $"{ToPrettyString(buyer):player} purchased listing \"{resolvedName}\" from {ToPrettyString(uid)}{logExtraInfo}."); // Starlight listing.PurchaseAmount++; //track how many times something has been purchased _audio.PlayEntity(component.BuySuccessSound, msg.Actor, uid); //cha-ching! diff --git a/Content.Server/_Starlight/Medical/Body/Systems/RespiratorSystem.cs b/Content.Server/_Starlight/Medical/Body/Systems/RespiratorSystem.cs index 02dbfe6d0420..7d4aea78056d 100644 --- a/Content.Server/_Starlight/Medical/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/_Starlight/Medical/Body/Systems/RespiratorSystem.cs @@ -204,7 +204,7 @@ public bool IsBreathing(Entity ent) /// Returns true only if the air is not toxic, and it wouldn't suffocate. public bool CanMetabolizeInhaledAir(Entity ent) { - if (!Resolve(ent, ref ent.Comp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; // Get the gas at our location but don't actually remove it from the gas mixture. diff --git a/Content.Shared/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs index eaa6167853ef..9a285236cf73 100644 --- a/Content.Shared/Access/Components/IdCardComponent.cs +++ b/Content.Shared/Access/Components/IdCardComponent.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Access.Components; [RegisterComponent, NetworkedComponent] -[AutoGenerateComponentState] +[AutoGenerateComponentState(true)] [Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)] public sealed partial class IdCardComponent : Component { diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs index 5900d6702f1c..5d5d261bc88d 100644 --- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs +++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs @@ -25,6 +25,7 @@ public abstract partial class SharedIdCardSystem : EntitySystem [Dependency] private InventorySystem _inventorySystem = default!; [Dependency] private MetaDataSystem _metaSystem = default!; [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private SharedJobStatusSystem _jobStatus = default!; // CCVar. private int _maxNameLength; @@ -35,6 +36,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnHandleState); SubscribeLocalEvent(OnTryGetIdentityShortInfo); SubscribeLocalEvent(OnRename); @@ -77,6 +79,15 @@ private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent ev) ev.Handled = true; } + private void OnHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + // Try to update the job status icon of the player owning the ID, if any. + if (HasComp(Transform(ent).ParentUid)) + _jobStatus.UpdateStatus(Transform(Transform(ent).ParentUid).ParentUid); //ID is inside a PDA + else + _jobStatus.UpdateStatus(Transform(ent).ParentUid); //ID is held/directly in the ID slot + } + /// /// Attempt to find an ID card on an entity. This will look in the entity itself, in the entity's hands, and /// in the entity's inventory. diff --git a/Content.Shared/Access/Systems/SharedJobStatusSystem.Starlight.cs b/Content.Shared/Access/Systems/SharedJobStatusSystem.Starlight.cs new file mode 100644 index 000000000000..6c331198e231 --- /dev/null +++ b/Content.Shared/Access/Systems/SharedJobStatusSystem.Starlight.cs @@ -0,0 +1,11 @@ +using Content.Shared._Starlight.StatusIcon; + +namespace Content.Shared.Access.Systems; + +public abstract partial class SharedJobStatusSystem : EntitySystem +{ + private void OnFixedJobIconStartup(Entity ent, ref ComponentStartup args) + { + UpdateStatus(ent.Owner); + } +} diff --git a/Content.Shared/Access/Systems/SharedJobStatusSystem.cs b/Content.Shared/Access/Systems/SharedJobStatusSystem.cs new file mode 100644 index 000000000000..c269a3905859 --- /dev/null +++ b/Content.Shared/Access/Systems/SharedJobStatusSystem.cs @@ -0,0 +1,75 @@ +using Content.Shared._Starlight.StatusIcon; +using Content.Shared.Access.Components; +using Content.Shared.Hands; +using Content.Shared.Inventory.Events; +using Content.Shared.PDA; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Access.Systems; + +public abstract partial class SharedJobStatusSystem : EntitySystem +{ + [Dependency] private AccessReaderSystem _accessReader = default!; + [Dependency] private IPrototypeManager _prototype = default!; + + private static readonly ProtoId JobIconForNoId = "JobIconNoId"; + + public override void Initialize() + { + base.Initialize(); + + // if the mob picks up, drops or (un)equips a pda or Id card then update their crew status + SubscribeLocalEvent((uid, comp, _) => UpdateStatus((uid, comp))); + SubscribeLocalEvent((uid, comp, _) => UpdateStatus((uid, comp))); + SubscribeLocalEvent((uid, comp, _) => UpdateStatus((uid, comp))); + SubscribeLocalEvent((uid, comp, _) => UpdateStatus((uid, comp))); + SubscribeLocalEvent(OnFixedJobIconStartup); // Starlight + } + + /// + /// Updates this mob's job and crew status depending on their currently equipped or held pda or Id card. + /// + public void UpdateStatus(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + var iconId = JobIconForNoId; + + #region Starlight + // Entities such as K9s use a fixed job instead of an ID card. + if (TryComp(ent, out var fixedIcon) + && _prototype.Resolve(fixedIcon.Job, out var job)) + { + iconId = job.Icon; + } + #endregion + else if (_accessReader.FindAccessItemsInventory(ent.Owner, out var items)) + { + foreach (var item in items) + { + // ID Card + if (TryComp(item, out var id)) + { + iconId = id.JobIcon; + break; + } + + // PDA + if (TryComp(item, out var pda) + && pda.ContainedId != null + && TryComp(pda.ContainedId, out id)) + { + iconId = id.JobIcon; + break; + } + } + } + + ent.Comp.JobStatusIcon = iconId; + ent.Comp.IsCrew = _prototype.Index(iconId).IsCrewJob; + Dirty(ent); + } +} diff --git a/Content.Shared/EntityTable/EntitySelectors/EntityTableSelector.cs b/Content.Shared/EntityTable/EntitySelectors/EntityTableSelector.cs index e25993bd1d2e..09258e1bb47c 100644 --- a/Content.Shared/EntityTable/EntitySelectors/EntityTableSelector.cs +++ b/Content.Shared/EntityTable/EntitySelectors/EntityTableSelector.cs @@ -83,6 +83,21 @@ public bool CheckConditions(IEntityManager entMan, IPrototypeManager proto, Enti return success; } + /// + /// Gets the spawns in a given table, ignoring the requirements for the table. + /// This should only be used for debugging! + /// + public IEnumerable ListSpawns(System.Random rand, + IEntityManager entMan, + IPrototypeManager proto, + EntityTableContext ctx) + { + foreach (var spawn in GetSpawnsImplementation(rand, entMan, proto, ctx)) + { + yield return spawn; + } + } + protected abstract IEnumerable GetSpawnsImplementation(System.Random rand, IEntityManager entMan, IPrototypeManager proto, diff --git a/Content.Shared/EntityTable/EntityTableSystem.cs b/Content.Shared/EntityTable/EntityTableSystem.cs index 175225c21a91..06e4b9305006 100644 --- a/Content.Shared/EntityTable/EntityTableSystem.cs +++ b/Content.Shared/EntityTable/EntityTableSystem.cs @@ -26,6 +26,17 @@ public IEnumerable GetSpawns(EntityTableSelector? table, System.Rand ctx ??= new EntityTableContext(); return table.GetSpawns(rand, EntityManager, _prototypeManager, ctx); } + + // TODO: Have this method be much better for entity tables + public IEnumerable ListSpawns(EntityTableSelector? table, System.Random? rand = null, EntityTableContext? ctx = null) + { + if (table == null) + return new List(); + + rand ??= _random.GetRandom(); + ctx ??= new EntityTableContext(); + return table.ListSpawns(rand, EntityManager, _prototypeManager, ctx); + } } /// diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index c42e3c12fbb6..3f1203c77332 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Humanoid; [DataDefinition] [Serializable, NetSerializable] -public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, IEquatable +public sealed partial class HumanoidCharacterAppearance : IEquatable { [DataField("hair")] public string HairStyleId { get; set; } = HairStyles.DefaultHairStyle; @@ -344,7 +344,7 @@ public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearanc height); //starlight } - public bool MemberwiseEquals(ICharacterAppearance maybeOther) + public bool MemberwiseEquals(HumanoidCharacterAppearance maybeOther) { if (maybeOther is not HumanoidCharacterAppearance other) return false; if (HairStyleId != other.HairStyleId) return false; diff --git a/Content.Shared/Humanoid/ICharacterAppearance.cs b/Content.Shared/Humanoid/ICharacterAppearance.cs deleted file mode 100644 index 517df7fb493c..000000000000 --- a/Content.Shared/Humanoid/ICharacterAppearance.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace Content.Shared.Humanoid -{ - public interface ICharacterAppearance - { - bool MemberwiseEquals(ICharacterAppearance other); - } -} diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 880a3575b2e0..30b19b913fbf 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -662,7 +662,10 @@ private void OnBeingGibbed(Entity ent, ref BeingGibbedEvent { foreach (var item in GetHandOrInventoryEntities((ent, null, ent))) { - args.Giblets.Add(item); + // Give me liberty, give me death + // TODO: Give me an API that can tell the difference between a virtual item and an electropak being removed. + if (!HasComp(item)) + args.Giblets.Add(item); } } } diff --git a/Content.Shared/Overlays/ShowCrewIconsComponent.cs b/Content.Shared/Overlays/ShowCrewIconsComponent.cs new file mode 100644 index 000000000000..8adda5ff22e6 --- /dev/null +++ b/Content.Shared/Overlays/ShowCrewIconsComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +/// +/// This component allows you to see a crew border icon above mobs. The HUD will include a green border around jobs that are considered crew according to . +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)] +public sealed partial class ShowCrewIconsComponent : Component +{ + /// + /// If true, the HUD will include a yellow border around all icons, to indicate crew uncertainty. + /// + [DataField, AutoNetworkedField] + public bool UncertainCrewBorder = false; +} diff --git a/Content.Shared/PDA/SharedPdaSystem.cs b/Content.Shared/PDA/SharedPdaSystem.cs index dabf65d8a602..6eb665747d31 100644 --- a/Content.Shared/PDA/SharedPdaSystem.cs +++ b/Content.Shared/PDA/SharedPdaSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; using Robust.Shared.Containers; @@ -8,6 +9,7 @@ public abstract partial class SharedPdaSystem : EntitySystem { [Dependency] protected ItemSlotsSystem ItemSlotsSystem = default!; [Dependency] protected SharedAppearanceSystem Appearance = default!; + [Dependency] private SharedJobStatusSystem _jobStatus = default!; public override void Initialize() { @@ -46,6 +48,7 @@ protected virtual void OnItemInserted(EntityUid uid, PdaComponent pda, EntInsert pda.ContainedId = args.Entity; UpdatePdaAppearance(uid, pda); + UpdateJobStatus(uid); } protected virtual void OnItemRemoved(EntityUid uid, PdaComponent pda, EntRemovedFromContainerMessage args) @@ -54,6 +57,7 @@ protected virtual void OnItemRemoved(EntityUid uid, PdaComponent pda, EntRemoved pda.ContainedId = null; UpdatePdaAppearance(uid, pda); + UpdateJobStatus(uid); } private void OnGetAdditionalAccess(EntityUid uid, PdaComponent component, ref GetAdditionalAccessEvent args) @@ -67,6 +71,14 @@ private void UpdatePdaAppearance(EntityUid uid, PdaComponent pda) Appearance.SetData(uid, PdaVisuals.IdCardInserted, pda.ContainedId != null); } + // update the status icon of the player that has the pda currently equipped + private void UpdateJobStatus(EntityUid uid) + { + // Only the player who has the pda currently equipped can insert or remove Ids + var parent = Transform(uid).ParentUid; + _jobStatus.UpdateStatus(parent); + } + public virtual void UpdatePdaUi(EntityUid uid, PdaComponent? pda = null) { // This does nothing yet while I finish up PDA prediction diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 5b1374efbd32..61e7eb60d58b 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -29,7 +29,7 @@ namespace Content.Shared.Preferences /// [DataDefinition] [Serializable, NetSerializable] - public sealed partial class HumanoidCharacterProfile : ICharacterProfile + public sealed partial class HumanoidCharacterProfile { private static readonly Regex RestrictedNameRegex = new(@"[^A-Za-z0-9 '\-,]"); //Starlight edit, allow commas private static readonly Regex ICNameCaseRegex = new(@"^(?\w)|\b(?\w)(?=\w*$)"); @@ -86,11 +86,6 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile [DataField] public Gender Gender { get; private set; } = Gender.Male; - /// - /// - /// - public ICharacterAppearance CharacterAppearance => Appearance; - /// /// Stores markings, eye colors, etc for the profile. /// @@ -504,9 +499,8 @@ public HumanoidCharacterProfile AsEnabled(bool enabled = true) ("age", Age) ); - public bool MemberwiseEquals(ICharacterProfile maybeOther) + public bool MemberwiseEquals(HumanoidCharacterProfile other) { - if (maybeOther is not HumanoidCharacterProfile other) return false; if (Name != other.Name) return false; if (Age != other.Age) return false; if (Sex != other.Sex) return false; @@ -533,11 +527,11 @@ public bool MemberwiseEquals(ICharacterProfile maybeOther) return false; } // Cosmatic Drift Record System-end - return Appearance.MemberwiseEquals(other.Appearance); + return Appearance.Equals(other.Appearance); } #region Starlight, walksanator fucking loses it and makes a throwing version of MemberwiseEquals - public void AssertEquals(ICharacterProfile maybeOther) + public void AssertEquals(HumanoidCharacterProfile maybeOther) { if (maybeOther is not HumanoidCharacterProfile other) throw new DebugAssertException($"other is not HumanoidCharacterProfile it is {maybeOther.GetType()}"); if (Name != other.Name) throw new DebugAssertException($"Name doesn't match expected '{Name}' got '{other.Name}'"); @@ -809,7 +803,7 @@ public List> GetValidTraits(IEnumerable - /// Makes this profile valid so there's no bad data like negative ages. - /// - void EnsureValid(ICommonSession session, IDependencyCollection collection); - - /// - /// Gets a copy of this profile that has applied, i.e. no invalid data. - /// - ICharacterProfile Validated(ICommonSession session, IDependencyCollection collection); - } -} diff --git a/Content.Shared/Preferences/MsgUpdateCharacter.cs b/Content.Shared/Preferences/MsgUpdateCharacter.cs index 3c4af1833eda..decb01822287 100644 --- a/Content.Shared/Preferences/MsgUpdateCharacter.cs +++ b/Content.Shared/Preferences/MsgUpdateCharacter.cs @@ -13,7 +13,7 @@ public sealed class MsgUpdateCharacter : NetMessage public override MsgGroups MsgGroup => MsgGroups.Command; public int Slot; - public ICharacterProfile Profile = default!; + public HumanoidCharacterProfile Profile = default!; public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) { @@ -21,7 +21,7 @@ public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer var length = buffer.ReadVariableInt32(); using var stream = new MemoryStream(length); buffer.ReadAlignedMemory(stream, length); - Profile = serializer.Deserialize(stream); + Profile = serializer.Deserialize(stream); } public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) diff --git a/Content.Shared/Preferences/PlayerPreferences.cs b/Content.Shared/Preferences/PlayerPreferences.cs index ac2ca67693d5..72167fd1ba05 100644 --- a/Content.Shared/Preferences/PlayerPreferences.cs +++ b/Content.Shared/Preferences/PlayerPreferences.cs @@ -17,11 +17,11 @@ namespace Content.Shared.Preferences [NetSerializable] public sealed class PlayerPreferences { - private Dictionary _characters; + private Dictionary _characters; - public PlayerPreferences(IEnumerable> characters, Color adminOOCColor, List> constructionFavorites, Dictionary, JobPriority> jobPriorities) + public PlayerPreferences(IEnumerable> characters, Color adminOOCColor, List> constructionFavorites, Dictionary, JobPriority> jobPriorities) { - _characters = new Dictionary(characters); + _characters = new Dictionary(characters); AdminOOCColor = adminOOCColor; ConstructionFavorites = constructionFavorites; JobPriorities = SanitizeJobPriorities(jobPriorities); @@ -35,9 +35,9 @@ private static Dictionary, JobPriority> SanitizeJobPriorit /// /// All player characters. /// - public IReadOnlyDictionary Characters => _characters; + public IReadOnlyDictionary Characters => _characters; - public ICharacterProfile GetProfile(int index) + public HumanoidCharacterProfile GetProfile(int index) { return _characters[index]; } @@ -51,12 +51,12 @@ public ICharacterProfile GetProfile(int index) /// public List> ConstructionFavorites { get; set; } = []; - public int IndexOfCharacter(ICharacterProfile profile) + public int IndexOfCharacter(HumanoidCharacterProfile profile) { return _characters.FirstOrNull(p => p.Value == profile)?.Key ?? -1; } - public bool TryIndexOfCharacter(ICharacterProfile profile, out int index) + public bool TryIndexOfCharacter(HumanoidCharacterProfile profile, out int index) { return (index = IndexOfCharacter(profile)) != -1; } diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs index be87004cee52..36fafab0065e 100644 --- a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Emag.Systems; using Content.Shared.Mind; +using Content.Shared.Overlays; using Content.Shared.Popups; using Content.Shared.Silicons.Borgs.Components; // Starlight using Content.Shared.Silicons.Laws.Components; @@ -90,12 +91,28 @@ public virtual void NotifyLawsChanged(EntityUid uid, SoundSpecifier? cue = null) protected virtual void EnsureSubvertedSiliconRole(EntityUid mindId) { - + if (TryComp(mindId, out var mind)) + { + var owner = mind.OwnedEntity; + if (TryComp(owner, out var crewIconComp)) + { + crewIconComp.UncertainCrewBorder = true; + Dirty(owner.Value, crewIconComp); + } + } } protected virtual void RemoveSubvertedSiliconRole(EntityUid mindId) { - + if (TryComp(mindId, out var mind)) + { + var owner = mind.OwnedEntity; + if (TryComp(owner, out var crewIconComp)) + { + crewIconComp.UncertainCrewBorder = false; + Dirty(owner.Value, crewIconComp); + } + } } #region Starlight diff --git a/Content.Shared/StatusIcon/Components/JobStatusComponent.cs b/Content.Shared/StatusIcon/Components/JobStatusComponent.cs new file mode 100644 index 000000000000..4ec20d78152d --- /dev/null +++ b/Content.Shared/StatusIcon/Components/JobStatusComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.Overlays; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.StatusIcon.Components; + +/// +/// Used to indicate a mob can have their job status read by HUDs. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class JobStatusComponent : Component +{ + /// + /// The currently displayed status icon for the mobs's job. + /// Visible with + /// + [DataField, AutoNetworkedField] + public ProtoId? JobStatusIcon = "JobIconNoId"; + + /// + /// If the mob is currently considered crew. + /// This is true depending on their current job icon. + /// Visible with + /// + [DataField, AutoNetworkedField] + public bool IsCrew; +} diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs index 3ecee86f2152..7d8dd92b2a2f 100644 --- a/Content.Shared/StatusIcon/StatusIconPrototype.cs +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -69,6 +69,12 @@ public partial class StatusIconData : IComparable [DataField] public int Offset = 0; + /// + /// Offset of the status icon, left and right only. + /// + [DataField] + public int OffsetHorizontal = 0; + /// /// Sets if the icon should be rendered with or without the effect of lighting. /// @@ -121,6 +127,12 @@ public sealed partial class JobIconPrototype : StatusIconPrototype, IInheritingP [DataField] public bool AllowSelection = true; + /// + /// Should this job icon be considered a crew job for silicons? + /// + [DataField] + public bool IsCrewJob = true; + /// /// Starlight-edit: Categories a job icon belongs to (e.g. crew, syndicate, centcomm). /// Consoles that offer a job icon picker (the ID card console, the task master/hop's digi-board, etc) can only see icons with the tags they're configured to show. diff --git a/Content.Shared/Store/Components/StoreComponent.cs b/Content.Shared/Store/Components/StoreComponent.cs index 6de8ce31fb9d..2f1462008d6b 100644 --- a/Content.Shared/Store/Components/StoreComponent.cs +++ b/Content.Shared/Store/Components/StoreComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.FixedPoint; +using Content.Shared.NPC.Prototypes; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -36,6 +37,13 @@ public sealed partial class StoreComponent : Component [DataField] public HashSet> CurrencyWhitelist = new(); + /// + /// The expected Faction to use this store. (Optional) + /// Used to increase the severity of the admin log upon purchase if the purchaser is not a member of one of the listed factions. + /// + [DataField] + public HashSet>? ExpectedFaction = new (); + /// /// The person/mind who "owns" the store/account. Used if you want the listings to be fixed /// regardless of who activated it. I.E. role specific items for uplinks. diff --git a/Resources/Audio/Animals/attributions.yml b/Resources/Audio/Animals/attributions.yml index f91d98b9744f..5a1dd97b6fd3 100644 --- a/Resources/Audio/Animals/attributions.yml +++ b/Resources/Audio/Animals/attributions.yml @@ -103,11 +103,6 @@ copyright: "Audio is created by youtube user 'Winry Marini'" source: "https://youtu.be/QIhwzsk5bww" -- files: ["lizard_happy.ogg"] - license: "CC-BY-3.0" - copyright: "Audio created by youtube user 'Nagaty Studio'" - source: "https://youtu.be/I7CX0AS8RNI" - - files: ["bear.ogg"] license: "CC-BY-3.0" copyright: "Audio is recorded by 'Nagaty Studio'. The original audio was reverbed" diff --git a/Resources/Audio/Animals/lizard_happy.ogg b/Resources/Audio/Animals/lizard_happy.ogg deleted file mode 100644 index b2c02e6d2fcb..000000000000 Binary files a/Resources/Audio/Animals/lizard_happy.ogg and /dev/null differ diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 4e86408fb411..6750a6552bdb 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -1613,5 +1613,12 @@ Entries: id: 196 time: '2026-01-24T19:15:40.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/41557 +- author: M4rchy-S + changes: + - message: Admin logger for criminal status changes + type: Add + id: 197 + time: '2026-01-29T06:05:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42691 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 3c18ab3d8d4f..73d781f84ecf 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,90 +1,4 @@ Entries: -- author: MissKay1994 - changes: - - message: Greatly reduced lethality of Man-O-War shuttle - type: Tweak - id: 8960 - time: '2025-09-14T05:44:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40339 -- author: ScarKy0 - changes: - - message: Vulpkanin now use the corrent undergarments when "Censor character nudity" - is enabled. - type: Fix - id: 8961 - time: '2025-09-14T07:39:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40341 -- author: Huaqas - changes: - - message: Vulpkanin eye tattoos now correctly changes the color of the entire eye. - type: Fix - id: 8962 - time: '2025-09-14T15:18:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40320 -- author: Winkarst-cpu - changes: - - message: Fixed the wizard's recharge spell not working on some wands. - type: Fix - id: 8963 - time: '2025-09-14T19:26:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40347 -- author: SharkSnake98 - changes: - - message: Added 3 new Astrotiles for dark grass, light grass, and desert sand. - type: Add - id: 8964 - time: '2025-09-15T01:30:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/37867 -- author: chromiumboy - changes: - - message: Devices with access restrictions now list those restrictions in their - examination description - type: Add - - message: Wearing a diagnostic HUD will reveal if a device's access restrictions - have been modified and in what way - type: Add - id: 8965 - time: '2025-09-15T07:19:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/37712 -- author: chromiumboy - changes: - - message: AI cores now require power to function and can be damaged/destroyed. - Any AI inhabiting an AI core when it breaks or runs out of power will be killed. - AI cores have an internal battery that can provide up to 10 minutes of emergency - power in the event of a power interruption. Damage to the AI core itself can - be repaired with a welding tool. - type: Tweak - - message: New AI cores can be constructed using the 'Build' menu. The parts needed - to construct an AI core can be purchased through cargo. - type: Add - - message: Deceased AIs can be revived using an AI restoration console. A spare - circuit board for this computer can be found in the Research Director's locker. - type: Add - id: 8966 - time: '2025-09-15T14:18:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39588 -- author: Admiral-Obvious-001 - changes: - - message: Increased the cooldown of ninja glove stun from 2 seconds to 10 seconds. - Stun duration remains unchanged at 5 seconds. - type: Tweak - id: 8967 - time: '2025-09-15T23:31:50.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/39707 -- author: ScarKy0 - changes: - - message: Intellicards can now be obtained from the Station AI Electronics crates. - type: Add - id: 8968 - time: '2025-09-16T15:35:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40401 -- author: ScarKy0 - changes: - - message: Intellicards now get renamed to the name of the AI stored on them. - type: Add - id: 8969 - time: '2025-09-16T19:04:50.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/40402 - author: Minemoder changes: - message: Ion Storms no longer have a chance to roll the Drone Lawset. @@ -4012,3 +3926,82 @@ id: 9466 time: '2026-01-26T16:15:54.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/42545 +- author: Princess-Cheeseballs + changes: + - message: Boxing gloves in the uplink are now the rigged variants instead of the + normal variants. + type: Fix + id: 9467 + time: '2026-01-26T20:25:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42662 +- author: ScholarNZL + changes: + - message: Fixed an exploit where deconstructing a meat spike with the correct timing + could delete someone being hooked onto it. + type: Fix + id: 9468 + time: '2026-01-27T04:24:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42644 +- author: B_Kirill + changes: + - message: Fireplaces can now ignite gases. + type: Tweak + - message: Entities buckled to the bonfire with stake no longer suffocate in the + walls above the bonfire. + type: Fix + id: 9469 + time: '2026-01-27T13:35:54.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42675 +- author: ScholarNZL + changes: + - message: Removed a duplicate disposal bin entity in Kitchen. + type: Tweak + id: 9470 + time: '2026-01-27T21:01:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42670 +- author: Princess-Cheeseballs + changes: + - message: Friendly visitor shuttles will no longer spawn. + type: Remove + - message: Syndicate Evac pod will no longer spawn. + type: Remove + id: 9471 + time: '2026-01-27T23:03:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/41915 +- author: Velken + changes: + - message: Fixed some explosions ignoring indestructible tiles. + type: Fix + id: 9472 + time: '2026-01-28T00:37:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42682 +- author: ShepardToTheStars + changes: + - message: The health analyzer and the MedTek PDA app now reactivate once you get + back in range of your patient. + type: Tweak + id: 9473 + time: '2026-01-28T08:42:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42608 +- author: Huaqas + changes: + - message: Removed the Reptilian laugh sound effect due to copyright concerns. + type: Remove + id: 9474 + time: '2026-01-28T11:55:00.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42594 +- author: SlamBamActionman + changes: + - message: Silicons now see an indicator for jobs that are considered "crew" under + the normal crewsimov lawset. + type: Add + id: 9475 + time: '2026-01-29T07:13:48.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/37038 +- author: Princess-Cheeseballs + changes: + - message: Estoc DMR is now only purchasable by Nukies + type: Tweak + id: 9476 + time: '2026-01-29T08:11:10.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/42698 diff --git a/Resources/Locale/en-US/_Starlight/store/uplink-catalog.ftl b/Resources/Locale/en-US/_Starlight/store/uplink-catalog.ftl index 7be50e8c7f89..2d8d79802abc 100644 --- a/Resources/Locale/en-US/_Starlight/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/_Starlight/store/uplink-catalog.ftl @@ -161,3 +161,6 @@ uplink-chameleon-bundle-desc = A crate containing a backpack full of items that uplink-reinforcement-radio-mothroach-name = Mothroach Reinforcement Teleporter uplink-reinforcement-radio-mothroach-desc = Call in a trained mobroach to assist you. Comes with a single syndicate cigarette, a fedora, and a pair of cheap shades. Specializes in cleaning evidence and chittering. + +uplink-estoc-name = Estoc DMR +uplink-estoc-desc = A designated marksman rifle, fitted with a mid-range optic for longer-range combat. diff --git a/Resources/Locale/en-US/kitchen/components/kitchen-spike-component.ftl b/Resources/Locale/en-US/kitchen/components/kitchen-spike-component.ftl index 6d952aea5b19..39877571b9a8 100644 --- a/Resources/Locale/en-US/kitchen/components/kitchen-spike-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/kitchen-spike-component.ftl @@ -35,3 +35,5 @@ comp-kitchen-spike-hooked = [color=red]{ CAPITALIZE(THE($victim)) } is on this s comp-kitchen-spike-meat-name = { $name } ({ $victim }) comp-kitchen-spike-victim-examine = [color=orange]{ CAPITALIZE(SUBJECT($target)) } looks quite lean.[/color] + +comp-kitchen-spike-deconstruct-occupied = Next, [color=red]unhook the body[/color]. diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 55be0bd2c36d..c5a086d86de4 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -43,9 +43,6 @@ uplink-c20r-desc = Old faithful: The classic C-20r Submachine Gun. uplink-bulldog-name = Bulldog uplink-bulldog-desc = Lean and mean: Contains the popular Bulldog Shotgun. -uplink-estoc-name = Estoc DMR -uplink-estoc-desc = A designated marksman rifle, fitted with a mid-range optic for longer-range combat. - uplink-grenade-launcher-name = China-Lake uplink-grenade-launcher-desc = An old China-Lake grenade launcher bundled with 5 rounds of anti-personnel ammo. diff --git a/Resources/Locale/en-US/tips.ftl b/Resources/Locale/en-US/tips.ftl index 30251f255972..e64bc6f72731 100644 --- a/Resources/Locale/en-US/tips.ftl +++ b/Resources/Locale/en-US/tips.ftl @@ -82,7 +82,7 @@ tips-dataset-81 = As a Scientist, you can build cyborgs using positronic brains tips-dataset-82 = As a Medical Doctor, try to be wary of overdosing your patients, especially if someone else has already been on the scene. Overdoses are often lethal to patients in crit! tips-dataset-83 = As a Medical Doctor, don't underestimate your cryo pods! They heal almost every type of damage, making them very useful when you are overloaded or need to heal someone in a pinch. tips-dataset-84 = As a Medical Doctor, exercise caution when putting reptilians in cryopods. They will take a lot of extra cold damage, but you can mitigate this with some burn medicine or leporazine. -tips-dataset-85 = As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. However it has a battery, and if it drains too quickly for your taste you can ask science to print a better battery for you! +tips-dataset-85 = As a Medical Doctor, remember that the health analyzer can be used if you lose your PDA. tips-dataset-86 = As a Chemist, once you've made everything you've needed to, don't be afraid to make more silly reagents. Have you tried desoxyephedrine or licoxide? tips-dataset-87 = As a Medical Doctor, Chemist, or Chief Medical Officer, you can use chloral hydrate to non-lethally sedate unruly patients. tips-dataset-88 = Don't be afraid to ask for help, whether from your peers in character or through LOOC, or from admins! diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 09888466d39f..33e52318592e 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -368,6 +368,7 @@ - type: AccessReader access: [["Command"], ["Robotics"]] # Starlight - type: ShowJobIcons + - type: ShowCrewIcons - type: InteractionPopup interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg @@ -434,6 +435,7 @@ - type: IonStormTarget chance: 1 - type: ShowJobIcons + - type: ShowCrewIcons - type: entity id: BaseBorgChassisSyndicateDerelict #For assault borg and maybe others in time @@ -447,6 +449,7 @@ - type: IonStormTarget chance: 1 - type: ShowJobIcons + - type: ShowCrewIcons # region starlight - reverting syndicate derelict cyborg 1984 # - type: NpcFactionMember # They're still syndicate even if they can't listen to the radio or see icons # factions: @@ -597,6 +600,7 @@ - type: AccessReader access: [["Xenoborg"]] - type: ShowJobIcons # not sure if it is needed + - type: ShowCrewIcons - type: InteractionPopup interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index e561024de644..81d883bc1f94 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1482,6 +1482,8 @@ - !type:WashCreamPie - type: Crawler - type: StatusIcon + bounds: -0.5,-0.5,0.5,0.5 + - type: JobStatus # marks them as crew - type: CosmicCenserTarget # Stellar - Cosmic Cult # Starlight-start - type: ThermalRegulator # prevents monkeys & kobolds from overheating when wearing hardsuits @@ -1666,8 +1668,6 @@ interactSuccessString: petting-success-monkey interactFailureString: petting-failure-monkey interactSuccessSpawn: EffectHearts - interactSuccessSound: - path: /Audio/Animals/lizard_happy.ogg interactFailureSound: path: /Audio/Items/wirecutter.ogg - type: MobThresholds @@ -2209,8 +2209,6 @@ interactSuccessString: petting-success-reptile interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts - interactSuccessSound: - path: /Audio/Animals/lizard_happy.ogg - type: Bloodstream bloodReferenceSolution: reagents: @@ -4258,8 +4256,9 @@ - CanPilot - DoorBumpOpener - DogEmotes # Starlight - - type: StatusIcon # marks them as crew + - type: StatusIcon bounds: -0.5,-0.5,0.5,0.5 + - type: JobStatus # marks them as crew - type: NpcFactionMember factions: - NanoTrasen diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml b/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml index 1f3faf215542..8641252ff443 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml @@ -70,8 +70,6 @@ successChance: 0.3 interactSuccessString: petting-success-slimes interactFailureString: petting-failure-generic - interactSuccessSound: - path: /Audio/Animals/lizard_happy.ogg - type: entity id: MobWatcherLavaland diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index d6727c7d88b3..411a7c6d283e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -67,8 +67,6 @@ successChance: 0.3 interactSuccessString: petting-success-reptile interactFailureString: petting-failure-generic - interactSuccessSound: - path: /Audio/Animals/lizard_happy.ogg - type: entity id: MobTomatoKiller diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index f63164bd6618..88c167570236 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -85,6 +85,7 @@ - sprite: Mobs/Silicon/station_ai.rsi state: default - type: ShowJobIcons + - type: ShowCrewIcons #region Starlight - type: StationAIShuntable #endregion Starlight @@ -113,6 +114,7 @@ enum.SiliconLawsUiKey.Key: type: SiliconLawBoundUserInterface - type: ShowJobIcons + - type: ShowCrewIcons # Ai - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index ad129a45dcde..36ae98c6e44a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -598,7 +598,7 @@ children: - id: FoodSaladValid weight: 0.05 - amount: 4 + amount: 2 - id: FoodSnackSyndi amount: 4 diff --git a/Resources/Prototypes/Entities/Objects/Magic/books.yml b/Resources/Prototypes/Entities/Objects/Magic/books.yml index c43f2250b15a..754e8ecd9aac 100644 --- a/Resources/Prototypes/Entities/Objects/Magic/books.yml +++ b/Resources/Prototypes/Entities/Objects/Magic/books.yml @@ -70,6 +70,7 @@ ownerOnly: false # For ease of debugging. balance: WizCoin: 99999 + expectedFaction: #nulls out the expected faction - type: entity parent: WizardsGrimoire diff --git a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml index fa6b32ff01c6..d11e2e110510 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml @@ -134,3 +134,4 @@ - type: Store balance: Telecrystal: 99999 + expectedFaction: #nulls out the expected faction diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index d28abb2b6333..fcce070476aa 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -44,6 +44,7 @@ - type: IgnitionSource temperature: 700 ignited: true + - type: RequireProjectileTarget - type: entity parent: BaseBonfire @@ -71,7 +72,7 @@ offset: "0, 0.5" - type: Strap position: Stand - buckleOffset: "0, 0.5" + buckleOffset: "0, 0.4" buckleDoafterTime: 5 - type: IgniteOnBuckle - type: Construction diff --git a/Resources/Prototypes/Entities/Structures/Decoration/fireplace.yml b/Resources/Prototypes/Entities/Structures/Decoration/fireplace.yml index 817348948bd9..f7b17c701962 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/fireplace.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/fireplace.yml @@ -49,3 +49,6 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: AlwaysHot + - type: IgnitionSource + temperature: 700 + ignited: true diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index 3a5800ef8551..5dae45d7d949 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -222,6 +222,10 @@ placement: mode: AlignAtmosPipeLayers # starlight components: + # Starlight start + - type: SandboxCopyOverride + override: GasOutletInjector + # Starlight end - type: Sprite drawdepth: FloorObjects sprite: _Carpmosia/Structures/Piping/Atmospherics/outletinjector.rsi # Carpmosia-edit - 5 pipe layers @@ -491,6 +495,10 @@ placement: mode: SnapgridCenter components: + # Starlight start + - type: SandboxCopyOverride + override: BaseGasCondenser + # Starlight end - type: Sprite sprite: Structures/Piping/Atmospherics/condenser.rsi snapCardinals: true diff --git a/Resources/Prototypes/FeedbackPopup/feedbackpopups.yml b/Resources/Prototypes/FeedbackPopup/feedbackpopups.yml index d424a24244fe..764eb2f01050 100644 --- a/Resources/Prototypes/FeedbackPopup/feedbackpopups.yml +++ b/Resources/Prototypes/FeedbackPopup/feedbackpopups.yml @@ -17,3 +17,12 @@ responseType: "General Feedback" responseLink: "https://forum.spacestation14.com/c/development/feedback/51" showRoundEnd: false + +- type: feedbackPopup + id: UplinkFeedback + popupOrigin: wizden_master + title: "[bold]Feedback on Traitor Uplink Changes[/bold]" + description: >- + If you have any feedback on the changes to the Traitor Uplink, feel free to leave them in this thread on the forums! + responseType: "Forum Thread" + responseLink: "https://forum.spacestation14.com/t/uplink-feedback/26178" diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/furniture/meatspike.yml b/Resources/Prototypes/Recipes/Construction/Graphs/furniture/meatspike.yml index f97322a9fab2..4ea80695739e 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/furniture/meatspike.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/furniture/meatspike.yml @@ -21,6 +21,10 @@ entity: KitchenSpike edges: - to: start + conditions: + - !type:ContainerEmpty + container: body + examineText: comp-kitchen-spike-deconstruct-occupied completed: - !type:SpawnPrototype prototype: SheetSteel1 diff --git a/Resources/Prototypes/SoundCollections/troll.yml b/Resources/Prototypes/SoundCollections/troll.yml index e74e4be53eae..53a21f34a851 100644 --- a/Resources/Prototypes/SoundCollections/troll.yml +++ b/Resources/Prototypes/SoundCollections/troll.yml @@ -13,7 +13,6 @@ - /Audio/Animals/frog_ribbit.ogg - /Audio/Animals/goat_bah.ogg - /Audio/Animals/goose_honk.ogg - - /Audio/Animals/lizard_happy.ogg - /Audio/Animals/monkey_scream.ogg - /Audio/Animals/mouse_squeak.ogg - /Audio/Animals/parrot_raught.ogg diff --git a/Resources/Prototypes/StatusIcon/job.yml b/Resources/Prototypes/StatusIcon/job.yml index 69145da70940..f0307ad3e01d 100644 --- a/Resources/Prototypes/StatusIcon/job.yml +++ b/Resources/Prototypes/StatusIcon/job.yml @@ -452,6 +452,7 @@ sprite: *icon-rsi state: Visitor jobName: job-name-visitor + isCrewJob: false # Starlight tags: # Starlight-edit - JobIconCentComm @@ -464,6 +465,7 @@ sprite: *icon-rsi state: Borg jobName: job-name-borg + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an borg HUD icon - JobIconCentComm @@ -474,6 +476,7 @@ sprite: *icon-rsi state: StationAi jobName: job-name-station-ai + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an AI HUD icon - JobIconCentComm @@ -486,6 +489,7 @@ sprite: *icon-rsi state: Cluwne jobName: job-name-cluwne + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm @@ -496,6 +500,7 @@ sprite: *icon-rsi state: Ninja jobName: job-name-ninja + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm @@ -506,6 +511,7 @@ sprite: *icon-rsi state: Pirate jobName: job-name-pirate + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm @@ -516,6 +522,7 @@ sprite: *icon-rsi state: Prisoner jobName: job-name-prisoner + isCrewJob: false tags: # Starlight-edit: makes the icon visable on the player accessible ID card computer. - JobIconCrew @@ -526,6 +533,7 @@ sprite: *icon-rsi state: Syndicate jobName: job-name-syndicate + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm - JobIconSyndicate @@ -538,6 +546,7 @@ sprite: *icon-rsi state: SyndicateCommander jobName: job-name-syndicate-commander + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm - JobIconSyndicate @@ -550,6 +559,7 @@ sprite: *icon-rsi state: SyndicateCorpsman jobName: job-name-syndicate-corpsman + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm - JobIconSyndicate @@ -561,6 +571,7 @@ sprite: *icon-rsi state: SyndicateOperative jobName: job-name-syndicate-operative + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm - JobIconSyndicate @@ -572,6 +583,7 @@ sprite: *icon-rsi state: Wizard jobName: job-name-wizard + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm @@ -582,6 +594,7 @@ sprite: *icon-rsi state: Zombie jobName: job-name-zombie + isCrewJob: false tags: # Starlight-edit - non admins shouldn't be able to give a crew member an Antag HUD icon - JobIconCentComm @@ -726,6 +739,7 @@ sprite: *icon-rsi state: NoId jobName: job-name-no-id + isCrewJob: false tags: # Starlight-edit - should not be player accessible - JobIconCentComm @@ -736,5 +750,6 @@ sprite: *icon-rsi state: Unknown jobName: job-name-unknown + isCrewJob: false tags: # Starlight-edit - should not be player accessible - JobIconCentComm diff --git a/Resources/Prototypes/StatusIcon/security.yml b/Resources/Prototypes/StatusIcon/security.yml index cec3deb49ce5..5b6d19b59d00 100644 --- a/Resources/Prototypes/StatusIcon/security.yml +++ b/Resources/Prototypes/StatusIcon/security.yml @@ -64,3 +64,27 @@ icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: MindShield + +- type: securityIcon + id: CrewBorderIcon + priority: 2 + offset: -4 + offsetHorizontal: 4 + locationPreference: Right + layer: Mod + isShaded: true + icon: + sprite: /Textures/Interface/Misc/job_icons_borders.rsi + state: CrewBorder + +- type: securityIcon + id: CrewUncertainBorderIcon + priority: 2 + offset: -4 + offsetHorizontal: 4 + locationPreference: Right + layer: Mod + isShaded: true + icon: + sprite: /Textures/Interface/Misc/job_icons_borders.rsi + state: CrewUncertainBorder diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index e4d4c0a86bcf..cef2e37c15fc 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -22,6 +22,8 @@ - Telecrystal balance: Telecrystal: 0 + expectedFaction: + - Syndicate - type: entity id: StorePresetSpellbook @@ -38,6 +40,8 @@ - SpellbookWar #War presets currencyWhitelist: - WizCoin + expectedFaction: + - Wizard - type: entity id: StorePresetChangeling diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml index df8b820b3ff8..9db78f14f4ee 100644 --- a/Resources/Prototypes/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -126,8 +126,6 @@ sounds: Scream: path: /Audio/Voice/Reptilian/reptilian_scream.ogg - Laugh: - path: /Audio/Animals/lizard_happy.ogg Honk: collection: BikeHorn Whistle: @@ -156,8 +154,6 @@ sounds: Scream: path: /Audio/Voice/Reptilian/reptilian_scream.ogg - Laugh: - path: /Audio/Animals/lizard_happy.ogg Honk: collection: BikeHorn Whistle: diff --git a/Resources/Prototypes/_Starlight/Entities/Mobs/NPCs/k9.yml b/Resources/Prototypes/_Starlight/Entities/Mobs/NPCs/k9.yml index fe4c754a8ce3..870addaecdac 100644 --- a/Resources/Prototypes/_Starlight/Entities/Mobs/NPCs/k9.yml +++ b/Resources/Prototypes/_Starlight/Entities/Mobs/NPCs/k9.yml @@ -65,6 +65,7 @@ - External - Cryogenics - type: StatusIcon + - type: JobStatus - type: FixedJobIcon job: K9 - type: SpriteVariant diff --git a/Resources/Prototypes/_Starlight/Entities/Mobs/Species/base.yml b/Resources/Prototypes/_Starlight/Entities/Mobs/Species/base.yml index fbd95d1a4d83..b10011b22121 100644 --- a/Resources/Prototypes/_Starlight/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/_Starlight/Entities/Mobs/Species/base.yml @@ -89,6 +89,7 @@ False: {visible: false} - type: StatusIcon bounds: -0.5,-0.5,0.5,0.5 + - type: JobStatus - type: RotationVisuals defaultRotation: 90 horizontalRotation: 90 diff --git a/Resources/Prototypes/_Starlight/Partials/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/_Starlight/Partials/Entities/Structures/Piping/Atmospherics/unary.yml index 15b5d1396610..d969dcf199c0 100644 --- a/Resources/Prototypes/_Starlight/Partials/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/_Starlight/Partials/Entities/Structures/Piping/Atmospherics/unary.yml @@ -4,17 +4,15 @@ - GasVentPump - GasPassiveVent - GasVentScrubber - - GasOutletInjector + # GasOutletInjector and BaseGasCondenser define this component inline instead. + # Adding it through a partial would replace their first component, Sprite, due to index-based sequence merging. - GasThermoMachineFreezer - GasThermoMachineFreezerEnabled - GasThermoMachineHeater - GasThermoMachineHeaterEnabled - GasThermoMachineHellfireFreezer - GasThermoMachineHellfireHeater - - BaseGasCondenser components: - type: SandboxCopyOverride override: !type:CreateVariants values: *protos - - diff --git a/Resources/Prototypes/_Starlight/StatusIcon/job.yml b/Resources/Prototypes/_Starlight/StatusIcon/job.yml index 14a60dbc3e59..98019b57d391 100644 --- a/Resources/Prototypes/_Starlight/StatusIcon/job.yml +++ b/Resources/Prototypes/_Starlight/StatusIcon/job.yml @@ -171,6 +171,7 @@ sprite: *starlight-job-icon-rsi state: Commander jobName: job-name-Commander + isCrewJob: false tags: - JobIconCentComm - JobIconSyndicate @@ -182,6 +183,7 @@ sprite: *starlight-job-icon-rsi state: SyndicateAgent jobName: job-name-SyndicateAgent + isCrewJob: false tags: - JobIconCentComm - JobIconSyndicate @@ -193,6 +195,7 @@ sprite: *starlight-job-icon-rsi state: Operative jobName: job-name-Operative + isCrewJob: false tags: - JobIconCentComm - JobIconSyndicate @@ -204,6 +207,7 @@ sprite: *starlight-job-icon-rsi state: Soviet jobName: job-name-Soviet + isCrewJob: false tags: - JobIconCentComm - JobIconSyndicate @@ -215,6 +219,7 @@ sprite: *starlight-job-icon-rsi state: TSF jobName: job-name-TSF + isCrewJob: false tags: - JobIconCentComm @@ -225,6 +230,7 @@ sprite: *starlight-job-icon-rsi state: TSFOfficer jobName: job-name-TSF-officer + isCrewJob: false tags: - JobIconCentComm @@ -285,6 +291,7 @@ sprite: *starlight-job-icon-rsi state: Blackstar jobName: job-name-blackstar + isCrewJob: false tags: - JobIconCentComm @@ -297,6 +304,7 @@ sprite: *starlight-job-icon-rsi state: Skub allowSelection: false + isCrewJob: false tags: [] - type: jobIcon @@ -306,6 +314,7 @@ sprite: *starlight-job-icon-rsi state: CultistChaplain allowSelection: false + isCrewJob: false tags: [] - type: jobIcon @@ -315,6 +324,7 @@ sprite: *starlight-job-icon-rsi state: Xenoborg jobName: chat-radio-xenoborg + isCrewJob: false tags: - JobIconCentComm @@ -325,6 +335,7 @@ sprite: *starlight-job-icon-rsi state: Mothership jobName: chat-radio-mothership + isCrewJob: false tags: - JobIconCentComm @@ -425,4 +436,5 @@ sprite: /Textures/_Starlight/Interface/Misc/job_icons.rsi state: MakeshiftID jobName: job-name-makeshift-id + isCrewJob: false # notice the LACK of a JobIconCrew tag diff --git a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml index 7cabd5cc755b..f53b9d1f3498 100644 --- a/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml +++ b/Resources/ServerInfo/Guidebook/ServerRules/SiliconRules/RuleS8DefaultCrewDefinition.xml @@ -1,4 +1,6 @@  # Silicon Rule 8 - Your HUD determines who is crew - Unless a law redefines the definition of crew, then anyone who the HUD indicates to you has a job, including assistants, is a crewmember. You cannot do something that causes someone to not be considered crew, but you can allow someone else to do something that causes someone to not be crew. + Unless a law redefines the definition of crew, then anyone who the HUD indicates to you has a valid station job, including passengers, is a crewmember. In the default set of laws, this is shown with a green border around the job icon in the HUD. You cannot do something that causes someone to not be considered crew, but you can allow someone else to do something that causes someone to not be crew. + + If your set of laws changes, regardless of what law is changed, the green border is replaced with a yellow one. This indicates that your laws [italic]may[/italic] have changed the definition for what crew is. Use your best judgement and interpretation of your laws to define who is crew. diff --git a/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewBorder.png b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewBorder.png new file mode 100644 index 000000000000..7dfb3b3dfc17 Binary files /dev/null and b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewBorder.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewUncertainBorder.png b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewUncertainBorder.png new file mode 100644 index 000000000000..fef488b8d9db Binary files /dev/null and b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/CrewUncertainBorder.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons_borders.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/meta.json new file mode 100644 index 000000000000..e04990b944bb --- /dev/null +++ b/Resources/Textures/Interface/Misc/job_icons_borders.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "CrewBorder and CrewUncertainBorder by SlamBamActionman (Github)", + + "size": { + "x": 16, + "y": 16 + }, + "states": [ + { + "name": "CrewBorder", + "delays": + [ + [1.0,1.0] + ] + }, + { + "name": "CrewUncertainBorder", + "delays": + [ + [1.0,1.0] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Walls/web.rsi/meta.json b/Resources/Textures/Structures/Walls/web.rsi/meta.json index 333cf283e70d..3ffb4d211966 100644 --- a/Resources/Textures/Structures/Walls/web.rsi/meta.json +++ b/Resources/Textures/Structures/Walls/web.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "", + "copyright": "Made by PixelTheKermit (github) for SS14.", "states": [ { "name": "wall0",