From 2de4f6d72adbbecbe7b53e57482dc30caa0d2499 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Wed, 2 Oct 2024 19:15:07 -0400 Subject: [PATCH] Anomalist Power System (#991) # Description This PR extends the system originally created for Shadeskip, into a system that allows any (currently existing) anomaly effect to be replicated as an Instant Action psionic power. This will be needed for Wizard/Rogue Psion antagonists. I'll be making a metric shitton of new powers using this in separate PRs. This is extremely blatantly re-using code as-is from the various Anomaly Systems, because it unfortunately turns out that it's simply not possible to build a constructor to interface between Psionics and Anomalies. # Changelog No changelog because this isn't player facing. --------- Signed-off-by: VMSolidus --- .../Abilities/AnomalyPowerSystem.Bluespace.cs | 79 +++++ .../AnomalyPowerSystem.Electricity.cs | 39 +++ .../AnomalyPowerSystem.EntitySpawn.cs | 79 +++++ .../Abilities/AnomalyPowerSystem.Explosion.cs | 52 +++ .../AnomalyPowerSystem.GasProducer.cs | 110 ++++++ .../Abilities/AnomalyPowerSystem.Gravity.cs | 78 +++++ .../Abilities/AnomalyPowerSystem.Injection.cs | 78 +++++ .../Abilities/AnomalyPowerSystem.Puddle.cs | 38 ++ .../AnomalyPowerSystem.Pyroclastic.cs | 49 +++ .../Abilities/Psionics/AnomalyPowerSystem.cs | 121 +++---- .../Actions/Events/AnomalyPowerActionEvent.cs | 328 +++++++++++++++++- .../Locale/en-US/psionics/psionic-powers.ftl | 1 + Resources/Prototypes/Actions/psionics.yml | 23 +- 13 files changed, 973 insertions(+), 102 deletions(-) create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs create mode 100644 Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs new file mode 100644 index 00000000000..698f49a112e --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs @@ -0,0 +1,79 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Mobs.Components; +using System.Linq; +using System.Numerics; +using Content.Shared.Database; +using Robust.Shared.Collections; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Bluespace Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Essentially, scramble the location of entities near the caster(possibly to include the caster). + /// + private void DoBluespaceAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Bluespace is null) + return; + + if (overcharged) + BluespaceSupercrit(uid, component, args); + else BluespacePulse(uid, component, args); + } + + private void BluespaceSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + var mapPos = _xform.GetWorldPosition(xform); + var radius = args.Bluespace!.Value.SupercriticalTeleportRadius * component.CurrentAmplification; + var gridBounds = new Box2(mapPos - new Vector2(radius, radius), mapPos + new Vector2(radius, radius)); + var mobs = new HashSet>(); + _lookup.GetEntitiesInRange(xform.Coordinates, args.Bluespace!.Value.MaxShuffleRadius, mobs); + foreach (var comp in mobs) + { + if (args.Bluespace!.Value.SupercritTeleportsCaster && comp.Owner == uid) + continue; + + var ent = comp.Owner; + var randomX = _random.NextFloat(gridBounds.Left, gridBounds.Right); + var randomY = _random.NextFloat(gridBounds.Bottom, gridBounds.Top); + + var pos = new Vector2(randomX, randomY); + + _adminLogger.Add(LogType.Teleport, $"{ToPrettyString(ent)} has been teleported to {pos} by the supercritical {ToPrettyString(uid)} at {mapPos}"); + + _xform.SetWorldPosition(ent, pos); + _audio.PlayPvs(args.Bluespace!.Value.TeleportSound, ent); + } + } + + private void BluespacePulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var range = args.Bluespace!.Value.MaxShuffleRadius * component.CurrentAmplification; + var mobs = new HashSet>(); + _lookup.GetEntitiesInRange(xform.Coordinates, range, mobs); + var allEnts = new ValueList(mobs.Select(m => m.Owner)) { uid }; + var coords = new ValueList(); + foreach (var ent in allEnts) + { + if (args.Bluespace!.Value.PulseTeleportsCaster && ent == uid + || !xformQuery.TryGetComponent(ent, out var allXform)) + continue; + + coords.Add(_xform.GetWorldPosition(allXform)); + } + + _random.Shuffle(coords); + for (var i = 0; i < allEnts.Count; i++) + { + _adminLogger.Add(LogType.Teleport, $"{ToPrettyString(allEnts[i])} has been shuffled to {coords[i]} by the {ToPrettyString(uid)} at {xform.Coordinates}"); + _xform.SetWorldPosition(allEnts[i], coords[i]); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs new file mode 100644 index 00000000000..3f494aafb10 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs @@ -0,0 +1,39 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Electrical Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// This fires lightning bolts at random entities near the caster. + /// + private void DoElectricityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Electricity is null) + return; + + if (overcharged) + ElectricitySupercrit(uid, component, args); + else ElectricityPulse(uid, component, args); + } + + private void ElectricitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var range = args.Electricity!.Value.MaxElectrocuteRange * component.CurrentAmplification; + + _emp.EmpPulse(_xform.GetMapCoordinates(uid), range, args.Electricity!.Value.EmpEnergyConsumption, args.Electricity!.Value.EmpDisabledDuration); + _lightning.ShootRandomLightnings(uid, range, args.Electricity!.Value.MaxBoltCount * (int) component.CurrentAmplification, arcDepth: (int) component.CurrentDampening); + } + + private void ElectricityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var range = args.Electricity!.Value.MaxElectrocuteRange * component.CurrentAmplification; + + int boltCount = (int) MathF.Floor(MathHelper.Lerp(args.Electricity!.Value.MinBoltCount, args.Electricity!.Value.MaxBoltCount, component.CurrentAmplification)); + + _lightning.ShootRandomLightnings(uid, range, boltCount); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs new file mode 100644 index 00000000000..8d4898ed8d6 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs @@ -0,0 +1,79 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Random.Helpers; +using Robust.Shared.Random; +using Content.Shared.Anomaly.Effects.Components; +using Robust.Shared.Map.Components; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private const string NoGrid = "entity-anomaly-no-grid"; + + /// + /// This function handles emulating the effects of an "Entity Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Essentially, spawn entities on random tiles in a radius around the caster. + /// + private void DoEntityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.EntitySpawnEntries is null) + return; + + if (Transform(uid).GridUid is null) + { + _popup.PopupEntity(Loc.GetString(NoGrid), uid, uid); + return; + } + + if (overcharged) + EntitySupercrit(uid, component, args); + else EntityPulse(uid, component, args); + } + + private void EntitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + foreach (var entry in args.EntitySpawnEntries!) + { + if (!entry.Settings.SpawnOnSuperCritical) + continue; + + SpawnEntities(uid, component, entry); + } + } + + private void EntityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.EntitySpawnEntries is null) + return; + + foreach (var entry in args.EntitySpawnEntries!) + { + if (!entry.Settings.SpawnOnPulse) + continue; + + SpawnEntities(uid, component, entry); + } + } + + private void SpawnEntities(EntityUid uid, PsionicComponent component, EntitySpawnSettingsEntry entry) + { + if (!TryComp(Transform(uid).GridUid, out var grid)) + return; + + var tiles = _anomalySystem.GetSpawningPoints(uid, + component.CurrentDampening, + component.CurrentAmplification, + entry.Settings, + _glimmerSystem.Glimmer / 1000, + component.CurrentAmplification, + component.CurrentAmplification); + + if (tiles is null) + return; + + foreach (var tileref in tiles) + Spawn(_random.Pick(entry.Spawns), _mapSystem.ToCenterCoordinates(tileref, grid)); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs new file mode 100644 index 00000000000..06501afa71b --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs @@ -0,0 +1,52 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Explosion Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Generates an explosion centered on the caster. + /// + private void DoExplosionAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Explosion is null) + return; + + if (overcharged) + ExplosionSupercrit(uid, component, args); + else ExplosionPulse(uid, component, args); + } + + private void ExplosionSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.Explosion!.Value.SupercritExplosionPrototype is null) + return; + + var explosion = args.Explosion!.Value; + _boom.QueueExplosion( + uid, + explosion.SupercritExplosionPrototype, + explosion.SupercritTotalIntensity * component.CurrentAmplification, + explosion.SupercritDropoff / component.CurrentDampening, + explosion.SupercritMaxTileIntensity * component.CurrentDampening + ); + } + + private void ExplosionPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.Explosion!.Value.ExplosionPrototype is null) + return; + + var explosion = args.Explosion!.Value; + _boom.QueueExplosion( + uid, + explosion.ExplosionPrototype, + explosion.TotalIntensity * component.CurrentAmplification, + explosion.Dropoff / component.CurrentDampening, + explosion.MaxTileIntensity * component.CurrentDampening + ); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs new file mode 100644 index 00000000000..e0dbfd2d1fe --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs @@ -0,0 +1,110 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Robust.Shared.Map.Components; +using System.Linq; +using System.Numerics; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoGasProducerAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Gas is not null) + return; + + if (overcharged) + GasProducerSupercrit(uid, component, args); + else GasProducerPulse(uid, component, args); + } + + private void GasProducerSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out var grid)) + return; + + var gas = args.Gas!.Value.SupercritReleasedGas; + var mols = args.Gas!.Value.SupercritMoleAmount * component.CurrentAmplification; + var radius = args.Gas!.Value.SupercritSpawnRadius * component.CurrentAmplification; + var count = args.Gas!.Value.SupercritTileCount * component.CurrentDampening; + var temp = args.Gas!.Value.SupercritTempChange * component.CurrentDampening; + var localpos = xform.Coordinates.Position; + var tilerefs = grid.GetLocalTilesIntersecting( + new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray(); + + if (tilerefs.Length == 0) + return; + + var mixture = _atmosphere.GetTileMixture((uid, xform), true); + if (mixture != null) + { + mixture.AdjustMoles(gas, mols); + mixture.Temperature += temp; + } + + if (count == 0) + return; + + _random.Shuffle(tilerefs); + var amountCounter = 0; + foreach (var tileref in tilerefs) + { + var mix = _atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, tileref.GridIndices, true); + amountCounter++; + if (mix is not { }) + continue; + + mix.AdjustMoles(gas, mols); + mix.Temperature += temp; + + if (amountCounter >= count) + return; + } + } + + private void GasProducerPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out var grid)) + return; + + var gas = args.Gas!.Value.ReleasedGas; + var mols = args.Gas!.Value.MoleAmount * component.CurrentAmplification; + var radius = args.Gas!.Value.SpawnRadius * component.CurrentAmplification; + var count = args.Gas!.Value.TileCount * component.CurrentDampening; + var temp = args.Gas!.Value.TempChange * component.CurrentDampening; + var localpos = xform.Coordinates.Position; + var tilerefs = grid.GetLocalTilesIntersecting( + new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray(); + + if (tilerefs.Length == 0) + return; + + var mixture = _atmosphere.GetTileMixture((uid, xform), true); + if (mixture != null) + { + mixture.AdjustMoles(gas, mols); + mixture.Temperature += temp; + } + + if (count == 0) + return; + + _random.Shuffle(tilerefs); + var amountCounter = 0; + foreach (var tileref in tilerefs) + { + var mix = _atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, tileref.GridIndices, true); + amountCounter++; + if (mix is not { }) + continue; + + mix.AdjustMoles(gas, mols); + mix.Temperature += temp; + + if (amountCounter >= count) + return; + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs new file mode 100644 index 00000000000..532cb846d4b --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs @@ -0,0 +1,78 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Robust.Shared.Physics.Components; +using Content.Shared.Physics; +using System.Linq; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoGravityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Gravity is null) + return; + + if (overcharged) + GravitySupercrit(uid, component, args); + else GravityPulse(uid, component, args); + } + + private void GravitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out MapGridComponent? grid)) + return; + + var gravity = args.Gravity!.Value; + var worldPos = _xform.GetWorldPosition(xform); + var tileref = _mapSystem.GetTilesIntersecting( + xform.GridUid.Value, + grid, + new Circle(worldPos, gravity.SpaceRange)) + .ToArray(); + + var tiles = tileref.Select(t => (t.GridIndices, Tile.Empty)).ToList(); + _mapSystem.SetTiles(xform.GridUid.Value, grid, tiles); + + var range = gravity.MaxThrowRange * component.CurrentDampening; + var strength = gravity.MaxThrowStrength * component.CurrentAmplification; + var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries); + var xformQuery = GetEntityQuery(); + var physQuery = GetEntityQuery(); + + foreach (var ent in lookup) + { + if (physQuery.TryGetComponent(ent, out var phys) + && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0) + continue; + + var foo = _xform.GetWorldPosition(ent, xformQuery) - worldPos; + _throwing.TryThrow(ent, foo * 5, strength, uid, 0); + } + } + + private void GravityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var gravity = args.Gravity!.Value; + var xform = Transform(uid); + var range = gravity.MaxThrowRange * component.CurrentDampening; + var strength = gravity.MaxThrowStrength * component.CurrentAmplification; + var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries); + var xformQuery = GetEntityQuery(); + var worldPos = _xform.GetWorldPosition(xform, xformQuery); + var physQuery = GetEntityQuery(); + + foreach (var ent in lookup) + { + if (physQuery.TryGetComponent(ent, out var phys) + && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0) + continue; + + var foo = _xform.GetWorldPosition(ent, xformQuery) - worldPos; + _throwing.TryThrow(ent, foo * 10, strength, uid, 0); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs new file mode 100644 index 00000000000..e2b11392718 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs @@ -0,0 +1,78 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Chemistry.Components.SolutionManager; +using System.Linq; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private EntityQuery _injectableQuery; + private void DoInjectionAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Injection is null) + return; + + if (overcharged) + InjectionSupercrit(uid, component, args); + else InjectionPulse(uid, component, args); + } + + private void InjectionSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var injection = args.Injection!.Value; + var injectRadius = injection.SuperCriticalInjectRadius * component.CurrentAmplification; + var maxInject = injection.SuperCriticalSolutionInjection * component.CurrentDampening; + + if (!_solutionContainer.TryGetSolution(uid, injection.Solution, out _, out var sol)) + return; + + //We get all the entity in the radius into which the reagent will be injected. + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var allEnts = _lookup.GetEntitiesInRange(_xform.GetMapCoordinates(uid), injectRadius) + .Select(x => x.Owner).ToList(); + + //for each matching entity found + foreach (var ent in allEnts) + { + if (!_solutionContainer.TryGetInjectableSolution(ent, out var injectable, out _) + || !_injectableQuery.TryGetComponent(ent, out var injEnt) + || !_solutionContainer.TryTransferSolution(injectable.Value, sol, maxInject)) + continue; + + //Spawn Effect + var uidXform = Transform(ent); + Spawn(injection.VisualEffectPrototype, uidXform.Coordinates); + } + } + + private void InjectionPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var injection = args.Injection!.Value; + var injectRadius = injection.InjectRadius * component.CurrentAmplification; + var maxInject = injection.MaxSolutionInjection * component.CurrentDampening; + + if (!_solutionContainer.TryGetSolution(uid, injection.Solution, out _, out var sol)) + return; + + //We get all the entity in the radius into which the reagent will be injected. + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var allEnts = _lookup.GetEntitiesInRange(_xform.GetMapCoordinates(uid), injectRadius) + .Select(x => x.Owner).ToList(); + + //for each matching entity found + foreach (var ent in allEnts) + { + if (!_solutionContainer.TryGetInjectableSolution(ent, out var injectable, out _) + || !_injectableQuery.TryGetComponent(ent, out var injEnt) + || !_solutionContainer.TryTransferSolution(injectable.Value, sol, maxInject)) + continue; + + //Spawn Effect + var uidXform = Transform(ent); + Spawn(injection.VisualEffectPrototype, uidXform.Coordinates); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs new file mode 100644 index 00000000000..a53b5e4930f --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs @@ -0,0 +1,38 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoPuddleAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Puddle is null) + return; + + if (overcharged) + PuddleSupercrit(uid, args); + else PuddlePulse(uid, component, args); + } + + private void PuddleSupercrit(EntityUid uid, AnomalyPowerActionEvent args) + { + var puddle = args.Puddle!.Value; + if (!_solutionContainer.TryGetSolution(uid, puddle.Solution, out _, out var sol)) + return; + + var xform = Transform(uid); + _puddle.TrySpillAt(xform.Coordinates, sol, out _); + } + + private void PuddlePulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var puddle = args.Puddle!.Value; + if (!_solutionContainer.TryGetSolution(uid, puddle.Solution, out var sol, out _)) + return; + + var xform = Transform(uid); + var puddleSol = _solutionContainer.SplitSolution(sol.Value, puddle.MaxPuddleSize * component.CurrentAmplification); + _puddle.TrySplashSpillAt(uid, xform.Coordinates, puddleSol, out _); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs new file mode 100644 index 00000000000..0ff6fc28b83 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs @@ -0,0 +1,49 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Server.Atmos.Components; +using Robust.Shared.Map; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoPyroclasticAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Pyroclastic is null) + return; + + if (overcharged) + PyroclasticSupercrit(uid, component, args); + else PyroclasticPulse(uid, component, args); + } + + private void PyroclasticSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var pyroclastic = args.Pyroclastic!.Value; + var xform = Transform(uid); + var ignitionRadius = pyroclastic.SupercritMaximumIgnitionRadius * component.CurrentAmplification; + IgniteNearby(uid, xform.Coordinates, component.CurrentAmplification, ignitionRadius); + } + + private void PyroclasticPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var pyroclastic = args.Pyroclastic!.Value; + var xform = Transform(uid); + var ignitionRadius = pyroclastic.MaximumIgnitionRadius * component.CurrentAmplification; + IgniteNearby(uid, xform.Coordinates, component.CurrentAmplification, ignitionRadius); + } + + private void IgniteNearby(EntityUid uid, EntityCoordinates coordinates, float severity, float radius) + { + var flammables = new HashSet>(); + _lookup.GetEntitiesInRange(coordinates, radius, flammables); + + foreach (var flammable in flammables) + { + var ent = flammable.Owner; + var stackAmount = 1 + (int) (severity / 0.15f); + _flammable.AdjustFireStacks(ent, stackAmount, flammable); + _flammable.Ignite(ent, uid, flammable); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs index 1548e4e2f3e..388a40a93a0 100644 --- a/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs @@ -1,19 +1,24 @@ using Content.Shared.Abilities.Psionics; using Content.Shared.Actions.Events; using Content.Shared.Psionics.Glimmer; -using Content.Shared.Random.Helpers; using Robust.Shared.Random; -using Content.Shared.Anomaly.Effects.Components; -using Robust.Shared.Map.Components; using Content.Shared.Anomaly; using Robust.Shared.Audio.Systems; using Content.Shared.Actions; using Content.Shared.Damage; using Content.Server.Popups; +using Content.Shared.Administration.Logs; +using Content.Server.Lightning; +using Content.Server.Emp; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Throwing; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Fluids.EntitySystems; namespace Content.Server.Abilities.Psionics; -public sealed class AnomalyPowerSystem : EntitySystem +public sealed partial class AnomalyPowerSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; @@ -24,6 +29,18 @@ public sealed class AnomalyPowerSystem : EntitySystem [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly EmpSystem _emp = default!; + [Dependency] private readonly ExplosionSystem _boom = default!; + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly PuddleSystem _puddle = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + public override void Initialize() { base.Initialize(); @@ -37,20 +54,18 @@ private void OnPowerUsed(EntityUid uid, PsionicComponent component, AnomalyPower return; var overcharged = _glimmerSystem.Glimmer * component.CurrentAmplification - > Math.Min(args.SupercriticalThreshold * component.CurrentDampening, args.MaxSupercriticalThreshold); + > Math.Min(args.Settings.SupercriticalThreshold * component.CurrentDampening, args.Settings.MaxSupercriticalThreshold); - // I already hate this, so much. - //DoBluespaceAnomalyEffects(uid, component, args, overcharged); - //DoElectricityAnomalyEffects(uid, component, args, overcharged); + // Behold the wall of nullable logic gates. + DoBluespaceAnomalyEffects(uid, component, args, overcharged); + DoElectricityAnomalyEffects(uid, component, args, overcharged); DoEntityAnomalyEffects(uid, component, args, overcharged); - //DoExplosionAnomalyEffects(uid, component, args, overcharged); - //DoGasProducerAnomalyEffects(uid, component, args, overcharged); - //DoGravityAnomalyEffects(uid, component, args, overcharged); - //DoInjectionAnomalyEffects(uid, component, args, overcharged); - //DoPuddleCreateAnomalyEffects(uid, component, args, overcharged); - //DoPyroclasticAnomalyEffects(uid, component, args, overcharged); - //DoTemperatureAnomalyEffects(uid, component, args, overcharged); - + DoExplosionAnomalyEffects(uid, component, args, overcharged); + DoGasProducerAnomalyEffects(uid, component, args, overcharged); + DoGravityAnomalyEffects(uid, component, args, overcharged); + DoInjectionAnomalyEffects(uid, component, args, overcharged); + DoPuddleAnomalyEffects(uid, component, args, overcharged); + DoPyroclasticAnomalyEffects(uid, component, args, overcharged); DoAnomalySounds(uid, component, args, overcharged); DoGlimmerEffects(uid, component, args, overcharged); @@ -60,87 +75,45 @@ private void OnPowerUsed(EntityUid uid, PsionicComponent component, AnomalyPower args.Handled = true; } - public void DoEntityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged) - { - if (args.EntitySpawnEntries is null) - return; - - if (overcharged) - foreach (var entry in args.EntitySpawnEntries) - { - if (!entry.Settings.SpawnOnSuperCritical) - continue; - - SpawnEntities(uid, component, entry); - } - else foreach (var entry in args.EntitySpawnEntries) - { - if (!entry.Settings.SpawnOnPulse) - continue; - - SpawnEntities(uid, component, entry); - } - } - - public void SpawnEntities(EntityUid uid, PsionicComponent component, EntitySpawnSettingsEntry entry) - { - if (!TryComp(Transform(uid).GridUid, out var grid)) - return; - - var tiles = _anomalySystem.GetSpawningPoints(uid, - component.CurrentDampening, - component.CurrentAmplification, - entry.Settings, - _glimmerSystem.Glimmer / 1000, - component.CurrentAmplification, - component.CurrentAmplification); - - if (tiles is null) - return; - - foreach (var tileref in tiles) - Spawn(_random.Pick(entry.Spawns), _mapSystem.ToCenterCoordinates(tileref, grid)); - } - public void DoAnomalySounds(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) { - if (overcharged && args.SupercriticalSound is not null) + if (overcharged && args.Settings.SupercriticalSound is not null) { - _audio.PlayPvs(args.SupercriticalSound, uid); + _audio.PlayPvs(args.Settings.SupercriticalSound, uid); return; } - if (args.PulseSound is null - || _glimmerSystem.Glimmer < args.GlimmerSoundThreshold * component.CurrentDampening) + if (args.Settings.PulseSound is null + || _glimmerSystem.Glimmer < args.Settings.GlimmerSoundThreshold * component.CurrentDampening) return; - _audio.PlayEntity(args.PulseSound, uid, uid); + _audio.PlayEntity(args.Settings.PulseSound, uid, uid); } public void DoGlimmerEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) { - var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer) - * (overcharged ? args.SupercriticalGlimmerMultiplier : 1) + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.Settings.MinGlimmer, args.Settings.MaxGlimmer) + * (overcharged ? args.Settings.SupercriticalGlimmerMultiplier : 1) * component.CurrentAmplification - component.CurrentDampening); - var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer) - * (overcharged ? args.SupercriticalGlimmerMultiplier : 1) + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.Settings.MinGlimmer, args.Settings.MaxGlimmer) + * (overcharged ? args.Settings.SupercriticalGlimmerMultiplier : 1) * component.CurrentAmplification - component.CurrentDampening); - _psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer); + _psionics.LogPowerUsed(uid, args.Settings.PowerName, minGlimmer, maxGlimmer); } public void DoOverchargedEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) { - if (args.OverchargeFeedback is not null - && Loc.TryGetString(args.OverchargeFeedback, out var popup)) + if (args.Settings.OverchargeFeedback is not null + && Loc.TryGetString(args.Settings.OverchargeFeedback, out var popup)) _popup.PopupEntity(popup, uid, uid); - if (args.OverchargeRecoil is not null + if (args.Settings.OverchargeRecoil is not null && TryComp(uid, out var damageable)) - _damageable.TryChangeDamage(uid, args.OverchargeRecoil / component.CurrentDampening, true, true, damageable, uid); + _damageable.TryChangeDamage(uid, args.Settings.OverchargeRecoil / component.CurrentDampening, true, true, damageable, uid); - if (args.OverchargeCooldown > 0) + if (args.Settings.OverchargeCooldown > 0) foreach (var action in component.Actions) - _actions.SetCooldown(action.Value, TimeSpan.FromSeconds(args.OverchargeCooldown / component.CurrentDampening)); + _actions.SetCooldown(action.Value, TimeSpan.FromSeconds(args.Settings.OverchargeCooldown / component.CurrentDampening)); } } diff --git a/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs index c5a792b4798..0146f57ffab 100644 --- a/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs +++ b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs @@ -1,84 +1,378 @@ using Content.Shared.Anomaly.Effects.Components; +using Content.Shared.Atmos; using Content.Shared.Damage; using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Content.Shared.Explosion; +using Robust.Shared.Prototypes; namespace Content.Shared.Actions.Events; public sealed partial class AnomalyPowerActionEvent : InstantActionEvent { + /// + /// Contains settings common to all "Anomalist" Powers. + /// + [DataField] + public AnomalyPowerSettings Settings = default!; + + /// + /// Contains settings specific to "Bluespace Anomaly" powers. + /// + [DataField] + public BluespaceAnomalySettings? Bluespace = default!; + /// + /// Contains settings specific to "Electrical Anomaly" powers. + /// + [DataField] + public ElectricalAnomalySettings? Electricity = default!; + + /// + /// What entities will be spawned by this action, using the same arguments as an EntitySpawnAnomalyComponent. + /// [DataField] + public List? EntitySpawnEntries; + + /// + /// Contains settings specific to "Explosion Anomaly" powers. + /// + [DataField] + public ExplosionAnomalySettings? Explosion = default!; + + /// + /// Contains settings specific to "Gas Producer Anomaly" powers. + /// + [DataField] + public GasProducerAnomalySettings? Gas = default!; + + /// + /// Contains settings specific to "Gravity Anomaly" powers. + /// + [DataField] + public GravityAnomalySettings? Gravity = default!; + + /// + /// Contains settings specific to "Injection Anomaly" powers. + /// + [DataField] + public InjectionAnomalySettings? Injection = default!; + + /// + /// Contains settings specific to "Puddle Create Anomaly" powers. + /// + [DataField] + public PuddleAnomalySettings? Puddle = default!; + + /// + /// Contains settings specific to "Pyroclastic Anomaly" powers. + /// + [DataField] + public PyroclasticAnomalySettings? Pyroclastic = default!; +} + +[DataRecord] +public partial record struct AnomalyPowerSettings() +{ public string PowerName; /// /// When casting above the Supercritical Threshold, if not 0, this will cause all powers to enter cooldown for the given duration. /// - [DataField] public float OverchargeCooldown; /// /// When casting above the Supercritical Threshold, if not 0, this will deal recoil damage to the caster of the specified amounts. /// - [DataField] public DamageSpecifier? OverchargeRecoil; /// /// When casting above the Supercritical Threshold, play a popup above the caster's head. /// - [DataField] public string? OverchargeFeedback; /// /// The minimum amount of glimmer generated by this power. /// - [DataField] public int MinGlimmer; /// /// The maximum amount of glimmer generated by this power. /// - [DataField] public int MaxGlimmer; /// /// The amount to multiply glimmer generation by when above the Supercritical Threshold /// - [DataField] public int SupercriticalGlimmerMultiplier = 1; /// /// The threshold of glimmer at which this power will play a sound. /// - [DataField] public float GlimmerSoundThreshold; /// /// The glimmer threshold(divided by amplification and multiplied by dampening) at which this power will act as a Supercritical Anomaly. /// - [DataField] public float SupercriticalThreshold = 500f; /// /// The maximum amount Dampening can increase the Supercritical threshold to. /// - [DataField] public float MaxSupercriticalThreshold = 800f; - /// - /// What entities will be spawned by this action, using the same arguments as an EntitySpawnAnomalyComponent? - /// - [DataField] - public List? EntitySpawnEntries; - /// /// The sound to be played upon activating this power(and not Supercritically) /// - [DataField] public SoundSpecifier? PulseSound = new SoundCollectionSpecifier("RadiationPulse"); /// /// The sound plays when this power is activated above a Supercritical glimmer threshold /// - [DataField] public SoundSpecifier? SupercriticalSound = new SoundCollectionSpecifier("Explosion"); } + +[DataRecord] +public partial record struct BluespaceAnomalySettings() +{ + /// + /// The maximum radius that the shuffle effect will extend for + /// scales with stability + /// + public float MaxShuffleRadius = 10; + + /// + /// Whether or not a standard pulse teleports the caster. + /// + public bool PulseTeleportsCaster; + + /// + /// Whether or not a supercrit teleports the caster. + /// + public bool SupercritTeleportsCaster; + + /// + /// How far the supercritical event can teleport you + /// + public float SupercriticalTeleportRadius = 50f; + + /// + /// The sound played after players are shuffled/teleported around + /// + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); +} + +[DataRecord] +public partial record struct ElectricalAnomalySettings() +{ + /// + /// the minimum number of lightning strikes + /// + public int MinBoltCount = 2; + + /// + /// The number of lightning strikes, at the maximum severity of the anomaly + /// + public int MaxBoltCount = 5; + + /// + /// The maximum radius of the passive electrocution effect + /// scales with stability + /// + public float MaxElectrocuteRange = 7f; + + /// + /// Energy consumed from devices by the emp pulse upon going supercritical. + /// + public float EmpEnergyConsumption = 100000f; + + /// + /// Duration of devices being disabled by the emp pulse upon going supercritical. + /// + public float EmpDisabledDuration = 60f; +} + +[DataRecord] +public partial record struct ExplosionAnomalySettings() +{ + /// + /// The explosion prototype to spawn + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? ExplosionPrototype = default!; + + /// + /// The total amount of intensity an explosion can achieve + /// + public float TotalIntensity = 100f; + + /// + /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage + /// + public float Dropoff = 10f; + + /// + /// How much intensity can be applied per tile? + /// + public float MaxTileIntensity = 10f; + + /// + /// The explosion prototype to spawn on Supercrit + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? SupercritExplosionPrototype = default!; + + /// + /// The total amount of intensity an explosion can achieve + /// + public float SupercritTotalIntensity = 100f; + + /// + /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage + /// + public float SupercritDropoff = 10f; + + /// + /// How much intensity can be applied per tile? + /// + public float SupercritMaxTileIntensity = 10f; +} + +[DataRecord] +public partial record struct GasProducerAnomalySettings() +{ + /// + /// The gas to release + /// + public Gas ReleasedGas = Gas.WaterVapor; + + /// + /// The gas to release + /// + public Gas SupercritReleasedGas = Gas.WaterVapor; + + /// + /// The amount of gas released passively + /// + public float MoleAmount = 1f; + + /// + /// The radius of random gas spawns. + /// + public float SpawnRadius = 3; + + /// + /// The number of tiles which will be modified. + /// + public int TileCount = 1; + + /// + /// The the amount the temperature should be modified by (negative for decreasing temp) + /// + public float TempChange = 0; + + /// + /// The amount of gas released when the anomaly reaches max severity + /// + public float SupercritMoleAmount = 150f; + + /// + /// The radius of random gas spawns. + /// + public float SupercritSpawnRadius = 10; + + /// + /// The number of tiles which will be modified. + /// + public int SupercritTileCount = 10; + + /// + /// The the amount the temperature should be modified by (negative for decreasing temp) + /// + public float SupercritTempChange = 0; +} + +[DataRecord] +public partial record struct GravityAnomalySettings() +{ + /// + /// The maximum distance from which the anomaly + /// can throw you via a pulse. + /// + public float MaxThrowRange = 5f; + + /// + /// The maximum strength the anomaly + /// can throw you via a pulse + /// + public float MaxThrowStrength = 10; + + /// + /// The range around the anomaly that will be spaced on supercritical. + /// + public float SpaceRange = 3f; +} + +[DataRecord] +public partial record struct InjectionAnomalySettings() +{ + /// + /// the maximum amount of injection of a substance into an entity per pulsation + /// scales with Severity + /// + public float MaxSolutionInjection = 15; + + /// + /// The maximum amount of injection of a substance into an entity in the supercritical phase + /// + public float SuperCriticalSolutionInjection = 50; + + /// + /// The maximum radius in which the anomaly injects reagents into the surrounding containers. + /// + public float InjectRadius = 3; + + /// + /// The maximum radius in which the anomaly injects reagents into the surrounding containers. + /// + public float SuperCriticalInjectRadius = 15; + + /// + /// The name of the prototype of the special effect that appears above the entities into which the injection was carried out + /// + public EntProtoId VisualEffectPrototype = "PuddleSparkle"; + + /// + /// Solution name that can be drained. + /// + public string Solution { get; set; } = "default"; +} + +[DataRecord] +public partial record struct PuddleAnomalySettings() +{ + /// + /// The maximum amount of solution that an anomaly can splash out of the storage on the floor during pulsation. + /// Scales with Amplification. + /// + public float MaxPuddleSize = 100; + + /// + /// Solution name that can be drained. + /// + public string Solution { get; set; } = "default"; +} + +[DataRecord] +public partial record struct PyroclasticAnomalySettings() +{ + /// + /// The maximum distance from which entities will be ignited. + /// + public float MaximumIgnitionRadius = 5f; + + /// + /// The maximum distance from which entities will be ignited on a Supercrit cast. + /// + public float SupercritMaximumIgnitionRadius = 20f; +} diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index 43cb1a03f84..9b4d3c49db7 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -128,4 +128,5 @@ examine-mindbroken-message = Eyes unblinking, staring deep into the horizon. {CAPITALIZE($entity)} is a sack of meat pretending it has a soul. There is nothing behind its gaze, no evidence there can be found of the divine light of creation. psionic-roll-failed = For a moment, my consciousness expands, yet I feel that it is not enough. +entity-anomaly-no-grid = There is nowhere for me to conjure beings. power-overwhelming-power-feedback = {CAPITALIZE($entity)} wields a vast connection to the noƶsphere diff --git a/Resources/Prototypes/Actions/psionics.yml b/Resources/Prototypes/Actions/psionics.yml index b929a11c7e6..2e105f3a690 100644 --- a/Resources/Prototypes/Actions/psionics.yml +++ b/Resources/Prototypes/Actions/psionics.yml @@ -233,17 +233,18 @@ useDelay: 45 checkCanInteract: false event: !type:AnomalyPowerActionEvent - powerName: "Shadeskip" - overchargeFeedback: "shadeskip-overcharge-feedback" - overchargeCooldown: 120 - overchargeRecoil: - groups: - Burn: -100 #This will be divided by the caster's Dampening. - minGlimmer: 6 - maxGlimmer: 8 - supercriticalGlimmerMultiplier: 3 - glimmerSoundThreshold: 50 - supercriticalThreshold: 500 + settings: + powerName: "Shadeskip" + overchargeFeedback: "shadeskip-overcharge-feedback" + overchargeCooldown: 120 + overchargeRecoil: + groups: + Burn: -100 #This will be divided by the caster's Dampening. + minGlimmer: 6 + maxGlimmer: 8 + supercriticalGlimmerMultiplier: 3 + glimmerSoundThreshold: 50 + supercriticalThreshold: 500 entitySpawnEntries: - settings: spawnOnPulse: true