From aea3a37dcf32eee0b33963a76fcb774e7578ac62 Mon Sep 17 00:00:00 2001 From: ReWAFFlution Date: Tue, 15 Sep 2026 02:48:37 +0300 Subject: [PATCH 1/5] Artlead patron added --- Content.Server/_Arcane/DiscordRoles/DiscordRoleIds.cs | 1 + Content.Shared/_Arcane/DiscordRoles/DiscordRole.cs | 1 + Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs | 2 ++ Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl | 1 + 4 files changed, 5 insertions(+) diff --git a/Content.Server/_Arcane/DiscordRoles/DiscordRoleIds.cs b/Content.Server/_Arcane/DiscordRoles/DiscordRoleIds.cs index df7208bc56..2a8a038816 100644 --- a/Content.Server/_Arcane/DiscordRoles/DiscordRoleIds.cs +++ b/Content.Server/_Arcane/DiscordRoles/DiscordRoleIds.cs @@ -13,6 +13,7 @@ public static class DiscordRoleIds [DiscordRole.SponsorTier1] = 1510991486399942707, [DiscordRole.SponsorTier2] = 1510991694785675397, [DiscordRole.AdminBenefit] = 1545943229143195658, + [DiscordRole.ArtLead] = 1509175991241670696, [DiscordRole.UnlockRoles] = 1533054913318223872, }; } diff --git a/Content.Shared/_Arcane/DiscordRoles/DiscordRole.cs b/Content.Shared/_Arcane/DiscordRoles/DiscordRole.cs index cb203ee1ca..704d900a97 100644 --- a/Content.Shared/_Arcane/DiscordRoles/DiscordRole.cs +++ b/Content.Shared/_Arcane/DiscordRoles/DiscordRole.cs @@ -8,6 +8,7 @@ public enum DiscordRole : byte SponsorTier1, SponsorTier2, AdminBenefit, + ArtLead, UnlockRoles, } diff --git a/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs b/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs index 778e0d3f81..683da8c44f 100644 --- a/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs +++ b/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs @@ -17,10 +17,12 @@ public static class SponsorRoleBenefits [DiscordRole.SponsorTier1] = new("Tier1", "#7d25a8", 2, -1), [DiscordRole.SponsorTier2] = new("Tier2", "#d8aa2d", 3, -2), [DiscordRole.AdminBenefit] = new("AdminBenefit", "#78ecf5", 5, -3), + [DiscordRole.ArtLead] = new("ArtRole", "#ffdaf9", 6, -4) }; private static readonly DiscordRole[] Priority = [ + DiscordRole.ArtLead, DiscordRole.AdminBenefit, DiscordRole.SponsorTier2, DiscordRole.SponsorTier1, diff --git a/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl b/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl index 39543c56ad..a265d9e185 100644 --- a/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl +++ b/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl @@ -5,6 +5,7 @@ not-patron-name = Отсутствует Tier1-patron-name = [color=#8b00d1]Тир 1[/color] Tier2-patron-name = [color=#ecad00]Тир 2[/color] AdminBenefit-patron-name = [color=#ecad00]Хорошая Вахта[/color] +ArtLead-patron-name = [color=#ffdaf9]Арт-Лид[/color] loadout-sponsor-requirement = Этот предмет доступен только для спонсоров! loadout-group-patron = Спонсор From 64a7eb50508ce9260f53dcbbf327c1be2a9481c3 Mon Sep 17 00:00:00 2001 From: ReWAFFlution Date: Wed, 16 Sep 2026 00:11:37 +0300 Subject: [PATCH 2/5] ren marking --- .../Customization/Markings/humanoid_xeno.yml | 29 ++++++++++++++++++ .../HumanoidXeno/head.rsi/meta.json | 4 +++ .../HumanoidXeno/head.rsi/ren_head.png | Bin 0 -> 1168 bytes .../HumanoidXeno/tail.rsi/meta.json | 8 +++++ .../HumanoidXeno/tail.rsi/ren_tail.png | Bin 0 -> 1250 bytes .../HumanoidXeno/tail.rsi/ren_tail_grad.png | Bin 0 -> 1416 bytes .../HumanoidXeno/tracheas.rsi/meta.json | 4 +++ .../HumanoidXeno/tracheas.rsi/ren_tubes.png | Bin 0 -> 409 bytes 8 files changed, 45 insertions(+) create mode 100644 Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/ren_head.png create mode 100644 Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/tail.rsi/ren_tail.png create mode 100644 Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/tail.rsi/ren_tail_grad.png create mode 100644 Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/tracheas.rsi/ren_tubes.png diff --git a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml index d143f71019..37b9ddc5d6 100644 --- a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml +++ b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml @@ -263,6 +263,17 @@ - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tail.rsi state: warrior_tail_thin_grad +- type: marking + id: HumanoidXenoTailRen + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [HumanoidXeno] + sprites: + - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tail.rsi + state: ren_tail + - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tail.rsi + state: ren_tail_grad + # Head @@ -566,6 +577,15 @@ - sprite: _Arcane/Mobs/Customization/HumanoidXeno/head.rsi state: warrior_head_shine +- type: marking + id: HumanoidXenoHeadRen + bodyPart: Head + markingCategory: Head + speciesRestriction: [HumanoidXeno] + sprites: + - sprite: _Arcane/Mobs/Customization/HumanoidXeno/head.rsi + state: ren_head + # Chest - type: marking @@ -743,6 +763,15 @@ - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tracheas.rsi state: warrior_tubes +- type: marking + id: HumanoidXenoTracheasRen + bodyPart: Chest + markingCategory: Tracheas + speciesRestriction: [HumanoidXeno] + sprites: + - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tracheas.rsi + state: ren_tubes + # Legs - type: marking diff --git a/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/meta.json b/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/meta.json index 6662091b1f..3122047c50 100644 --- a/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/meta.json +++ b/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/meta.json @@ -286,6 +286,10 @@ { "name": "warrior_teeth", "directions": 4 + }, + { + "name": "ren_head", + "directions": 4 } ] } diff --git a/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/ren_head.png b/Resources/Textures/_Arcane/Mobs/Customization/HumanoidXeno/head.rsi/ren_head.png new file mode 100644 index 0000000000000000000000000000000000000000..c7dd57d04e798bb313a999d0cb02d1e263707a53 GIT binary patch literal 1168 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 T8ER7p3~L5YS3j3^P6 Date: Wed, 16 Sep 2026 16:39:35 +0300 Subject: [PATCH 3/5] marking only for ArtLead --- Content.Client/Humanoid/MarkingPicker.xaml.cs | 12 +++++- .../Humanoid/SingleMarkingPicker.xaml.cs | 24 +++++++++-- .../Humanoid/HumanoidCharacterAppearance.cs | 6 ++- .../Humanoid/Markings/MarkingManager.cs | 29 +++++++++++++ .../Humanoid/Markings/MarkingPrototype.cs | 18 ++++++++ .../Humanoid/Markings/MarkingsSet.cs | 43 +++++++++++++++++++ .../Preferences/HumanoidCharacterProfile.cs | 4 +- .../_Arcane/Sponsor/SponsorRoleBenefits.cs | 2 +- .../ru-RU/_Arcane/markings/humanoid_xeno.ftl | 3 ++ .../Customization/Markings/humanoid_xeno.yml | 3 ++ 10 files changed, 136 insertions(+), 8 deletions(-) diff --git a/Content.Client/Humanoid/MarkingPicker.xaml.cs b/Content.Client/Humanoid/MarkingPicker.xaml.cs index 847f8d2d5a..ce32725f6f 100644 --- a/Content.Client/Humanoid/MarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/MarkingPicker.xaml.cs @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT using System.Linq; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; @@ -21,6 +23,10 @@ public sealed partial class MarkingPicker : Control [Dependency] private readonly MarkingManager _markingManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + // Arcane-Start + [Dependency] private readonly ISharedDiscordRoleManager _discordRoles = default!; + [Dependency] private readonly IPlayerManager _player = default!; + // Arcane-End private readonly SpriteSystem _sprite; @@ -205,9 +211,11 @@ private List GetMarkingStateNames(MarkingPrototype marking) private IReadOnlyDictionary GetMarkings(MarkingCategories category) { - return IgnoreSpecies + var markings = IgnoreSpecies // Arcane-Edit ? _markingManager.MarkingsByCategoryAndSex(category, _currentSex) : _markingManager.MarkingsByCategoryAndSpeciesAndSex(category, _currentSpecies, _currentSex); + + return _markingManager.FilterSponsorMarkings(markings, _discordRoles, _player.LocalSession); // Arcane } public void Populate(string filter) @@ -576,4 +584,4 @@ private void MarkingRemove() CMarkingColors.Visible = false; OnMarkingRemoved?.Invoke(_currentMarkings); } -} \ No newline at end of file +} diff --git a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs index b1511c1af4..989424d28b 100644 --- a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later using System.Linq; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid.Markings; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; +using Robust.Client.Player; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; @@ -14,6 +16,10 @@ public sealed partial class SingleMarkingPicker : BoxContainer { [Dependency] private readonly MarkingManager _markingManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + // Arcane-Start + [Dependency] private readonly ISharedDiscordRoleManager _discordRoles = default!; + [Dependency] private readonly IPlayerManager _player = default!; + // Arcane-End private readonly SpriteSystem _sprite; @@ -157,7 +163,13 @@ public void UpdateData(List markings, string species, int totalPoints) _species = species; _totalPoints = totalPoints; - _markingPrototypeCache = _markingManager.MarkingsByCategoryAndSpecies(Category, _species); + // Arcane-Edit-Start + // _markingManager.MarkingsByCategoryAndSpecies(Category, _species) + _markingPrototypeCache = _markingManager.FilterSponsorMarkings( + _markingManager.MarkingsByCategoryAndSpecies(Category, _species), + _discordRoles, + _player.LocalSession); + // Arcane-Edit-End Visible = _markingPrototypeCache.Count != 0; if (_markingPrototypeCache.Count == 0) @@ -177,7 +189,13 @@ public void PopulateList(string filter) throw new ArgumentException("Tried to populate marking list without a set species!"); } - _markingPrototypeCache ??= _markingManager.MarkingsByCategoryAndSpecies(Category, _species); + // Arcane-Edit-Start + // _markingManager.MarkingsByCategoryAndSpecies(Category, _species) + _markingPrototypeCache ??= _markingManager.FilterSponsorMarkings( + _markingManager.MarkingsByCategoryAndSpecies(Category, _species), + _discordRoles, + _player.LocalSession); + // Arcane-Edit-End MarkingSelectorContainer.Visible = _markings != null && _markings.Count != 0; if (_markings == null || _markings.Count == 0) @@ -302,4 +320,4 @@ private string GetMarkingName(MarkingPrototype marking) { return Loc.GetString($"marking-{marking.ID}"); } -} \ No newline at end of file +} diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index 54c48052e4..e401f5a710 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -2,8 +2,10 @@ using System.Linq; using System.Numerics; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization; @@ -174,7 +176,8 @@ public static Color ClampColor(Color color) return new(color.RByte, color.GByte, color.BByte); } - public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex) + public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex, + ISharedDiscordRoleManager? discordRoles = null, ICommonSession? session = null) // Arcane { var hairStyleId = appearance.HairStyleId; var facialHairStyleId = appearance.FacialHairStyleId; @@ -202,6 +205,7 @@ public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearanc { markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto); markingSet.EnsureValid(markingManager); + markingSet.EnsureSponsorRoles(discordRoles, session, markingManager); // Arcane var strategy = proto.Index(speciesProto.SkinColoration).Strategy; skinColor = strategy.EnsureVerified(skinColor); diff --git a/Content.Shared/Humanoid/Markings/MarkingManager.cs b/Content.Shared/Humanoid/Markings/MarkingManager.cs index 95b2846fe3..544bdf2e88 100644 --- a/Content.Shared/Humanoid/Markings/MarkingManager.cs +++ b/Content.Shared/Humanoid/Markings/MarkingManager.cs @@ -2,7 +2,9 @@ using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Shared.Humanoid.Markings @@ -49,6 +51,33 @@ public FrozenDictionary MarkingsByCategory(MarkingCate return CategorizedMarkings[category]; } + // Arcane-Start + /// + /// Filters out markings that require a sponsor role the session does not have. + /// + public IReadOnlyDictionary FilterSponsorMarkings( + IReadOnlyDictionary source, + ISharedDiscordRoleManager? discordRoles, + ICommonSession? session) + { + if (discordRoles == null || session == null) + { + return source; + } + + var filtered = new Dictionary(); + foreach (var (id, prototype) in source) + { + if (prototype.CanUse(discordRoles, session)) + { + filtered.Add(id, prototype); + } + } + + return filtered; + } + // Arcane-End + /// /// Markings by category and species. /// diff --git a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs index 4f031d2547..c810d354e8 100644 --- a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs +++ b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +using Content.Shared._Arcane.DiscordRoles; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -44,6 +46,22 @@ public sealed partial class MarkingPrototype : IPrototype [DataField("sprites", required: true)] public List Sprites { get; private set; } = default!; + // Arcane-Start + /// + /// Discord roles that unlock this marking. Empty means no restriction. + /// + [DataField("sponsorRoles")] + public HashSet SponsorRoles { get; private set; } = new(); + + public bool CanUse(ISharedDiscordRoleManager? discordRoles, ICommonSession? session) + { + return SponsorRoles.Count == 0 + || discordRoles != null + && session != null + && discordRoles.HasAnyRole(session, SponsorRoles); + } + // Arcane-End + /// Impstation start [DataField] public string? Shader { get; private set; } = null; diff --git a/Content.Shared/Humanoid/Markings/MarkingsSet.cs b/Content.Shared/Humanoid/Markings/MarkingsSet.cs index 440a28c2be..72b077cc15 100644 --- a/Content.Shared/Humanoid/Markings/MarkingsSet.cs +++ b/Content.Shared/Humanoid/Markings/MarkingsSet.cs @@ -3,7 +3,9 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -234,6 +236,47 @@ public void EnsureSexes(Sex sex, MarkingManager? markingManager = null) } } + // Arcane-Start + /// + /// Removes markings that require a sponsor role the session does not have. + /// + /// Discord role manager, or null to skip filtering. + /// The session to check, or null to skip filtering. + /// Marking manager. + public void EnsureSponsorRoles(ISharedDiscordRoleManager? discordRoles, ICommonSession? session, + MarkingManager? markingManager = null) + { + if (discordRoles == null || session == null) + { + return; + } + + IoCManager.Resolve(ref markingManager); + + var toRemove = new List<(MarkingCategories category, string id)>(); + + foreach (var (category, list) in Markings) + { + foreach (var marking in list) + { + if (!markingManager.TryGetMarking(marking, out var prototype)) + { + continue; + } + + if (!prototype.CanUse(discordRoles, session)) + { + toRemove.Add((category, marking.MarkingId)); + } + } + } + + foreach (var remove in toRemove) + { + Remove(remove.category, remove.id); + } + } + /// /// Ensures that all markings in this set are valid. /// diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 55a87ff56f..27b370be5b 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -61,6 +61,7 @@ using System.Linq; using System.Text.RegularExpressions; using Content.Shared._Arcane.CCVars; +using Content.Shared._Arcane.DiscordRoles; using Content.Shared._Arcane.TTS; using Content.Shared.CCVar; using Content.Shared.GameTicking; @@ -1107,7 +1108,8 @@ public void EnsureValid(ICommonSession session, IDependencyCollection collection width = Math.Clamp(Width, speciesPrototype.MinWidth, speciesPrototype.MaxWidth); // end Goobstation: port EE height/width sliders - var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex); + var discordRoles = collection.Resolve(); // Arcane + var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex, discordRoles, session); // Arcane-Edit var prefsUnavailableMode = PreferenceUnavailable switch { diff --git a/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs b/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs index 683da8c44f..17070a45b6 100644 --- a/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs +++ b/Content.Shared/_Arcane/Sponsor/SponsorRoleBenefits.cs @@ -17,7 +17,7 @@ public static class SponsorRoleBenefits [DiscordRole.SponsorTier1] = new("Tier1", "#7d25a8", 2, -1), [DiscordRole.SponsorTier2] = new("Tier2", "#d8aa2d", 3, -2), [DiscordRole.AdminBenefit] = new("AdminBenefit", "#78ecf5", 5, -3), - [DiscordRole.ArtLead] = new("ArtRole", "#ffdaf9", 6, -4) + [DiscordRole.ArtLead] = new("ArtLead", "#ffdaf9", 6, -4) }; private static readonly DiscordRole[] Priority = diff --git a/Resources/Locale/ru-RU/_Arcane/markings/humanoid_xeno.ftl b/Resources/Locale/ru-RU/_Arcane/markings/humanoid_xeno.ftl index 844113d260..8282dc2cbf 100644 --- a/Resources/Locale/ru-RU/_Arcane/markings/humanoid_xeno.ftl +++ b/Resources/Locale/ru-RU/_Arcane/markings/humanoid_xeno.ftl @@ -21,6 +21,7 @@ marking-HumanoidXenoTailOpress = Хвост угнетателя marking-HumanoidXenoTailValkyr = Хвост валькирии marking-HumanoidXenoTailDefender = Хвост защитника marking-HumanoidXenoTailWarrior = Хвост воина +marking-HumanoidXenoTailRen = Хвост Рэн marking-HumanoidXenoHeadDrone = Голова дрона marking-HumanoidXenoHeadSpitter = Голова плевателя marking-HumanoidXenoHeadPretor = Голова претора @@ -44,6 +45,7 @@ marking-HumanoidXenoHeadBurrower = Голова бурильщика marking-HumanoidXenoHeadDefender = Голова защитника marking-HumanoidXenoHeadValkyr = Голова валькирии marking-HumanoidXenoHeadWarrior = Голова воина +marking-HumanoidXenoHeadRen = Голова Рэн marking-HumanoidXenoChestPredalien = Грудь предалиена marking-HumanoidXenoChestSpitter = Грудь плевателя marking-HumanoidXenoChestMuscles = "Мышцы" @@ -63,6 +65,7 @@ marking-HumanoidXenoTracheasCougar = Трахеи пумы marking-HumanoidXenoTracheasCrocodile = Трахеи крокодила marking-HumanoidXenoTracheasBers = Трахеи берсерка marking-HumanoidXenoTracheasWarrior = Трахеи воина +marking-HumanoidXenoTracheasRen = Трахеи Рэн marking-HumanoidXenoLegsPredalien = Ноги предалиена marking-HumanoidXenoLegsMuscles = "Мышцы" marking-HumanoidXenoLegsCougar = Ноги пумы diff --git a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml index 37b9ddc5d6..0004ce0984 100644 --- a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml +++ b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml @@ -268,6 +268,7 @@ bodyPart: Tail markingCategory: Tail speciesRestriction: [HumanoidXeno] + sponsorRoles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tail.rsi state: ren_tail @@ -582,6 +583,7 @@ bodyPart: Head markingCategory: Head speciesRestriction: [HumanoidXeno] + sponsorRoles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/head.rsi state: ren_head @@ -768,6 +770,7 @@ bodyPart: Chest markingCategory: Tracheas speciesRestriction: [HumanoidXeno] + sponsorRoles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tracheas.rsi state: ren_tubes From 8e11950fc74d43a4af67debf43883428f3cd8a31 Mon Sep 17 00:00:00 2001 From: ReWAFFlution Date: Wed, 16 Sep 2026 19:32:13 +0300 Subject: [PATCH 4/5] old new metod --- Content.Client/Humanoid/MarkingPicker.xaml.cs | 12 +++++-- .../Humanoid/SingleMarkingPicker.xaml.cs | 29 ++++++++--------- .../Humanoid/HumanoidCharacterAppearance.cs | 2 +- .../Markings/Effects/MarkingEffect.cs | 24 ++++++++++++++ .../Humanoid/Markings/MarkingManager.cs | 31 +----------------- .../Humanoid/Markings/MarkingPrototype.cs | 26 +++++++++++---- .../Humanoid/Markings/MarkingsSet.cs | 11 ++----- .../SponsorRequirementMarkingEffect.cs | 32 +++++++++++++++++++ .../Locale/ru-RU/_Arcane/sponsor/sponsor.ftl | 1 + .../Customization/Markings/humanoid_xeno.yml | 12 +++++-- 10 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 Content.Shared/Humanoid/Markings/Effects/MarkingEffect.cs create mode 100644 Content.Shared/_Arcane/Humanoid/Markings/Effects/SponsorRequirementMarkingEffect.cs diff --git a/Content.Client/Humanoid/MarkingPicker.xaml.cs b/Content.Client/Humanoid/MarkingPicker.xaml.cs index ce32725f6f..0eaee8b8cd 100644 --- a/Content.Client/Humanoid/MarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/MarkingPicker.xaml.cs @@ -211,11 +211,9 @@ private List GetMarkingStateNames(MarkingPrototype marking) private IReadOnlyDictionary GetMarkings(MarkingCategories category) { - var markings = IgnoreSpecies // Arcane-Edit + return IgnoreSpecies ? _markingManager.MarkingsByCategoryAndSex(category, _currentSex) : _markingManager.MarkingsByCategoryAndSpeciesAndSex(category, _currentSpecies, _currentSex); - - return _markingManager.FilterSponsorMarkings(markings, _discordRoles, _player.LocalSession); // Arcane } public void Populate(string filter) @@ -239,6 +237,14 @@ public void Populate(string filter) var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", _sprite.Frame0(marking.Sprites[0])); item.Metadata = marking; + + // Arcane-Start + if (!marking.CanUse(_discordRoles, _player.LocalSession, out var reason)) + { + item.Disabled = true; + item.TooltipText = reason?.ToString(); + } + // Arcane-End } CMarkingPoints.Visible = _currentMarkings.PointsLeft(_selectedMarkingCategory) != -1; diff --git a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs index 989424d28b..024d5ff2e4 100644 --- a/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/SingleMarkingPicker.xaml.cs @@ -163,13 +163,7 @@ public void UpdateData(List markings, string species, int totalPoints) _species = species; _totalPoints = totalPoints; - // Arcane-Edit-Start - // _markingManager.MarkingsByCategoryAndSpecies(Category, _species) - _markingPrototypeCache = _markingManager.FilterSponsorMarkings( - _markingManager.MarkingsByCategoryAndSpecies(Category, _species), - _discordRoles, - _player.LocalSession); - // Arcane-Edit-End + _markingPrototypeCache = _markingManager.MarkingsByCategoryAndSpecies(Category, _species); Visible = _markingPrototypeCache.Count != 0; if (_markingPrototypeCache.Count == 0) @@ -189,13 +183,9 @@ public void PopulateList(string filter) throw new ArgumentException("Tried to populate marking list without a set species!"); } - // Arcane-Edit-Start - // _markingManager.MarkingsByCategoryAndSpecies(Category, _species) - _markingPrototypeCache ??= _markingManager.FilterSponsorMarkings( - _markingManager.MarkingsByCategoryAndSpecies(Category, _species), - _discordRoles, - _player.LocalSession); - // Arcane-Edit-End + // Arcane-Start + _markingPrototypeCache ??= _markingManager.MarkingsByCategoryAndSpecies(Category, _species); + // Arcane-End MarkingSelectorContainer.Visible = _markings != null && _markings.Count != 0; if (_markings == null || _markings.Count == 0) @@ -215,7 +205,16 @@ public void PopulateList(string filter) var item = MarkingList.AddItem(Loc.GetString($"marking-{id}"), _sprite.Frame0(marking.Sprites[0])); item.Metadata = marking.ID; - if (_markings[Slot].MarkingId == id) + // Arcane-Start + var isCurrent = _markings![Slot].MarkingId == id; + if (!isCurrent && !marking.CanUse(_discordRoles, _player.LocalSession, out var reason)) + { + item.Disabled = true; + item.TooltipText = reason?.ToString(); + } + // Arcane-End + + if (isCurrent) { _ignoreItemSelected = true; item.Selected = true; diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index e401f5a710..7d62e64a10 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -205,7 +205,7 @@ public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearanc { markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto); markingSet.EnsureValid(markingManager); - markingSet.EnsureSponsorRoles(discordRoles, session, markingManager); // Arcane + markingSet.EnsureEffects(discordRoles, session, markingManager); // Arcane var strategy = proto.Index(speciesProto.SkinColoration).Strategy; skinColor = strategy.EnsureVerified(skinColor); diff --git a/Content.Shared/Humanoid/Markings/Effects/MarkingEffect.cs b/Content.Shared/Humanoid/Markings/Effects/MarkingEffect.cs new file mode 100644 index 0000000000..8fe6b7fce9 --- /dev/null +++ b/Content.Shared/Humanoid/Markings/Effects/MarkingEffect.cs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +using System.Diagnostics.CodeAnalysis; +using Content.Shared._Arcane.DiscordRoles; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared.Humanoid.Markings.Effects; + +/// +/// Base class for effects that gate whether a marking may be used by a given session. +/// +[ImplicitDataDefinitionForInheritors] +public abstract partial class MarkingEffect +{ + /// + /// Tries to validate whether the marking is available to the session. + /// + public abstract bool Validate( + MarkingPrototype marking, + ICommonSession? session, + ISharedDiscordRoleManager? discordRoles, + [NotNullWhen(false)] out FormattedMessage? reason); +} diff --git a/Content.Shared/Humanoid/Markings/MarkingManager.cs b/Content.Shared/Humanoid/Markings/MarkingManager.cs index 544bdf2e88..0a501e35d4 100644 --- a/Content.Shared/Humanoid/Markings/MarkingManager.cs +++ b/Content.Shared/Humanoid/Markings/MarkingManager.cs @@ -2,9 +2,7 @@ using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; -using Content.Shared._Arcane.DiscordRoles; using Content.Shared.Humanoid.Prototypes; -using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Shared.Humanoid.Markings @@ -51,35 +49,8 @@ public FrozenDictionary MarkingsByCategory(MarkingCate return CategorizedMarkings[category]; } - // Arcane-Start /// - /// Filters out markings that require a sponsor role the session does not have. - /// - public IReadOnlyDictionary FilterSponsorMarkings( - IReadOnlyDictionary source, - ISharedDiscordRoleManager? discordRoles, - ICommonSession? session) - { - if (discordRoles == null || session == null) - { - return source; - } - - var filtered = new Dictionary(); - foreach (var (id, prototype) in source) - { - if (prototype.CanUse(discordRoles, session)) - { - filtered.Add(id, prototype); - } - } - - return filtered; - } - // Arcane-End - - /// - /// Markings by category and species. + /// Filters and colors markings based on species and it's restrictions in the marking's prototype from this marking set. /// /// /// diff --git a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs index c810d354e8..26ef208d81 100644 --- a/Content.Shared/Humanoid/Markings/MarkingPrototype.cs +++ b/Content.Shared/Humanoid/Markings/MarkingPrototype.cs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT using Content.Shared._Arcane.DiscordRoles; +using Content.Shared.Humanoid.Markings.Effects; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -48,17 +49,28 @@ public sealed partial class MarkingPrototype : IPrototype // Arcane-Start /// - /// Discord roles that unlock this marking. Empty means no restriction. + /// Effects that gate whether this marking may be used by a given session. Empty means no restriction. /// - [DataField("sponsorRoles")] - public HashSet SponsorRoles { get; private set; } = new(); + [DataField("effects")] + public List Effects { get; private set; } = new(); public bool CanUse(ISharedDiscordRoleManager? discordRoles, ICommonSession? session) + => CanUse(discordRoles, session, out _); + + public bool CanUse(ISharedDiscordRoleManager? discordRoles, ICommonSession? session, out FormattedMessage? reason) { - return SponsorRoles.Count == 0 - || discordRoles != null - && session != null - && discordRoles.HasAnyRole(session, SponsorRoles); + reason = null; + + if (discordRoles == null || session == null) + return true; + + foreach (var effect in Effects) + { + if (!effect.Validate(this, session, discordRoles, out reason)) + return false; + } + + return true; } // Arcane-End diff --git a/Content.Shared/Humanoid/Markings/MarkingsSet.cs b/Content.Shared/Humanoid/Markings/MarkingsSet.cs index 72b077cc15..31d329e11f 100644 --- a/Content.Shared/Humanoid/Markings/MarkingsSet.cs +++ b/Content.Shared/Humanoid/Markings/MarkingsSet.cs @@ -238,19 +238,14 @@ public void EnsureSexes(Sex sex, MarkingManager? markingManager = null) // Arcane-Start /// - /// Removes markings that require a sponsor role the session does not have. + /// Removes markings whose effects reject the session. /// - /// Discord role manager, or null to skip filtering. + /// Discord role manager, used by sponsor effects. /// The session to check, or null to skip filtering. /// Marking manager. - public void EnsureSponsorRoles(ISharedDiscordRoleManager? discordRoles, ICommonSession? session, + public void EnsureEffects(ISharedDiscordRoleManager? discordRoles, ICommonSession? session, MarkingManager? markingManager = null) { - if (discordRoles == null || session == null) - { - return; - } - IoCManager.Resolve(ref markingManager); var toRemove = new List<(MarkingCategories category, string id)>(); diff --git a/Content.Shared/_Arcane/Humanoid/Markings/Effects/SponsorRequirementMarkingEffect.cs b/Content.Shared/_Arcane/Humanoid/Markings/Effects/SponsorRequirementMarkingEffect.cs new file mode 100644 index 0000000000..17233222bb --- /dev/null +++ b/Content.Shared/_Arcane/Humanoid/Markings/Effects/SponsorRequirementMarkingEffect.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared._Arcane.DiscordRoles; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Humanoid.Markings.Effects; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared._Arcane.Humanoid.Markings.Effects; + +/// +/// Verifies possession of a specific Discord role that unlocks the sponsor marking +/// +public sealed partial class SponsorRequirementMarkingEffect : MarkingEffect +{ + [DataField(required: true)] + public HashSet Roles; + + public override bool Validate( + MarkingPrototype marking, + ICommonSession? session, + ISharedDiscordRoleManager? discordRoles, + [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = FormattedMessage.FromUnformatted(Loc.GetString("marking-sponsor-requirement")); + + if (session == null || discordRoles == null) + return true; + + return Roles.Any(role => discordRoles.HasRole(session, role)); + } +} diff --git a/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl b/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl index a265d9e185..80d9410369 100644 --- a/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl +++ b/Resources/Locale/ru-RU/_Arcane/sponsor/sponsor.ftl @@ -8,4 +8,5 @@ AdminBenefit-patron-name = [color=#ecad00]Хорошая Вахта[/color] ArtLead-patron-name = [color=#ffdaf9]Арт-Лид[/color] loadout-sponsor-requirement = Этот предмет доступен только для спонсоров! +marking-sponsor-requirement = Этот маркинг доступен только для спонсоров! loadout-group-patron = Спонсор diff --git a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml index 0004ce0984..60a52e2403 100644 --- a/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml +++ b/Resources/Prototypes/_Arcane/Entities/Mobs/Customization/Markings/humanoid_xeno.yml @@ -268,7 +268,9 @@ bodyPart: Tail markingCategory: Tail speciesRestriction: [HumanoidXeno] - sponsorRoles: [ArtLead] + effects: + - !type:SponsorRequirementMarkingEffect + roles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tail.rsi state: ren_tail @@ -583,7 +585,9 @@ bodyPart: Head markingCategory: Head speciesRestriction: [HumanoidXeno] - sponsorRoles: [ArtLead] + effects: + - !type:SponsorRequirementMarkingEffect + roles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/head.rsi state: ren_head @@ -770,7 +774,9 @@ bodyPart: Chest markingCategory: Tracheas speciesRestriction: [HumanoidXeno] - sponsorRoles: [ArtLead] + effects: + - !type:SponsorRequirementMarkingEffect + roles: [ArtLead] sprites: - sprite: _Arcane/Mobs/Customization/HumanoidXeno/tracheas.rsi state: ren_tubes From 5bf52cd56ee2425569c20e3ecddafbbe7573a5eb Mon Sep 17 00:00:00 2001 From: ReWAFFlution Date: Wed, 16 Sep 2026 23:26:58 +0300 Subject: [PATCH 5/5] fix p.1 --- Content.Client/Humanoid/MarkingPicker.xaml.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Content.Client/Humanoid/MarkingPicker.xaml.cs b/Content.Client/Humanoid/MarkingPicker.xaml.cs index 0eaee8b8cd..44b4f757c9 100644 --- a/Content.Client/Humanoid/MarkingPicker.xaml.cs +++ b/Content.Client/Humanoid/MarkingPicker.xaml.cs @@ -239,11 +239,7 @@ public void Populate(string filter) item.Metadata = marking; // Arcane-Start - if (!marking.CanUse(_discordRoles, _player.LocalSession, out var reason)) - { - item.Disabled = true; - item.TooltipText = reason?.ToString(); - } + SetMarkingUnavailableIfNeeded(item, marking); // Arcane-End } @@ -513,6 +509,15 @@ private void MarkingAdd() } var marking = (MarkingPrototype) _selectedUnusedMarking.Metadata!; + + // Arcane-Start + if (!marking.CanUse(_discordRoles, _player.LocalSession, out _)) + { + _selectedUnusedMarking = null; + return; + } + // Arcane-End + var markingObject = marking.AsMarking(); // We need add hair markings in cloned set manually because _currentMarkings doesn't have it @@ -585,9 +590,24 @@ private void MarkingRemove() { var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", _sprite.Frame0(marking.Sprites[0])); item.Metadata = marking; + + // Arcane-Start + SetMarkingUnavailableIfNeeded(item, marking); + // Arcane-End } _selectedMarking = null; CMarkingColors.Visible = false; OnMarkingRemoved?.Invoke(_currentMarkings); } + + // Arcane-Start + private void SetMarkingUnavailableIfNeeded(ItemList.Item item, MarkingPrototype marking) + { + if (!marking.CanUse(_discordRoles, _player.LocalSession, out var reason)) + { + item.Disabled = true; + item.TooltipText = reason?.ToString(); + } + } + // Arcane-End }