From a6df32a056739c72422fe61ea0f1d9538c446658 Mon Sep 17 00:00:00 2001 From: Sendir Date: Fri, 3 Jul 2026 21:04:47 +0100 Subject: [PATCH 1/5] Polymorph anom - Missing testing and fixing persistant saves - Anom core sprite work and name. Haven't locked at it at all - Remove the logs spread around the PolymorphSystem.cs that were used during development --- .../Anomaly/Effects/PolymorphAnomalySystem.cs | 189 +++++ .../Polymorph/Systems/PolymorphSystem.cs | 130 ++- .../Components/PolymorphAnomalyComponent.cs | 47 ++ .../PolymorphAnomalyTablePrototype.cs | 53 ++ .../Anomaly/polymorph_anomaly_tables.yml | 394 +++++++++ .../Structures/Specific/Anomaly/anomalies.yml | 33 + .../Polymorphs/anomaly_polymorphs.yml | 788 ++++++++++++++++++ .../polymorph_anom.rsi/anom-pulse.png | Bin 0 -> 14787 bytes .../Anomalies/polymorph_anom.rsi/anom.png | Bin 0 -> 9158 bytes .../Anomalies/polymorph_anom.rsi/meta.json | 61 ++ 10 files changed, 1682 insertions(+), 13 deletions(-) create mode 100644 Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs create mode 100644 Content.Shared/Anomaly/Effects/Components/PolymorphAnomalyComponent.cs create mode 100644 Content.Shared/Anomaly/Prototypes/PolymorphAnomalyTablePrototype.cs create mode 100644 Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml create mode 100644 Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml create mode 100644 Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom-pulse.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/meta.json diff --git a/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs new file mode 100644 index 00000000000..14b4790d80b --- /dev/null +++ b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs @@ -0,0 +1,189 @@ +using System.Linq; +using Content.Server.Polymorph.Systems; +using Content.Shared.Anomaly.Components; +using Content.Shared.Anomaly.Effects.Components; +using Content.Shared.Anomaly.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Inventory; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Polymorph; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Anomaly.Effects; + +/// +/// This handles , forcibly polymorphing everything +/// alive in range into a random entity for a random duration whenever the anomaly pulses. +/// +public sealed class PolymorphAnomalySystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + /// + /// Fallback thresholds applied to any polymorph destination that turns out to have no + /// MobState/MobThresholds configured at all (a content gap on that specific prototype). + /// Without this, such a creature is structurally incapable of ever reaching Critical/Dead, + /// no matter how much damage it takes, and revert-on-crit/death would never fire for it. + /// These are rough generic values, not tuned per-species - tune the actual prototype's own + /// MobThresholds later and this fallback will simply stop triggering for it. + /// + private const float FallbackCriticalThreshold = 45f; + private const float FallbackDeadThreshold = 65f; + + /// Pre-allocated and re-used collection. + private readonly HashSet> _targets = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPulse); + SubscribeLocalEvent(OnSupercritical); + } + + private void OnPulse(Entity ent, ref AnomalyPulseEvent args) + { + if (!_proto.TryIndex(ent.Comp.PolymorphTable, out var table)) + return; + + var coordinates = _transform.GetMapCoordinates(ent); + PolymorphInRange(ent, table, coordinates, ent.Comp.Range, args.Severity, permanentChance: 0f); + } + + private void OnSupercritical(Entity ent, ref AnomalySupercriticalEvent args) + { + if (!_proto.TryIndex(ent.Comp.PolymorphTable, out var table)) + return; + + var coordinates = _transform.GetMapCoordinates(ent); + + PolymorphInRange(ent, table, coordinates, ent.Comp.SupercriticalRange, severity: 1f, ent.Comp.SupercriticalPermanentChance); + } + + /// + /// Grabs everything alive in range and forcibly polymorphs each of them independently, + /// rolling a separate outcome and duration per victim. + /// + private void PolymorphInRange( + Entity ent, + PolymorphAnomalyTablePrototype table, + MapCoordinates coordinates, + float range, + float severity, + float permanentChance) + { + if (table.Options.Count == 0) + return; + + _targets.Clear(); + _lookup.GetEntitiesInRange(coordinates, range, _targets); + + foreach (var target in _targets) + { + if (!_mobState.IsAlive(target)) + continue; + + var option = PickOption(table); + if (!_proto.TryIndex(option.Polymorph, out var polymorphProto)) + continue; + + var permanent = permanentChance > 0f && _random.Prob(permanentChance); + + // TransferEntityInventories requires BOTH the source (the thing being polymorphed) + // AND the destination (the new form it's spawning into) to have InventoryComponent + var sourceHasInventory = HasComp(target); + var destHasInventory = _proto.Index(polymorphProto.Configuration.Entity) + .TryGetComponent(out _, EntityManager.ComponentFactory); + var hasInventory = sourceHasInventory && destHasInventory; + + var config = polymorphProto.Configuration with + { + Forced = true, + Inventory = hasInventory ? PolymorphInventoryChange.Transfer : PolymorphInventoryChange.None, + RevertOnCrit = true, + RevertOnDeath = true, + AllowRepeatedMorphs = true, + Duration = permanent ? null : (int)RollDuration(option, severity).TotalSeconds, + }; + + Log.Info($"PolymorphAnomaly: {ToPrettyString(target)} -> {option.Polymorph} | sourceHasInventory={sourceHasInventory} destHasInventory={destHasInventory} | Inventory={config.Inventory}"); + + var child = _polymorph.PolymorphEntity(target, config); + if (child != null) + EnsureCanDieOrCrit(child.Value); + } + + if (ent.Comp.PolymorphSound != null) + _audio.PlayPvs(ent.Comp.PolymorphSound, ent); + } + + /// + /// Safety net for polymorph destinations that are missing MobState/MobThresholds entirely - + /// without this, RevertOnCrit/RevertOnDeath can never fire for them since there's no data + /// telling the game at what damage value to change state. Only fills in fallback values if + /// the entity has none configured at all; a prototype with its own real MobThresholds is + /// left completely untouched. + /// + private void EnsureCanDieOrCrit(EntityUid child) + { + if (_mobThreshold.TryGetDeadThreshold(child, out _)) + return; + + Log.Warning($"PolymorphAnomaly: {ToPrettyString(child)} has no MobThresholds configured - it would never be able to die or crit. Applying fallback thresholds so revert-on-death/crit still works. Consider adding real MobState/MobThresholds to this prototype."); + + EnsureComp(child); + EnsureComp(child); + + _mobThreshold.SetMobStateThreshold(child, FixedPoint2.Zero, MobState.Alive); + _mobThreshold.SetMobStateThreshold(child, FallbackCriticalThreshold, MobState.Critical); + _mobThreshold.SetMobStateThreshold(child, FallbackDeadThreshold, MobState.Dead); + } + + /// + /// Picks a random option from the table, weighted by . + /// + private PolymorphAnomalyOption PickOption(PolymorphAnomalyTablePrototype table) + { + var totalWeight = table.Options.Sum(o => o.Weight); + var roll = _random.NextFloat() * totalWeight; + + var cumulative = 0f; + foreach (var option in table.Options) + { + cumulative += option.Weight; + if (roll <= cumulative) + return option; + } + + // Floating point rounding fallback. + return table.Options[^1]; + } + + /// + /// Rolls a duration between an option's min and max, biased towards the max end + /// as anomaly severity increases. + /// + private TimeSpan RollDuration(PolymorphAnomalyOption option, float severity) + { + var t = _random.NextFloat(); + + var exponent = MathF.Max(0.1f, 1f - Math.Clamp(severity, 0f, 1f)); + var biasedT = MathF.Pow(t, exponent); + + var seconds = MathHelper.Lerp((float)option.MinDuration.TotalSeconds, (float)option.MaxDuration.TotalSeconds, biasedT); + //return TimeSpan.FromSeconds(seconds); + return TimeSpan.FromSeconds(6000); + } +} diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index 6b769e4e5fc..b81dd67139f 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Destructible; +using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Mind; @@ -43,13 +44,36 @@ public sealed partial class PolymorphSystem : EntitySystem [Dependency] private readonly SharedVisualBodySystem _visualBody = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private ILogManager _log = default!; + private ISawmill _sawmill = default!; private const string RevertPolymorphId = "ActionRevertPolymorph"; + /// Accumulator for the throttled per-tick debug dump below. + private float _debugDumpTimer; + + /// + /// Tracks every currently-polymorphed entity ourselves, since EntityQueryEnumerator silently + /// skips paused entities and we don't want to force them to stay awake just to be checked - + /// that would undo the whole point of NPCs going to sleep when idle. Added/removed via the + /// component's own lifecycle, so it stays accurate regardless of pause state. + /// + private readonly HashSet _polymorphed = new(); + + /// + /// Reusable snapshot buffer. Revert() can remove entries from _polymorphed mid-iteration + /// (deleting the old entity fires ComponentShutdown), so iterating the live set directly + /// isn't safe - we copy it first each tick. + /// + private readonly List _updateBuffer = new(); public override void Initialize() { + _sawmill = _log.GetSawmill("polymorph"); + SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPolymorphedStartup); + SubscribeLocalEvent(OnPolymorphedShutdown); SubscribeLocalEvent(OnPolymorphActionEvent); SubscribeLocalEvent(OnRevertPolymorphActionEvent); @@ -61,15 +85,46 @@ public override void Initialize() InitializeMap(); } + private void OnPolymorphedStartup(Entity ent, ref ComponentStartup args) + { + _polymorphed.Add(ent); + } + + private void OnPolymorphedShutdown(Entity ent, ref ComponentShutdown args) + { + _polymorphed.Remove(ent); + } + public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) + _debugDumpTimer += frameTime; + var doDebugDump = _debugDumpTimer >= 0.5f; + if (doDebugDump) + _debugDumpTimer = 0f; + + if (_polymorphed.Count == 0) + return; + + // Snapshot first - Revert() can remove entries from _polymorphed mid-loop. + _updateBuffer.Clear(); + _updateBuffer.AddRange(_polymorphed); + + foreach (var uid in _updateBuffer) { + if (!TryComp(uid, out var comp) || comp.Reverted) + continue; + comp.Time += frameTime; + if (doDebugDump) + { + TryComp(uid, out var debugMob); + var debugTotalDamage = HasComp(uid) ? _damageable.GetTotalDamage(uid) : (FixedPoint2?)null; + _sawmill.Info($"[HP DEBUG] {ToPrettyString(uid)} | state={debugMob?.CurrentState} | totalDamage={debugTotalDamage} | time={comp.Time:F1}/{comp.Configuration.Duration}"); + } + if (comp.Configuration.Duration != null && comp.Time >= comp.Configuration.Duration) { Revert((uid, comp)); @@ -82,7 +137,9 @@ public override void Update(float frameTime) if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) || comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob)) { - Revert((uid, comp)); + _sawmill.Info($"Update() detected death/crit for {ToPrettyString(uid)}, calling Revert()"); + var result = Revert((uid, comp)); + _sawmill.Info($"Revert() from Update() returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); } } } @@ -142,19 +199,27 @@ private void OnBeforeFullySliced(Entity ent, ref Bef /// private void OnDestruction(Entity ent, ref DestructionEventArgs args) { + _sawmill.Info($"OnDestruction fired for {ToPrettyString(ent)}, Reverted={ent.Comp.Reverted}, RevertOnDeath={ent.Comp.Configuration.RevertOnDeath}"); + if (ent.Comp.Reverted || !ent.Comp.Configuration.RevertOnDeath) return; - Revert((ent, ent)); + var result = Revert((ent, ent)); + _sawmill.Info($"Revert() from OnDestruction returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); } private void OnPolymorphedTerminating(Entity ent, ref EntityTerminatingEvent args) { + _sawmill.Info($"OnPolymorphedTerminating fired for {ToPrettyString(ent)}, Reverted={ent.Comp.Reverted}, RevertOnDelete={ent.Comp.Configuration.RevertOnDelete}, Parent={ent.Comp.Parent}"); + if (ent.Comp.Reverted) return; if (ent.Comp.Configuration.RevertOnDelete) - Revert(ent.AsNullable()); + { + var result = Revert(ent.AsNullable()); + _sawmill.Info($"Revert() from OnPolymorphedTerminating returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); + } // Remove our original entity too // Note that Revert will set Parent to null, so reverted entities will not be deleted @@ -181,9 +246,12 @@ private void OnPolymorphedTerminating(Entity ent, re public EntityUid? PolymorphEntity(EntityUid uid, PolymorphConfiguration configuration) { // If they're morphed, check their current config to see if they can be - // morphed again + // morphed again. TryComp is called unconditionally (rather than inline in the && + // chain) so currentPoly is always definitely-assigned for later use tracking chained + // re-polymorphs, regardless of whether IgnoreAllowRepeatedMorphs short-circuits this. + TryComp(uid, out var currentPoly); if (!configuration.IgnoreAllowRepeatedMorphs - && TryComp(uid, out var currentPoly) + && currentPoly != null && !currentPoly.Configuration.AllowRepeatedMorphs) return null; @@ -212,8 +280,16 @@ private void OnPolymorphedTerminating(Entity ent, re _mindSystem.MakeSentient(child); + // If uid is itself already a polymorphed form (a chained re-polymorph), the new form's Parent must point at the + // TRUE original entity, not this intermediate form - otherwise each further re-polymorph + // only remembers one link back, and Revert() eventually unwinds to some intermediate + // creature instead of all the way back to what the victim actually started as. + var trueOriginal = uid; + if (currentPoly != null && currentPoly.Parent != null) + trueOriginal = currentPoly.Parent.Value; + var polymorphedComp = Factory.GetComponent(); - polymorphedComp.Parent = uid; + polymorphedComp.Parent = trueOriginal; polymorphedComp.Configuration = configuration; AddComp(child, polymorphedComp); @@ -268,10 +344,22 @@ private void OnPolymorphedTerminating(Entity ent, re if (_mindSystem.TryGetMind(uid, out var mindId, out var mind)) _mindSystem.TransferTo(mindId, child, mind: mind); - //Ensures a map to banish the entity to - EnsurePausedMap(); - if (PausedMap != null) - _transform.SetParent(uid, targetTransformComp, PausedMap.Value); + if (currentPoly != null) + { + // uid was itself an intermediate polymorph form - the true original is already + // safely parked from the very first polymorph in this chain, so this husk serves + // no purpose anymore. Mark it Reverted first so OnPolymorphedTerminating doesn't + // try to revert it a second time + currentPoly.Reverted = true; + QueueDel(uid); + } + else + { + //Ensures a map to banish the entity to + EnsurePausedMap(); + if (PausedMap != null) + _transform.SetParent(uid, targetTransformComp, PausedMap.Value); + } // Raise an event to inform anything that wants to know about the entity swap var ev = new PolymorphedEvent(uid, child, false); @@ -293,23 +381,39 @@ private void OnPolymorphedTerminating(Entity ent, re { var (uid, component) = ent; if (!Resolve(ent, ref component)) + { + _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: no PolymorphedEntityComponent"); return null; + } if (Deleted(uid)) + { + _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: uid itself already deleted"); return null; + } if (component.Parent is not { } parent) + { + _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: no Parent recorded"); return null; + } if (Deleted(parent)) + { + _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: Parent {parent} is DELETED"); // <-- this is your prime suspect return null; + } var uidXform = Transform(uid); var parentXform = Transform(parent); - // Don't swap back onto a terminating grid if (TerminatingOrDeleted(uidXform.ParentUid)) + { + _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: current grid/map {uidXform.ParentUid} is terminating"); return null; + } + + _sawmill.Info($"Reverting {ToPrettyString(uid)} back to {ToPrettyString(parent)}"); if (component.Configuration.ExitPolymorphSound != null) _audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates); diff --git a/Content.Shared/Anomaly/Effects/Components/PolymorphAnomalyComponent.cs b/Content.Shared/Anomaly/Effects/Components/PolymorphAnomalyComponent.cs new file mode 100644 index 00000000000..59979434b94 --- /dev/null +++ b/Content.Shared/Anomaly/Effects/Components/PolymorphAnomalyComponent.cs @@ -0,0 +1,47 @@ +using Content.Shared.Anomaly.Prototypes; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Anomaly.Effects.Components; + +/// +/// Anomaly effect that grabs every valid mob (players and NPCs alike) in range on pulse +/// and forcibly polymorphs them into a random entity, for a random duration. +/// On supercritical, the effect happens again over a larger area, and each victim has a chance +/// for the transformation to become permanent instead of timing out. +/// +[RegisterComponent] +public sealed partial class PolymorphAnomalyComponent : Component +{ + /// + /// The table of possible polymorph outcomes (and their durations) that this anomaly rolls from. + /// + [DataField(required: true)] + public ProtoId PolymorphTable; + + /// + /// The radius, in tiles, that a normal pulse will affect. + /// + [DataField] + public float Range = 5f; + + /// + /// The radius, in tiles, that a crit event will affect. + /// + [DataField] + public float SupercriticalRange = 10f; + + /// + /// Chance, per victim, that a polymorph rolled during a supercritical event becomes + /// permanent (i.e. its duration is cleared entirely) instead of using the normal rolled duration. + /// A permanent polymorph can still be reverted by death or critical condition. + /// + [DataField] + public float SupercriticalPermanentChance = 0.4f; + + /// + /// Sound played at the anomaly whenever it polymorphs something. + /// + [DataField] + public SoundSpecifier? PolymorphSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/Magic/staff_animation.ogg"); +} diff --git a/Content.Shared/Anomaly/Prototypes/PolymorphAnomalyTablePrototype.cs b/Content.Shared/Anomaly/Prototypes/PolymorphAnomalyTablePrototype.cs new file mode 100644 index 00000000000..079079a7aa5 --- /dev/null +++ b/Content.Shared/Anomaly/Prototypes/PolymorphAnomalyTablePrototype.cs @@ -0,0 +1,53 @@ +using Content.Shared.Polymorph; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Anomaly.Prototypes; + +/// +/// A weighted table of possible outcomes for a polymorph-inflicting anomaly. +/// +[Prototype] +public sealed partial class PolymorphAnomalyTablePrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// All possible polymorph outcomes this table can roll, along with their weight + /// and how long each one lasts. + /// + [DataField(required: true)] + public List Options = new(); +} + +/// +/// A single entry in a . +/// +[DataDefinition] +public sealed partial class PolymorphAnomalyOption +{ + /// + /// The polymorph that will be applied if this option is picked. + /// + [DataField(required: true)] + public ProtoId Polymorph; + + /// + /// Relative weight of this option when randomly picking from the table. + /// Weights don't need to add up to any particular total - they're relative to each other. + /// + [DataField] + public float Weight = 1f; + + /// + /// The minimum amount of time a victim will remain polymorphed when this option is picked. + /// + [DataField(required: true)] + public TimeSpan MinDuration = TimeSpan.FromSeconds(20); + + /// + /// The maximum amount of time a victim will remain polymorphed when this option is picked. + /// + [DataField(required: true)] + public TimeSpan MaxDuration = TimeSpan.FromMinutes(5); +} diff --git a/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml b/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml new file mode 100644 index 00000000000..7f8e019f0f6 --- /dev/null +++ b/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml @@ -0,0 +1,394 @@ +# To add a new possible polymorph outcome for the Polymorph anomaly, just add another +# entry below - no code changes required. `polymorph` must point at a `polymorph` prototype +# (see Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml and polymorph.yml), `weight` +# controls how often it's picked relative to the other entries, and minDuration/maxDuration +# control how long a victim stays transformed if this entry is rolled (higher anomaly severity +# biases the roll towards max). +# +# Deliberately excluded from this table: silicon/robot mobs (borgs, hivebots, etc.) and any +# playable species (human, lizard-person, moth, etc.) - only animals and non-playable monsters +# are eligible outcomes. +# +# Roughly tiered by threat/rarity: common critters are common and brief, apex predators are +# rare and long-lasting. Feel free to retune weights/durations - none of this is load-bearing. + +- type: polymorphAnomalyTable + id: DefaultPolymorphAnomalyTable + options: + # --- Common critters: high weight, short-to-medium duration --- + - polymorph: Chicken + weight: 6 + minDuration: 60 # 1 minute + maxDuration: 300 # 5 minutes + - polymorph: Mouse + weight: 6 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactMonkey + weight: 6 + minDuration: 60 + maxDuration: 600 # 10 minutes + - polymorph: ArtifactAnomalyCow + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyGoat + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyDuck + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyPig + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyGoose + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyFrog + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalySnake + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyCrab + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyPossum + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyRaccoon + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyFox + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyCat + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyCorgi + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyHamster + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyParrot + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyPenguin + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyKangaroo + weight: 5 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyBat + weight: 4 + minDuration: 60 + maxDuration: 240 # 4 minutes + - polymorph: ArtifactAnomalyBee + weight: 4 + minDuration: 60 + maxDuration: 240 + - polymorph: ArtifactAnomalySlug + weight: 4 + minDuration: 60 + maxDuration: 240 + - polymorph: ArtifactCluwne + weight: 2 + minDuration: 60 + maxDuration: 180 # 3 minutes - it's a curse, not a habitat + + # --- Medium threats: moderate weight, medium-to-long duration --- + - polymorph: ArtifactLizard + weight: 3 + minDuration: 180 # 3 minutes + maxDuration: 600 # 10 minutes + - polymorph: ArtifactLuminous + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyGiantSpider + weight: 2 + minDuration: 300 # 5 minutes + maxDuration: 900 # 15 minutes + - polymorph: ArtifactAnomalyCarp + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyShark + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalySlimeBlue + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalySlimeGreen + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalySlimeYellow + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyWatcher + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyFleshGolem + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyTick + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalySnail + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyCobra + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyBear + weight: 1 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyGoliath + weight: 1 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyBasilisk + weight: 1 + minDuration: 300 + maxDuration: 900 + + # --- Apex / rare: low weight, long duration --- + - polymorph: ArtifactAnomalyRavager + weight: 1 + minDuration: 300 # 5 minutes + maxDuration: 1200 # 20 minutes + - polymorph: ArtifactAnomalyXenoDrone + weight: 1 + minDuration: 300 + maxDuration: 1200 + - polymorph: ArtifactAnomalyXenoRunner + weight: 1 + minDuration: 300 + maxDuration: 1200 + - polymorph: ArtifactAnomalyXenoSpitter + weight: 1 + minDuration: 300 + maxDuration: 1200 + - polymorph: ArtifactAnomalyAbomination + weight: 1 + minDuration: 300 + maxDuration: 1200 + - polymorph: ArtifactAnomalyHivelord + weight: 1 + minDuration: 600 # 10 minutes + maxDuration: 1500 # 25 minutes + - polymorph: ArtifactAnomalyRatKing + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyXenoQueen + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyHellspawn + weight: 1 + minDuration: 900 # 15 minutes + maxDuration: 1800 # 30 minutes - the rarest, nastiest roll in the table + + # --- Second wave: mining elementals (common-medium) --- + - polymorph: ArtifactAnomalyQuartzCrab + weight: 2 + minDuration: 120 # 2 minutes + maxDuration: 480 # 8 minutes + - polymorph: ArtifactAnomalyIronCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + - polymorph: ArtifactAnomalyCoalCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + - polymorph: ArtifactAnomalyUraniumCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + - polymorph: ArtifactAnomalyBananiumCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + - polymorph: ArtifactAnomalySilverCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + - polymorph: ArtifactAnomalyGoldCrab + weight: 2 + minDuration: 120 + maxDuration: 480 + + # --- Second wave: common critters and oddities --- + - polymorph: ArtifactAnomalyKobold + weight: 4 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyGorilla + weight: 3 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyCockroach + weight: 5 + minDuration: 30 + maxDuration: 120 # trivial pest, keep it short + - polymorph: ArtifactAnomalyGlockroach + weight: 4 + minDuration: 30 + maxDuration: 150 + - polymorph: ArtifactAnomalyMothroach + weight: 5 + minDuration: 30 + maxDuration: 120 + - polymorph: ArtifactAnomalyReindeer + weight: 4 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyBoxingKangaroo + weight: 3 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyMimic + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyMoproach + weight: 4 + minDuration: 60 + maxDuration: 240 + - polymorph: ArtifactAnomalyScurret + weight: 4 + minDuration: 60 + maxDuration: 300 + - polymorph: ArtifactAnomalyEmotionalSupportScurret + weight: 3 + minDuration: 60 + maxDuration: 300 + + # --- Second wave: medium threats --- + - polymorph: ArtifactAnomalyClownSpider + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyCarpHolo + weight: 1 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyCarpRainbow + weight: 1 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyLuminousObject + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyLuminousEntity + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyTomatoKiller + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyArgocyteSlurva + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyArgocyteBarrier + weight: 2 + minDuration: 180 + maxDuration: 600 + - polymorph: ArtifactAnomalyArgocyteSkitter + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyArgocyteSwiper + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyArgocyteMolder + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyArgocyteGlider + weight: 2 + minDuration: 300 + maxDuration: 900 + - polymorph: ArtifactAnomalyArgocyteHarvester + weight: 2 + minDuration: 300 + maxDuration: 900 + + # --- Second wave: apex / rare --- + - polymorph: ArtifactAnomalyHivelordBrood + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyBehonkerElectrical + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyBehonkerPyro + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyBehonkerGrav + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyBehonkerIce + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyArgocytePouncer + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyArgocyteCrawler + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyArgocyteEnforcer + weight: 1 + minDuration: 600 + maxDuration: 1500 + - polymorph: ArtifactAnomalyArgocyteFounder + weight: 1 + minDuration: 900 + maxDuration: 1800 + - polymorph: ArtifactAnomalyArgocyteLeviathing + weight: 1 + minDuration: 900 + maxDuration: 1800 + - polymorph: ArtifactAnomalyLaserRaptor + weight: 1 + minDuration: 900 + maxDuration: 1800 diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 24ce8d2baba..979176c3fd6 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -1166,3 +1166,36 @@ projectilePrototype: DrinkLemonLimeCranberryCan minProjectiles: 1 maxProjectiles: 3 + +# Custom sprite: cross-fades between three collaged forms on a shared silhouette - +# apex predator (ravager/basilisk/goliath/hellspawn), hive royalty (xeno queen/rat king/ +# hivelord/abomination), and reptile-amphibian (basilisk/lizard/snake/frog). Both "anom1" +# (idle) and "anom1-pulse" (shown while pulsing/supercritical) are the same 9-frame cycle - +# pulse just plays it faster. See Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi. + +- type: entity + id: AnomalyPolymorph + parent: BaseAnomaly + suffix: Polymorph + components: + - type: Anomaly + supercriticalSoundAtAnimationStart: + collection: RadiationPulse + - type: Sprite + sprite: Structures/Specific/Anomalies/polymorph_anom.rsi + layers: + - state: anom + map: ["enum.AnomalyVisualLayers.Base"] + - state: anom-pulse + map: ["enum.AnomalyVisualLayers.Animated"] + visible: false + - type: PointLight + radius: 2.0 + energy: 5.0 + color: "#c060ff" + castShadows: false + - type: PolymorphAnomaly + polymorphTable: DefaultPolymorphAnomalyTable + range: 5 + supercriticalRange: 7 + supercriticalPermanentChance: 0.2 diff --git a/Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml b/Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml new file mode 100644 index 00000000000..1e4abc18efa --- /dev/null +++ b/Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml @@ -0,0 +1,788 @@ +# Polymorph outcomes used by the Polymorph anomaly (see Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml). +# +# Duration is deliberately left unset here - PolymorphAnomalySystem always overrides Duration, +# Forced, Inventory, RevertOnCrit, and RevertOnDeath per-victim at runtime, so whatever is set +# (or not set) for those fields below has no effect when used through the anomaly. They're set +# here anyway so each polymorph still behaves sensibly if ever triggered some other way (e.g. by +# an admin smite, a future artifact node, etc). +# +# NOTE: some existing polymorph prototypes are reused directly by the anomaly table instead of +# being redefined here - see Mouse, Chicken, Monkey, ArtifactLizard, ArtifactLuminous, and +# ArtifactCluwne in polymorph.yml. + +# --- Common critters --- + +- type: polymorph + id: ArtifactAnomalyCow + configuration: + entity: MobCow + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGoat + configuration: + entity: MobGoat + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyDuck + configuration: + entity: MobDuckMallard + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyPig + configuration: + entity: MobPig + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGoose + configuration: + entity: MobGoose + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyFrog + configuration: + entity: MobFrog + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySnake + configuration: + entity: MobSnake + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCrab + configuration: + entity: MobCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyPossum + configuration: + entity: MobPossum + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyRaccoon + configuration: + entity: MobRaccoon + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyFox + configuration: + entity: MobFox + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCat + configuration: + entity: MobCat + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCorgi + configuration: + entity: MobCorgi + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyHamster + configuration: + entity: MobHamster + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyParrot + configuration: + entity: MobParrot + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyPenguin + configuration: + entity: MobPenguin + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyKangaroo + configuration: + entity: MobKangaroo + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBat + configuration: + entity: MobBat + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBee + configuration: + entity: MobBee + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySlug + configuration: + entity: MobSlug + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +# --- Medium threats --- + +- type: polymorph + id: ArtifactAnomalyGiantSpider + configuration: + entity: MobGiantSpider + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCarp + configuration: + entity: MobCarp + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyShark + configuration: + entity: MobShark + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySlimeBlue + configuration: + entity: MobAdultSlimesBlue + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySlimeGreen + configuration: + entity: MobAdultSlimesGreen + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySlimeYellow + configuration: + entity: MobAdultSlimesYellow + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyWatcher + configuration: + entity: MobWatcherLavaland + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyFleshGolem + configuration: + entity: MobFleshGolem + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyTick + configuration: + entity: MobTick + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySnail + configuration: + entity: MobSnail + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCobra + configuration: + entity: MobCobraSpace + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBear + configuration: + entity: MobBearSpace + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGoliath + configuration: + entity: MobGoliath + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBasilisk + configuration: + entity: MobBasilisk + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +# --- Apex / rare --- + +- type: polymorph + id: ArtifactAnomalyRavager + configuration: + entity: MobXenoRavager + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyXenoDrone + configuration: + entity: MobXenoDrone + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyXenoRunner + configuration: + entity: MobXenoRunner + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyXenoSpitter + configuration: + entity: MobXenoSpitter + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyAbomination + configuration: + entity: MobAbomination + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyHivelord + configuration: + entity: MobHivelord + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyRatKing + configuration: + entity: MobRatKing + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyXenoQueen + configuration: + entity: MobXenoQueen + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +# NOTE: MobHellspawn is a ghost-role solo antagonist prototype. This is fine while the victim's +# mind occupies it (PolymorphEntity transfers their mind in immediately), but if they later ghost +# out of it, the underlying GhostRole/GhostTakeoverAvailable components mean the abandoned body +# could be picked up by another ghost. Treated here as an intentional, extremely rare jackpot. +- type: polymorph + id: ArtifactAnomalyHellspawn + configuration: + entity: MobHellspawn + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +# --- Second wave: mining elementals, oddities, argocytes, behonkers, plant anomaly spawns --- + +- type: polymorph + id: ArtifactAnomalyQuartzCrab + configuration: + entity: MobQuartzCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyIronCrab + configuration: + entity: MobIronCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCoalCrab + configuration: + entity: MobCoalCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyUraniumCrab + configuration: + entity: MobUraniumCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBananiumCrab + configuration: + entity: MobBananiumCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalySilverCrab + configuration: + entity: MobSilverCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGoldCrab + configuration: + entity: MobGoldCrab + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyKobold + configuration: + entity: MobKobold + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGorilla + configuration: + entity: MobGorilla + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCockroach + configuration: + entity: MobCockroach + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyGlockroach + configuration: + entity: MobGlockroach + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyMothroach + configuration: + entity: MobMothroach + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyReindeer + configuration: + entity: MobReindeerBuck + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBoxingKangaroo + configuration: + entity: MobBoxingKangaroo + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyClownSpider + configuration: + entity: MobClownSpider + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCarpHolo + configuration: + entity: MobCarpHolo + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyCarpRainbow + configuration: + entity: MobCarpRainbow + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyMimic + configuration: + entity: MobMimic + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyLaserRaptor + configuration: + entity: MobLaserRaptor + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyTomatoKiller + configuration: + entity: MobTomatoKiller + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyMoproach + configuration: + entity: MobMoproach + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyScurret + configuration: + entity: MobScurret + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyEmotionalSupportScurret + configuration: + entity: MobEmotionalSupportScurret + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyHivelordBrood + configuration: + entity: MobHivelordBrood + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyLuminousObject + configuration: + entity: MobLuminousObject + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyLuminousEntity + configuration: + entity: MobLuminousEntity + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBehonkerElectrical + configuration: + entity: MobBehonkerElectrical + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBehonkerPyro + configuration: + entity: MobBehonkerPyro + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBehonkerGrav + configuration: + entity: MobBehonkerGrav + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyBehonkerIce + configuration: + entity: MobBehonkerIce + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteSlurva + configuration: + entity: MobArgocyteSlurva + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteBarrier + configuration: + entity: MobArgocyteBarrier + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteSkitter + configuration: + entity: MobArgocyteSkitter + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteSwiper + configuration: + entity: MobArgocyteSwiper + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteMolder + configuration: + entity: MobArgocyteMolder + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocytePouncer + configuration: + entity: MobArgocytePouncer + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteGlider + configuration: + entity: MobArgocyteGlider + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteHarvester + configuration: + entity: MobArgocyteHarvester + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteCrawler + configuration: + entity: MobArgocyteCrawler + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteEnforcer + configuration: + entity: MobArgocyteEnforcer + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteFounder + configuration: + entity: MobArgocyteFounder + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true + +- type: polymorph + id: ArtifactAnomalyArgocyteLeviathing + configuration: + entity: MobArgocyteLeviathing + forced: true + inventory: Transfer + revertOnCrit: true + revertOnDeath: true diff --git a/Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom-pulse.png b/Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom-pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..1546bc70b0edb699d206e77b9c288e363d8753d9 GIT binary patch literal 14787 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 BGmHQL literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom.png b/Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi/anom.png new file mode 100644 index 0000000000000000000000000000000000000000..94959ac39fdc3f1ef561cb6679f13ad419e67f66 GIT binary patch literal 9158 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 Date: Fri, 3 Jul 2026 23:00:12 +0100 Subject: [PATCH 2/5] Final part --- .../Anomaly/Effects/PolymorphAnomalySystem.cs | 4 - .../Polymorph/Systems/PolymorphSystem.cs | 31 --- .../Anomaly/polymorph_anomaly_tables.yml | 214 ++++++++---------- .../Structures/Specific/Anomaly/anomalies.yml | 8 +- .../Structures/Specific/Anomaly/cores.yml | 26 +++ .../Cores/polymorph_core.rsi/core.png | Bin 0 -> 625 bytes .../Cores/polymorph_core.rsi/inhand-left.png | Bin 0 -> 861 bytes .../Cores/polymorph_core.rsi/inhand-right.png | Bin 0 -> 861 bytes .../Cores/polymorph_core.rsi/meta.json | 20 ++ .../Cores/polymorph_core.rsi/pulse.png | Bin 0 -> 1211 bytes 10 files changed, 145 insertions(+), 158 deletions(-) create mode 100644 Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/core.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-left.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-right.png create mode 100644 Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/meta.json create mode 100644 Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/pulse.png diff --git a/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs index 14b4790d80b..1fcde186eab 100644 --- a/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs @@ -118,8 +118,6 @@ private void PolymorphInRange( Duration = permanent ? null : (int)RollDuration(option, severity).TotalSeconds, }; - Log.Info($"PolymorphAnomaly: {ToPrettyString(target)} -> {option.Polymorph} | sourceHasInventory={sourceHasInventory} destHasInventory={destHasInventory} | Inventory={config.Inventory}"); - var child = _polymorph.PolymorphEntity(target, config); if (child != null) EnsureCanDieOrCrit(child.Value); @@ -141,8 +139,6 @@ private void EnsureCanDieOrCrit(EntityUid child) if (_mobThreshold.TryGetDeadThreshold(child, out _)) return; - Log.Warning($"PolymorphAnomaly: {ToPrettyString(child)} has no MobThresholds configured - it would never be able to die or crit. Applying fallback thresholds so revert-on-death/crit still works. Consider adding real MobState/MobThresholds to this prototype."); - EnsureComp(child); EnsureComp(child); diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index b81dd67139f..03ea6a37ae6 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -45,7 +45,6 @@ public sealed partial class PolymorphSystem : EntitySystem [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private ILogManager _log = default!; - private ISawmill _sawmill = default!; private const string RevertPolymorphId = "ActionRevertPolymorph"; /// Accumulator for the throttled per-tick debug dump below. @@ -68,8 +67,6 @@ public sealed partial class PolymorphSystem : EntitySystem public override void Initialize() { - _sawmill = _log.GetSawmill("polymorph"); - SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnPolymorphedStartup); @@ -98,12 +95,6 @@ private void OnPolymorphedShutdown(Entity ent, ref C public override void Update(float frameTime) { base.Update(frameTime); - - _debugDumpTimer += frameTime; - var doDebugDump = _debugDumpTimer >= 0.5f; - if (doDebugDump) - _debugDumpTimer = 0f; - if (_polymorphed.Count == 0) return; @@ -118,13 +109,6 @@ public override void Update(float frameTime) comp.Time += frameTime; - if (doDebugDump) - { - TryComp(uid, out var debugMob); - var debugTotalDamage = HasComp(uid) ? _damageable.GetTotalDamage(uid) : (FixedPoint2?)null; - _sawmill.Info($"[HP DEBUG] {ToPrettyString(uid)} | state={debugMob?.CurrentState} | totalDamage={debugTotalDamage} | time={comp.Time:F1}/{comp.Configuration.Duration}"); - } - if (comp.Configuration.Duration != null && comp.Time >= comp.Configuration.Duration) { Revert((uid, comp)); @@ -137,9 +121,7 @@ public override void Update(float frameTime) if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) || comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob)) { - _sawmill.Info($"Update() detected death/crit for {ToPrettyString(uid)}, calling Revert()"); var result = Revert((uid, comp)); - _sawmill.Info($"Revert() from Update() returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); } } } @@ -199,26 +181,20 @@ private void OnBeforeFullySliced(Entity ent, ref Bef /// private void OnDestruction(Entity ent, ref DestructionEventArgs args) { - _sawmill.Info($"OnDestruction fired for {ToPrettyString(ent)}, Reverted={ent.Comp.Reverted}, RevertOnDeath={ent.Comp.Configuration.RevertOnDeath}"); - if (ent.Comp.Reverted || !ent.Comp.Configuration.RevertOnDeath) return; var result = Revert((ent, ent)); - _sawmill.Info($"Revert() from OnDestruction returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); } private void OnPolymorphedTerminating(Entity ent, ref EntityTerminatingEvent args) { - _sawmill.Info($"OnPolymorphedTerminating fired for {ToPrettyString(ent)}, Reverted={ent.Comp.Reverted}, RevertOnDelete={ent.Comp.Configuration.RevertOnDelete}, Parent={ent.Comp.Parent}"); - if (ent.Comp.Reverted) return; if (ent.Comp.Configuration.RevertOnDelete) { var result = Revert(ent.AsNullable()); - _sawmill.Info($"Revert() from OnPolymorphedTerminating returned {(result == null ? "null (FAILED)" : ToPrettyString(result.Value))}"); } // Remove our original entity too @@ -382,25 +358,21 @@ private void OnPolymorphedTerminating(Entity ent, re var (uid, component) = ent; if (!Resolve(ent, ref component)) { - _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: no PolymorphedEntityComponent"); return null; } if (Deleted(uid)) { - _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: uid itself already deleted"); return null; } if (component.Parent is not { } parent) { - _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: no Parent recorded"); return null; } if (Deleted(parent)) { - _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: Parent {parent} is DELETED"); // <-- this is your prime suspect return null; } @@ -409,12 +381,9 @@ private void OnPolymorphedTerminating(Entity ent, re if (TerminatingOrDeleted(uidXform.ParentUid)) { - _sawmill.Warning($"Revert failed for {ToPrettyString(uid)}: current grid/map {uidXform.ParentUid} is terminating"); return null; } - _sawmill.Info($"Reverting {ToPrettyString(uid)} back to {ToPrettyString(parent)}"); - if (component.Configuration.ExitPolymorphSound != null) _audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates); diff --git a/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml b/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml index 7f8e019f0f6..39587811755 100644 --- a/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml +++ b/Resources/Prototypes/Anomaly/polymorph_anomaly_tables.yml @@ -1,5 +1,5 @@ # To add a new possible polymorph outcome for the Polymorph anomaly, just add another -# entry below - no code changes required. `polymorph` must point at a `polymorph` prototype +# entry below. `polymorph` must point at a `polymorph` prototype # (see Resources/Prototypes/Polymorphs/anomaly_polymorphs.yml and polymorph.yml), `weight` # controls how often it's picked relative to the other entries, and minDuration/maxDuration # control how long a victim stays transformed if this entry is rolled (higher anomaly severity @@ -8,26 +8,22 @@ # Deliberately excluded from this table: silicon/robot mobs (borgs, hivebots, etc.) and any # playable species (human, lizard-person, moth, etc.) - only animals and non-playable monsters # are eligible outcomes. -# -# Roughly tiered by threat/rarity: common critters are common and brief, apex predators are -# rare and long-lasting. Feel free to retune weights/durations - none of this is load-bearing. - type: polymorphAnomalyTable id: DefaultPolymorphAnomalyTable options: - # --- Common critters: high weight, short-to-medium duration --- - polymorph: Chicken weight: 6 - minDuration: 60 # 1 minute - maxDuration: 300 # 5 minutes + minDuration: 30 + maxDuration: 120 - polymorph: Mouse weight: 6 - minDuration: 60 + minDuration: 30 maxDuration: 300 - polymorph: ArtifactMonkey weight: 6 minDuration: 60 - maxDuration: 600 # 10 minutes + maxDuration: 600 - polymorph: ArtifactAnomalyCow weight: 5 minDuration: 60 @@ -50,7 +46,7 @@ maxDuration: 300 - polymorph: ArtifactAnomalyFrog weight: 5 - minDuration: 60 + minDuration: 20 maxDuration: 300 - polymorph: ArtifactAnomalySnake weight: 5 @@ -71,27 +67,27 @@ - polymorph: ArtifactAnomalyFox weight: 5 minDuration: 60 - maxDuration: 300 + maxDuration: 420 - polymorph: ArtifactAnomalyCat weight: 5 minDuration: 60 - maxDuration: 300 + maxDuration: 420 - polymorph: ArtifactAnomalyCorgi weight: 5 minDuration: 60 - maxDuration: 300 + maxDuration: 420 - polymorph: ArtifactAnomalyHamster weight: 5 - minDuration: 60 - maxDuration: 300 + minDuration: 120 + maxDuration: 1200 - polymorph: ArtifactAnomalyParrot weight: 5 minDuration: 60 - maxDuration: 300 + maxDuration: 120 - polymorph: ArtifactAnomalyPenguin weight: 5 minDuration: 60 - maxDuration: 300 + maxDuration: 120 - polymorph: ArtifactAnomalyKangaroo weight: 5 minDuration: 60 @@ -99,7 +95,7 @@ - polymorph: ArtifactAnomalyBat weight: 4 minDuration: 60 - maxDuration: 240 # 4 minutes + maxDuration: 240 - polymorph: ArtifactAnomalyBee weight: 4 minDuration: 60 @@ -108,26 +104,20 @@ weight: 4 minDuration: 60 maxDuration: 240 - - polymorph: ArtifactCluwne - weight: 2 - minDuration: 60 - maxDuration: 180 # 3 minutes - it's a curse, not a habitat - - # --- Medium threats: moderate weight, medium-to-long duration --- - polymorph: ArtifactLizard weight: 3 - minDuration: 180 # 3 minutes - maxDuration: 600 # 10 minutes + minDuration: 30 + maxDuration: 120 - polymorph: ArtifactLuminous - weight: 2 + weight: 3 minDuration: 180 maxDuration: 600 - polymorph: ArtifactAnomalyGiantSpider - weight: 2 - minDuration: 300 # 5 minutes - maxDuration: 900 # 15 minutes + weight: 3 + minDuration: 300 + maxDuration: 900 - polymorph: ArtifactAnomalyCarp - weight: 2 + weight: 4 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyShark @@ -135,15 +125,15 @@ minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalySlimeBlue - weight: 2 + weight: 4 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalySlimeGreen - weight: 2 + weight: 4 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalySlimeYellow - weight: 2 + weight: 4 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyWatcher @@ -151,155 +141,147 @@ minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyFleshGolem - weight: 2 + weight: 3 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyTick weight: 2 minDuration: 300 - maxDuration: 900 + maxDuration: 360 - polymorph: ArtifactAnomalySnail - weight: 2 - minDuration: 300 - maxDuration: 900 + weight: 4 + minDuration: 30 + maxDuration: 120 - polymorph: ArtifactAnomalyCobra - weight: 2 + weight: 3 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyBear - weight: 1 + weight: 3 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyGoliath weight: 1 minDuration: 300 - maxDuration: 900 + maxDuration: 1200 - polymorph: ArtifactAnomalyBasilisk weight: 1 minDuration: 300 - maxDuration: 900 - - # --- Apex / rare: low weight, long duration --- + maxDuration: 1200 - polymorph: ArtifactAnomalyRavager - weight: 1 - minDuration: 300 # 5 minutes - maxDuration: 1200 # 20 minutes + weight: 2 + minDuration: 300 + maxDuration: 1200 - polymorph: ArtifactAnomalyXenoDrone weight: 1 minDuration: 300 maxDuration: 1200 - polymorph: ArtifactAnomalyXenoRunner - weight: 1 + weight: 3 minDuration: 300 maxDuration: 1200 - polymorph: ArtifactAnomalyXenoSpitter - weight: 1 + weight: 3 minDuration: 300 maxDuration: 1200 - polymorph: ArtifactAnomalyAbomination - weight: 1 + weight: 4 minDuration: 300 maxDuration: 1200 - polymorph: ArtifactAnomalyHivelord - weight: 1 - minDuration: 600 # 10 minutes - maxDuration: 1500 # 25 minutes - - polymorph: ArtifactAnomalyRatKing weight: 1 minDuration: 600 maxDuration: 1500 + - polymorph: ArtifactAnomalyRatKing + weight: 0.5 + minDuration: 600 + maxDuration: 1800 - polymorph: ArtifactAnomalyXenoQueen weight: 1 minDuration: 600 - maxDuration: 1500 + maxDuration: 1800 - polymorph: ArtifactAnomalyHellspawn - weight: 1 - minDuration: 900 # 15 minutes - maxDuration: 1800 # 30 minutes - the rarest, nastiest roll in the table - - # --- Second wave: mining elementals (common-medium) --- + weight: 0.5 + minDuration: 900 + maxDuration: 1800 - polymorph: ArtifactAnomalyQuartzCrab - weight: 2 - minDuration: 120 # 2 minutes - maxDuration: 480 # 8 minutes + weight: 4 + minDuration: 120 + maxDuration: 480 - polymorph: ArtifactAnomalyIronCrab - weight: 2 + weight: 4 minDuration: 120 maxDuration: 480 - polymorph: ArtifactAnomalyCoalCrab - weight: 2 + weight: 4 minDuration: 120 maxDuration: 480 - polymorph: ArtifactAnomalyUraniumCrab - weight: 2 + weight: 4 minDuration: 120 maxDuration: 480 - polymorph: ArtifactAnomalyBananiumCrab - weight: 2 + weight: 1 minDuration: 120 maxDuration: 480 - polymorph: ArtifactAnomalySilverCrab - weight: 2 + weight: 4 minDuration: 120 maxDuration: 480 - polymorph: ArtifactAnomalyGoldCrab - weight: 2 + weight: 4 minDuration: 120 maxDuration: 480 - - # --- Second wave: common critters and oddities --- - polymorph: ArtifactAnomalyKobold - weight: 4 + weight: 5 minDuration: 60 maxDuration: 300 - polymorph: ArtifactAnomalyGorilla - weight: 3 + weight: 4 minDuration: 180 maxDuration: 600 - polymorph: ArtifactAnomalyCockroach - weight: 5 + weight: 6 minDuration: 30 - maxDuration: 120 # trivial pest, keep it short + maxDuration: 60 - polymorph: ArtifactAnomalyGlockroach weight: 4 minDuration: 30 - maxDuration: 150 + maxDuration: 120 - polymorph: ArtifactAnomalyMothroach - weight: 5 + weight: 6 minDuration: 30 maxDuration: 120 - polymorph: ArtifactAnomalyReindeer - weight: 4 + weight: 5 minDuration: 60 maxDuration: 300 - polymorph: ArtifactAnomalyBoxingKangaroo - weight: 3 + weight: 4 minDuration: 60 maxDuration: 300 - polymorph: ArtifactAnomalyMimic - weight: 2 - minDuration: 180 - maxDuration: 600 + weight: 3 + minDuration: 60 + maxDuration: 120 - polymorph: ArtifactAnomalyMoproach weight: 4 minDuration: 60 - maxDuration: 240 + maxDuration: 300 - polymorph: ArtifactAnomalyScurret weight: 4 - minDuration: 60 - maxDuration: 300 + minDuration: 300 + maxDuration: 1800 - polymorph: ArtifactAnomalyEmotionalSupportScurret weight: 3 - minDuration: 60 - maxDuration: 300 - - # --- Second wave: medium threats --- + minDuration: 420 + maxDuration: 1800 - polymorph: ArtifactAnomalyClownSpider weight: 2 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyCarpHolo - weight: 1 + weight: 3 minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyCarpRainbow @@ -307,33 +289,33 @@ minDuration: 300 maxDuration: 900 - polymorph: ArtifactAnomalyLuminousObject - weight: 2 - minDuration: 180 - maxDuration: 600 + weight: 3 + minDuration: 30 + maxDuration: 60 - polymorph: ArtifactAnomalyLuminousEntity - weight: 2 - minDuration: 180 - maxDuration: 600 + weight: 3 + minDuration: 30 + maxDuration: 60 - polymorph: ArtifactAnomalyTomatoKiller weight: 2 minDuration: 180 maxDuration: 600 - polymorph: ArtifactAnomalyArgocyteSlurva - weight: 2 - minDuration: 180 - maxDuration: 600 + weight: 3 + minDuration: 30 + maxDuration: 60 - polymorph: ArtifactAnomalyArgocyteBarrier - weight: 2 - minDuration: 180 - maxDuration: 600 + weight: 3 + minDuration: 60 + maxDuration: 120 - polymorph: ArtifactAnomalyArgocyteSkitter - weight: 2 - minDuration: 300 - maxDuration: 900 + weight: 3 + minDuration: 60 + maxDuration: 120 - polymorph: ArtifactAnomalyArgocyteSwiper - weight: 2 - minDuration: 300 - maxDuration: 900 + weight: 3 + minDuration: 60 + maxDuration: 120 - polymorph: ArtifactAnomalyArgocyteMolder weight: 2 minDuration: 300 @@ -346,12 +328,10 @@ weight: 2 minDuration: 300 maxDuration: 900 - - # --- Second wave: apex / rare --- - polymorph: ArtifactAnomalyHivelordBrood - weight: 1 - minDuration: 600 - maxDuration: 1500 + weight: 5 + minDuration: 30 + maxDuration: 300 - polymorph: ArtifactAnomalyBehonkerElectrical weight: 1 minDuration: 600 @@ -379,16 +359,16 @@ - polymorph: ArtifactAnomalyArgocyteEnforcer weight: 1 minDuration: 600 - maxDuration: 1500 + maxDuration: 1800 - polymorph: ArtifactAnomalyArgocyteFounder weight: 1 minDuration: 900 maxDuration: 1800 - polymorph: ArtifactAnomalyArgocyteLeviathing - weight: 1 + weight: 0.5 minDuration: 900 maxDuration: 1800 - polymorph: ArtifactAnomalyLaserRaptor - weight: 1 + weight: 0.5 minDuration: 900 maxDuration: 1800 diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 979176c3fd6..0c94e53631d 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -1167,18 +1167,14 @@ minProjectiles: 1 maxProjectiles: 3 -# Custom sprite: cross-fades between three collaged forms on a shared silhouette - -# apex predator (ravager/basilisk/goliath/hellspawn), hive royalty (xeno queen/rat king/ -# hivelord/abomination), and reptile-amphibian (basilisk/lizard/snake/frog). Both "anom1" -# (idle) and "anom1-pulse" (shown while pulsing/supercritical) are the same 9-frame cycle - -# pulse just plays it faster. See Resources/Textures/Structures/Specific/Anomalies/polymorph_anom.rsi. - - type: entity id: AnomalyPolymorph parent: BaseAnomaly suffix: Polymorph components: - type: Anomaly + corePrototype: AnomalyCorePolymorph + coreInertPrototype: AnomalyCorePolymorphInert supercriticalSoundAtAnimationStart: collection: RadiationPulse - type: Sprite diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml index 492ab11f0c6..0218d231c4a 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml @@ -372,3 +372,29 @@ energy: 2.0 color: "#fc0303" castShadows: false + +- type: entity + parent: BaseAnomalyCore + id: AnomalyCorePolymorph + suffix: Polymorph + components: + - type: Sprite + sprite: Structures/Specific/Anomalies/Cores/polymorph_core.rsi + - type: PointLight + radius: 1.5 + energy: 2.0 + color: "#c060ff" + castShadows: false + +- type: entity + parent: BaseAnomalyInertCore + id: AnomalyCorePolymorphInert + suffix: Polymorph, Inert + components: + - type: Sprite + sprite: Structures/Specific/Anomalies/Cores/polymorph_core.rsi + - type: PointLight + radius: 1.5 + energy: 2.0 + color: "#c060ff" + castShadows: false diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/core.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/core.png new file mode 100644 index 0000000000000000000000000000000000000000..27c26b23876a76a734d6c6242e843c081cc8ed4d GIT binary patch literal 625 zcmV-%0*?KOP)A#Hr%6l^(}FReC5Tq2qVb+X1%*JX7x5q-0^%iS4|*vSrFsyof{4=pz(Xn4 zqToSt37F)NBJm?pNv$T$#kIR`nkDNYWGQKq-Oa_zJ}|H|J2SuEyz|ZsSXfy6XKZE% z_`E^2yG@b>)AI~vz~>FB=ccEbNn8Tp`)CIu zpBV$Xj(y&sy7=HR08f`6anKnd;5q<6HoZptP6e`0SjkqY=I627?YNyj!_hDl0f5wt z8vv|+SfNnN0pJdev;FEi+qE3k{5$~4soO|$xM!{mZ6GISNG6gLly$AGP|PuP{ESw= zGLxj$nuXXLu#I#mVaUY5Fm=M8lQS&6TmnETH<|RD)WoytHLZVL5)hjrc1LGjBf4AR z)N|O2-7D`#wY%|q+nS=RKbPSg=to@=w2~V2127UiVtBkM14n;M;`pXeSba{sk|$ot z6IKKixn1TqH)%EgnljOMe|8@~fVZ#TY2Srn4$AG5keZ5dGoK|i5vBgML{o(^2dLo_ z)N32MIiQ<_02eP@=FR8jJ^zCrw}`qR(}2)K6j2lrs=oj@>w)(Kbqx&Mg3jPx;sC<2 zG137a_fN<8SuO#v@Nf|@CJqK~LVwhw)f;~rd-??SscbnKxFaC|b~fs`K6wop&`m-~ z7Hndv(N`QGvfnn8IGA`1JNe)oZ`YODVADqDHO(+Do`r>l#UTCx$#~?u9`QtP00000 LNkvXXu0mjfGGZNg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-left.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..6c23f6429da199f7b8b0cb580371ce690057815f GIT binary patch literal 861 zcmeAS@N?(olHy`uVBq!ia0vp^3P9Yz!3HF=(z2x(7?@Q&T^vIy=DfY(pB)n_bL`{! zo#r!Bgp!3X-*Wr3e47M|qqw6$%oV*yhr(W{RkH45iS>GXuUuiri(_8mO^XEHda-G?6E zt3EbUWR>t$hU@F+uM$5RzUgm?!cG6B_0gJ_1eyf?-P<4_q^Kg|xK-vxqxFVxK9kd* zj+JvYGatX-cyHf6AsxMYyVxS9ofX?EW%%^#mcab;cQU-$yZn58mKYRO|J3>N+a3H+w^MgS0NrdYbLRMe;J-!{bkm&Zn5ly>v^$l z2X8$P(1~7WHtUw@?pwzjfByU_^2z4>f_-mOEevh0@NyS(n^qKlP&F|7AhP3A?8Gtg!RxqG5>cSgkY)KnC4{hDV}B$>7MwQ0qM zkJaosYu8+P*xGweCa-LoamAwLo29k5zwc|SUc7jHv$Xj(>9bP0l+XkK5@f0n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-right.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..6c23f6429da199f7b8b0cb580371ce690057815f GIT binary patch literal 861 zcmeAS@N?(olHy`uVBq!ia0vp^3P9Yz!3HF=(z2x(7?@Q&T^vIy=DfY(pB)n_bL`{! zo#r!Bgp!3X-*Wr3e47M|qqw6$%oV*yhr(W{RkH45iS>GXuUuiri(_8mO^XEHda-G?6E zt3EbUWR>t$hU@F+uM$5RzUgm?!cG6B_0gJ_1eyf?-P<4_q^Kg|xK-vxqxFVxK9kd* zj+JvYGatX-cyHf6AsxMYyVxS9ofX?EW%%^#mcab;cQU-$yZn58mKYRO|J3>N+a3H+w^MgS0NrdYbLRMe;J-!{bkm&Zn5ly>v^$l z2X8$P(1~7WHtUw@?pwzjfByU_^2z4>f_-mOEevh0@NyS(n^qKlP&F|7AhP3A?8Gtg!RxqG5>cSgkY)KnC4{hDV}B$>7MwQ0qM zkJaosYu8+P*xGweCa-LoamAwLo29k5zwc|SUc7jHv$Xj(>9bP0l+XkK5@f0n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/meta.json new file mode 100644 index 00000000000..56bb4b6676e --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Kildar - .kildar(discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { "name": "core" }, + { + "name": "pulse", + "delays": [ + [0.15625, 0.15625, 0.15625, 0.15625] + ] + }, + { "name": "inhand-left", "directions": 4 }, + { "name": "inhand-right", "directions": 4 } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/pulse.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/polymorph_core.rsi/pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..cd88bdf65c001970b02d968b25e0de77bc18b6ae GIT binary patch literal 1211 zcmV;s1VsCZP)EHNueP!X%X2b3_&-L3Bwo)#WI>6AK>0F2K2UL)sXW^PSSJ#oZt8S zx%c*b^8<-Q@*iUg_XgE!6U&dy`n_Sd6OjPbY7A^8j4_^*nLKN2x7Hidcx* zXkab)ay9|`U#a8u#wq=}a0G-@tXgfN{`iLgoM`@lbS0C-n0o+lyUvndGXn09X?2e? z-cya)Y$jGw4UdMQ5dbLr;vfKR7hCD;_W%%^oXX6ZSDBgcFy2!QKp?k*S%1cgYb9a< z9)B05g{AZb&g;ke`aR@i?a=EbMWqCT#o(v{W|+OV(2$5Vhe;cLkH3pkU!MXXFg(xJ zxQF%NZr54;`pH=@zIh*Ji`K9BkW-)g_d%*DhVF`HfkF8jK575{43Hc*E&8-?7AoDI$M8P49 zuc5Xcl+=ZKU}A02M%xXWNWORyi9{liNdDJIe?a;J(jSoifb<8XKOp@9=?{dB^amso zi9{mVa75$}96nNxqA1kX)Da8@qw5cxY-}JqYa5>KUM>$t*&jIB*nncO;OXv##s5UN z>JNn68)_Sr?T#FN8g$cox{UbLQcWqBjt<-uQK-67`^j9>&K(w54`!t zTjH6$&(h>-r0J_BAjp%M?y$yC~SRH~RSl4^21o*yFiW9xA1yqa(Wc z1NU8do2Lu1s614P)9H+E{=mK$OL(EAh%Y~DAw9_+-TVP9cwI#$=Q@7?qL@FhwC7rI zjoY3-khe2W5B|%AOSkL~gf#*$W$fob>C3$H+Cg^xJwqzc7w~c=uyDT0H$|<J$R zM5NjO=jwwz>$_Y7^BJ$As&gOB5d{ z7Af0O^kP>KPsSsX%m};PF4EG{gla;lCPbc<_1VG|?Ag0waIL5*syN|r2-Sp0OG^{= z_4NxP37IZdiaO0;E46kw`Wm Z{{nuq%om6(a_0a5002ovPDHLkV1i{8QV9S6 literal 0 HcmV?d00001 From e0c09a702451c72a399de8bca63c069ead1a9c8b Mon Sep 17 00:00:00 2001 From: Sendir Date: Sat, 4 Jul 2026 00:02:01 +0100 Subject: [PATCH 3/5] Made time scale with severity --- Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs index 1fcde186eab..66d6aba8344 100644 --- a/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/PolymorphAnomalySystem.cs @@ -179,7 +179,6 @@ private TimeSpan RollDuration(PolymorphAnomalyOption option, float severity) var biasedT = MathF.Pow(t, exponent); var seconds = MathHelper.Lerp((float)option.MinDuration.TotalSeconds, (float)option.MaxDuration.TotalSeconds, biasedT); - //return TimeSpan.FromSeconds(seconds); - return TimeSpan.FromSeconds(6000); + return TimeSpan.FromSeconds(seconds); } } From c619e6997ac311c809865e7c93e0551c37385603 Mon Sep 17 00:00:00 2001 From: Sendir Date: Sat, 4 Jul 2026 00:20:37 +0100 Subject: [PATCH 4/5] Made sure the polymorph anomaly can now be spawned by both the generator and the artifact --- .../Prototypes/Entities/Markers/Spawners/Random/anomaly.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml index dc430527367..027814d0215 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml @@ -22,6 +22,7 @@ - AnomalyFlora - AnomalyShadow - AnomalyTech + - AnomalyPolymorph #- AnomalySanta rareChance: 0.3 rarePrototypes: From 12933c564ac14bd6c0b8c37c5b55bebd0a685a86 Mon Sep 17 00:00:00 2001 From: Sendir Date: Sat, 4 Jul 2026 00:34:27 +0100 Subject: [PATCH 5/5] Clean some leftover debug logs --- Content.Server/Polymorph/Systems/PolymorphSystem.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index 03ea6a37ae6..158ce29ad7f 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -7,7 +7,6 @@ using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Destructible; -using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Mind; @@ -44,11 +43,8 @@ public sealed partial class PolymorphSystem : EntitySystem [Dependency] private readonly SharedVisualBodySystem _visualBody = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; - [Dependency] private ILogManager _log = default!; private const string RevertPolymorphId = "ActionRevertPolymorph"; - /// Accumulator for the throttled per-tick debug dump below. - private float _debugDumpTimer; /// /// Tracks every currently-polymorphed entity ourselves, since EntityQueryEnumerator silently