From 2502a9fffe01ce08cddb7d80b1c97b21dcfd6ca4 Mon Sep 17 00:00:00 2001 From: Astra <226853568+EmberAstra@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:56:01 +0100 Subject: [PATCH] New glimmer events! Foxfire & Restyle (#5102) * Add glimmer restyle and foxfire * Fix typo * Fix typo * Fix test fail and kobold hair bug (thanks to FaintSpeaker) * Update Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * Update Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * Update Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * Update Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * Update Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> * Only filter for potential psionics * Lower restyle glimmer requirement * Semicolon my beloathed * Popup false when going bald to bald * Update Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> --------- Signed-off-by: Astra <226853568+EmberAstra@users.noreply.github.com> Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Co-authored-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../GlimmerFoxfireSpawnRuleComponent.cs | 43 +++++++++++ .../Components/GlimmerRestyleRuleComponent.cs | 35 +++++++++ .../Events/GlimmerFoxfireSpawnRule.cs | 59 ++++++++++++++ .../Events/GlimmerRestyleRule.cs | 76 +++++++++++++++++++ .../Humanoid/Markings/MarkingManager.cs | 5 ++ .../Locale/en-US/_DV/abilities/psionic.ftl | 2 + .../_DV/GameRules/glimmer_events.yml | 36 +++++++++ 7 files changed, 256 insertions(+) create mode 100644 Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs create mode 100644 Content.Server/_DV/StationEvents/Components/GlimmerRestyleRuleComponent.cs create mode 100644 Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs create mode 100644 Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs diff --git a/Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs new file mode 100644 index 0000000000..dee55c9c01 --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/GlimmerFoxfireSpawnRuleComponent.cs @@ -0,0 +1,43 @@ +using Content.Server._DV.StationEvents.Events; +using Robust.Shared.Prototypes; + +namespace Content.Server._DV.StationEvents.Components; + +/// +/// Spawns a small amount of randomly-colored foxfires, +/// centered around either the Oracle or Sophic Grammateus. +/// +[RegisterComponent, Access(typeof(GlimmerFoxfireSpawnRule))] +public sealed partial class GlimmerFoxfireSpawnRuleComponent : Component +{ + /// + /// Minimum+ amounts of foxfires to spawn. + /// + [DataField] + public int MinimumSpawned = 10; + + /// + /// Maximum amounts of foxfires to spawn. + /// + [DataField] + public int MaximumSpawned = 20; + + /// + /// Maximum distance from the Oracle or Sophic Grammateus in which the + /// foxfire will be spawned. + /// + [DataField] + public int SpawnRange = 10; + + /// + /// Prototype to be spawned. + /// + [DataField] + public EntProtoId FoxfirePrototype = "Foxfire"; + + /// + /// Available colors for the foxfire's light. + /// + [DataField] + public List? RandomColorList = new(); +} diff --git a/Content.Server/_DV/StationEvents/Components/GlimmerRestyleRuleComponent.cs b/Content.Server/_DV/StationEvents/Components/GlimmerRestyleRuleComponent.cs new file mode 100644 index 0000000000..614b080d2d --- /dev/null +++ b/Content.Server/_DV/StationEvents/Components/GlimmerRestyleRuleComponent.cs @@ -0,0 +1,35 @@ +using Content.Server._DV.StationEvents.Events; + +namespace Content.Server._DV.StationEvents.Components; + +/// +/// Attempts to change the hair and facial hair markings, plus their color +/// of a small amount of people. +/// +[RegisterComponent, Access(typeof(GlimmerRestyleRule))] +public sealed partial class GlimmerRestyleRuleComponent : Component +{ + /// + /// Minimum number of valid targets that will get restyled. + /// + [DataField] + public int MinimumTargets = 1; + + /// + /// Maximum number of valid targets that will get restyled. + /// + [DataField] + public int MaximumTargets = 5; + + /// + /// Chance of completely removing all hair markings instead of selecting a random one. + /// + [DataField] + public float BaldChance = 0.2f; + + /// + /// Chance of completely removing all facial hair markings instead of selecting a random one. + /// + [DataField] + public float CleanShavenChance = 0.5f; +} diff --git a/Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs b/Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs new file mode 100644 index 0000000000..729b2a23e5 --- /dev/null +++ b/Content.Server/_DV/StationEvents/Events/GlimmerFoxfireSpawnRule.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using Content.Server._DV.StationEvents.Components; +using Content.Server.Nyanotrasen.Research.SophicScribe; +using Content.Server.Research.Oracle; +using Content.Server.StationEvents.Events; +using Content.Shared.GameTicking.Components; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server._DV.StationEvents.Events; + +public sealed class GlimmerFoxfireSpawnRule : StationEventSystem +{ + [Dependency] private readonly SharedPointLightSystem _light = default!; + + protected override void Started(EntityUid uid, + GlimmerFoxfireSpawnRuleComponent comp, + GameRuleComponent gameRule, + GameRuleStartedEvent args) + { + base.Started(uid, comp, gameRule, args); + + var locations = new List(); + var queryScribe = EntityQueryEnumerator(); + var queryOracle = EntityQueryEnumerator(); + + while (queryScribe.MoveNext(out _, out var transform)) + { + locations.Add(transform.Coordinates); + } + + while (queryOracle.MoveNext(out _, out var transform)) + { + locations.Add(transform.Coordinates); + } + + if (locations.Count == 0) + return; + + var selectedLocation = RobustRandom.Pick(locations); + + var amountToSpawn = RobustRandom.Next(comp.MinimumSpawned, comp.MaximumSpawned); + for (var i = 0; i < amountToSpawn; i++) + { + var spawnLocation = selectedLocation.Offset(new Vector2( + RobustRandom.Next(-comp.SpawnRange, comp.SpawnRange), + RobustRandom.Next(-comp.SpawnRange, comp.SpawnRange) + )); + + + var color = Color.GhostWhite; + if (comp.RandomColorList != null && comp.RandomColorList.Count != 0) + color = RobustRandom.Pick(comp.RandomColorList); + + var fireEnt = Spawn(comp.FoxfirePrototype, spawnLocation); + _light.SetColor(fireEnt, color); + } + } +} diff --git a/Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs b/Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs new file mode 100644 index 0000000000..46448957b1 --- /dev/null +++ b/Content.Server/_DV/StationEvents/Events/GlimmerRestyleRule.cs @@ -0,0 +1,76 @@ +using System.Linq; +using Content.Server._DV.StationEvents.Components; +using Content.Server.Psionics; +using Content.Server.StationEvents.Events; +using Content.Shared.Abilities.Psionics; +using Content.Shared.GameTicking.Components; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Mobs.Components; +using Content.Shared.SSDIndicator; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Robust.Shared.Random; + +namespace Content.Server._DV.StationEvents.Events; + +public sealed class GlimmerRestyleRule : StationEventSystem +{ + [Dependency] private readonly MobStateSystem _mob = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MarkingManager _markingManager = default!; + + protected override void Started(EntityUid uid, GlimmerRestyleRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, comp, gameRule, args); + + var query = EntityQueryEnumerator(); + List<(EntityUid, HumanoidAppearanceComponent)> potentialTargets = new(); + + while (query.MoveNext(out var entity, out var humanoid, out var mobState)) + { + if (!_mob.IsAlive(entity, mobState) || HasComp(entity) || !HasComp(entity)) + continue; + potentialTargets.Add((entity, humanoid)); + } + + _random.Shuffle(potentialTargets); + var targetsToRestyle = _random.Next(comp.MinimumTargets, comp.MaximumTargets); + + foreach (var (entity, humanoid) in potentialTargets) + { + if(HasComp(entity)) + continue; + + if (targetsToRestyle-- <= 0) + break; + + var changedHair = TryApplyRestyle((entity, humanoid), MarkingCategories.Hair, comp.BaldChance); + var changedFacialHair = TryApplyRestyle((entity, humanoid), MarkingCategories.FacialHair, comp.CleanShavenChance); + if (changedHair || changedFacialHair) + _popup.PopupEntity(Loc.GetString("glimmer-restyle-event"), entity, entity, PopupType.Medium); + Dirty(entity, humanoid); + } + } + + private bool TryApplyRestyle(Entity ent, MarkingCategories category, float noMarkingsChance) + { + var newMarkingColor = new Color(_random.NextFloat(), _random.NextFloat(), _random.NextFloat()); + var availableMarkings = _markingManager.MarkingsByCategoryAndSpecies(category, ent.Comp.Species); + if (availableMarkings.Count == 0) + return false; + + var hadCategoryBefore = ent.Comp.MarkingSet.TryGetCategory(category, out _); + ent.Comp.MarkingSet.RemoveCategory(category); + if (_random.Prob(noMarkingsChance)) + return hadCategoryBefore; //Do not show the popup if you go from no markings to no markings. + + var newMarking = _random.Pick(availableMarkings.Values.ToList()).AsMarking(); + newMarking.SetColor(newMarkingColor); + + ent.Comp.MarkingSet.AddCategory(category); + ent.Comp.MarkingSet.AddFront(category, newMarking); + return true; + } +} diff --git a/Content.Shared/Humanoid/Markings/MarkingManager.cs b/Content.Shared/Humanoid/Markings/MarkingManager.cs index 28637f9303..d443e96235 100644 --- a/Content.Shared/Humanoid/Markings/MarkingManager.cs +++ b/Content.Shared/Humanoid/Markings/MarkingManager.cs @@ -65,6 +65,11 @@ namespace Content.Shared.Humanoid.Markings var markingPoints = _prototypeManager.Index(speciesProto.MarkingPoints); var res = new Dictionary(); + // Begin DeltaV addition - prevents errors when category is missing + if (!markingPoints.Points.ContainsKey(category)) + return res; + // End DeltaV addition + foreach (var (key, marking) in MarkingsByCategory(category)) { if ((markingPoints.OnlyWhitelisted || markingPoints.Points[category].OnlyWhitelisted) && marking.SpeciesRestrictions == null) diff --git a/Resources/Locale/en-US/_DV/abilities/psionic.ftl b/Resources/Locale/en-US/_DV/abilities/psionic.ftl index 66b2273ced..30f23aa7ad 100644 --- a/Resources/Locale/en-US/_DV/abilities/psionic.ftl +++ b/Resources/Locale/en-US/_DV/abilities/psionic.ftl @@ -92,3 +92,5 @@ fractured-form-nobodies = You have no alternate forms to switch to! fractured-form-sleepy = You feel very sleepy... You should find somewhere to rest. fractured-form-ssd = { CAPITALIZE(SUBJECT($ent)) } { CONJUGATE-BE($ent) } in a deep sleep. { CAPITALIZE(POSS-ADJ($ent)) } eyes seem to be darting around as if dreaming. fractured-form-examine-self = You feel a strange connection to { OBJECT($ent) }. + +glimmer-restyle-event = You feel like something changed about your looks... diff --git a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml index 7d47d72483..8120eb75e2 100644 --- a/Resources/Prototypes/_DV/GameRules/glimmer_events.yml +++ b/Resources/Prototypes/_DV/GameRules/glimmer_events.yml @@ -17,6 +17,8 @@ #- id: LockProbers - id: PsionicNosebleedEvent - id: MinorMassMindSwap # Delta V + - id: GlimmerFoxfireSpawn + - id: GlimmerRestyle - type: entity parent: BaseGameRule @@ -197,3 +199,37 @@ - type: GlimmerEvent minimumGlimmer: 350 - type: PsionicNosebleedRule + +- type: entity + parent: BaseGlimmerEvent + id: GlimmerFoxfireSpawn + components: + - type: GlimmerEvent + minimumGlimmer: 100 + maximumGlimmer: 500 + - type: GlimmerFoxfireSpawnRule + randomColorList: + - aqua + - betterviolet + - blue + - chartreuse + - cyan + - deeppink + - fuchsia + - green + - indigo + - lime + - pink + - red + - silver + - yellow + + +- type: entity + id: GlimmerRestyle + parent: BaseGlimmerSignaturesEvent + components: + - type: GlimmerEvent + minimumGlimmer: 300 + maximumGlimmer: 700 + - type: GlimmerRestyleRule