Files
wwdpublic/Content.Server/Psionics/Glimmer/GlimmerReactiveSystem.cs
Spatison 2a10c02eb5 No spead merge (#475)
* Revert "[GoobPort] WIZ REAL (#465)"

This reverts commit 091a8ff433.

* fix local
2025-04-26 10:50:32 +03:00

387 lines
15 KiB
C#

using Content.Server.Audio;
using Content.Server.Power.Components;
using Content.Server.Electrocution;
using Content.Server.Lightning;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Ghost;
using Content.Server.Revenant.EntitySystems;
using Content.Shared.Audio;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.GameTicking;
using Content.Shared.Psionics.Glimmer;
using Content.Shared.Verbs;
using Content.Shared.StatusEffect;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.Construction.Components;
using Content.Shared.Mind.Components;
using Content.Shared.Power;
using Content.Shared.Weapons.Melee.Components;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Psionics.Glimmer;
public sealed class GlimmerReactiveSystem : EntitySystem
{
[Dependency] private readonly GlimmerSystem _glimmerSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!;
[Dependency] private readonly SharedAudioSystem _sharedAudioSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _sharedAmbientSoundSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly LightningSystem _lightning = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
[Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!;
[Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly RevenantSystem _revenantSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
private ISawmill _sawmill = default!;
public float Accumulator = 0;
public const float UpdateFrequency = 15f;
public float BeamCooldown = 3;
public GlimmerTier LastGlimmerTier = GlimmerTier.Minimal;
public bool GhostsVisible = false;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, ComponentRemove>(OnComponentRemove);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, PowerChangedEvent>(OnPowerChanged);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, GlimmerTierChangedEvent>(OnTierChanged);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, GetVerbsEvent<AlternativeVerb>>(AddShockVerb);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, DamageChangedEvent>(OnDamageChanged);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, DestructionEventArgs>(OnDestroyed);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
SubscribeLocalEvent<SharedGlimmerReactiveComponent, AttemptMeleeThrowOnHitEvent>(OnMeleeThrowOnHitAttempt);
}
/// <summary>
/// Update relevant state on an Entity.
/// </summary>
/// <param name="glimmerTierDelta">The number of steps in tier
/// difference since last update. This can be zero for the sake of
/// toggling the enabled states.</param>
private void UpdateEntityState(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTier currentGlimmerTier, int glimmerTierDelta)
{
var isEnabled = true;
if (component.RequiresApcPower
&& TryComp(uid, out ApcPowerReceiverComponent? apcPower))
isEnabled = apcPower.Powered;
_appearanceSystem.SetData(uid, GlimmerReactiveVisuals.GlimmerTier, isEnabled ? currentGlimmerTier : GlimmerTier.Minimal);
// Update ambient sound
if (TryComp(uid, out GlimmerSoundComponent? glimmerSound)
&& TryComp(uid, out AmbientSoundComponent? ambientSoundComponent)
&& glimmerSound.GetSound(currentGlimmerTier, out SoundSpecifier? spec)
&& spec != null)
_sharedAmbientSoundSystem.SetSound(uid, spec, ambientSoundComponent);
// Update point light
if (component.ModulatesPointLight
&& _pointLightSystem.TryGetLight(uid, out var pointLight))
{
_pointLightSystem.SetEnabled(uid, isEnabled ? currentGlimmerTier != GlimmerTier.Minimal : false, pointLight);
_pointLightSystem.SetEnergy(uid, pointLight.Energy + glimmerTierDelta * component.GlimmerToLightEnergyFactor, pointLight);
_pointLightSystem.SetRadius(uid, pointLight.Radius + glimmerTierDelta * component.GlimmerToLightRadiusFactor, pointLight);
}
}
/// <summary>
/// Track when the component comes online so it can be given the
/// current status of the glimmer tier, if it wasn't around when an
/// update went out.
/// </summary>
private void OnMapInit(EntityUid uid, SharedGlimmerReactiveComponent component, MapInitEvent args)
{
if (component.RequiresApcPower && !HasComp<ApcPowerReceiverComponent>(uid))
_sawmill.Warning($"{ToPrettyString(uid)} had RequiresApcPower set to true but no ApcPowerReceiverComponent was found on init.");
UpdateEntityState(uid, component, LastGlimmerTier, (int) LastGlimmerTier);
}
/// <summary>
/// Reset the glimmer tier appearance data if the component's removed,
/// just in case some objects can temporarily become reactive to the
/// glimmer.
/// </summary>
private void OnComponentRemove(EntityUid uid, SharedGlimmerReactiveComponent component, ComponentRemove args)
{
UpdateEntityState(uid, component, GlimmerTier.Minimal, -1 * (int) LastGlimmerTier);
}
/// <summary>
/// If the Entity has RequiresApcPower set to true, this will force an
/// update to the entity's state.
/// </summary>
private void OnPowerChanged(EntityUid uid, SharedGlimmerReactiveComponent component, ref PowerChangedEvent args)
{
if (component.RequiresApcPower)
UpdateEntityState(uid, component, LastGlimmerTier, 0);
}
/// <summary>
/// Enable / disable special effects from higher tiers.
/// </summary>
private void OnTierChanged(EntityUid uid, SharedGlimmerReactiveComponent component, GlimmerTierChangedEvent args)
{
if (!TryComp<ApcPowerReceiverComponent>(uid, out var receiver))
return;
if (args.CurrentTier >= GlimmerTier.Dangerous)
{
if (!Transform(uid).Anchored)
AnchorOrExplode(uid);
receiver.PowerDisabled = false;
receiver.NeedsPower = false;
}
else
{
receiver.NeedsPower = true;
}
}
private void AddShockVerb(EntityUid uid, SharedGlimmerReactiveComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess
|| !args.CanInteract
|| !TryComp<ApcPowerReceiverComponent>(uid, out var receiver)
|| receiver.NeedsPower)
return;
AlternativeVerb verb = new()
{
Act = () =>
{
_sharedAudioSystem.PlayPvs(component.ShockNoises, args.User);
_electrocutionSystem.TryDoElectrocution(args.User, null, (int) _glimmerSystem.GlimmerOutput / 200, TimeSpan.FromSeconds(_glimmerSystem.GlimmerOutput / 100), false);
},
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")),
Text = Loc.GetString("power-switch-component-toggle-verb"),
Priority = -3
};
args.Verbs.Add(verb);
}
private void OnDamageChanged(EntityUid uid, SharedGlimmerReactiveComponent component, DamageChangedEvent args)
{
if (args.Origin == null
|| !_random.Prob((float) _glimmerSystem.GetGlimmerEquilibriumRatio() / 10))
return;
var tier = _glimmerSystem.GetGlimmerTier();
if (tier < GlimmerTier.High)
return;
Beam(uid, args.Origin.Value, tier);
}
private void OnDestroyed(EntityUid uid, SharedGlimmerReactiveComponent component, DestructionEventArgs args)
{
Spawn("MaterialBluespace1", Transform(uid).Coordinates);
var tier = _glimmerSystem.GetGlimmerTier();
if (tier < GlimmerTier.High)
return;
var glimmer = (float) _glimmerSystem.GlimmerOutput;
var totalIntensity = glimmer * 2;
var slope = 11 - glimmer / 100;
var maxIntensity = 20;
var removed = glimmer * _random.NextFloat(0.1f, 0.15f);
_glimmerSystem.DeltaGlimmerInput(-removed);
BeamRandomNearProber(uid, (int) glimmer / 350, glimmer / 50);
_explosionSystem.QueueExplosion(uid, "Default", totalIntensity, slope, maxIntensity);
}
private void OnUnanchorAttempt(EntityUid uid, SharedGlimmerReactiveComponent component, UnanchorAttemptEvent args)
{
if (_glimmerSystem.GetGlimmerTier() < GlimmerTier.Dangerous)
return;
_sharedAudioSystem.PlayPvs(component.ShockNoises, args.User);
_electrocutionSystem.TryDoElectrocution(args.User, null, (int) _glimmerSystem.GlimmerOutput / 200, TimeSpan.FromSeconds(_glimmerSystem.GlimmerOutput / 100), false);
args.Cancel();
}
public void BeamRandomNearProber(EntityUid prober, int targets, float range = 10f)
{
List<EntityUid> targetList = new();
foreach (var (target, status) in _entityLookupSystem.GetEntitiesInRange<StatusEffectsComponent>(_transformSystem.GetMapCoordinates(prober), range))
if (status.AllowedEffects.Contains("Electrocution"))
targetList.Add(target);
foreach (var reactive in _entityLookupSystem.GetEntitiesInRange<SharedGlimmerReactiveComponent>(_transformSystem.GetMapCoordinates(prober), range))
targetList.Add(reactive);
_random.Shuffle(targetList);
foreach (var target in targetList)
{
if (targets <= 0)
return;
Beam(prober, target, _glimmerSystem.GetGlimmerTier(), false);
targets--;
}
}
private void Beam(EntityUid prober, EntityUid target, GlimmerTier tier, bool obeyCD = true)
{
if (obeyCD && BeamCooldown != 0
|| Deleted(prober)
|| Deleted(target))
return;
var lxform = Transform(prober);
var txform = Transform(target);
if (!lxform.Coordinates.TryDistance(EntityManager, txform.Coordinates, out var distance))
return;
if (distance > _glimmerSystem.GlimmerOutput / 100)
return;
string beamproto;
switch (tier)
{
case GlimmerTier.Dangerous:
beamproto = "SuperchargedLightning";
break;
case GlimmerTier.Critical:
beamproto = "HyperchargedLightning";
break;
default:
beamproto = "ChargedLightning";
break;
}
_lightning.ShootLightning(prober, target, beamproto);
BeamCooldown += 3f;
}
private void AnchorOrExplode(EntityUid uid)
{
if (Transform(uid).GridUid is null)
_destructibleSystem.DestroyEntity(uid);
if (HasComp<AnchorableComponent>(uid))
_transformSystem.AnchorEntity(uid, Transform(uid));
}
private void OnMeleeThrowOnHitAttempt(Entity<SharedGlimmerReactiveComponent> ent, ref AttemptMeleeThrowOnHitEvent args)
{
if (_glimmerSystem.GetGlimmerTier() < GlimmerTier.Dangerous)
return;
args.Cancelled = true;
args.Handled = true;
_lightning.ShootRandomLightnings(ent, 10, 2, "SuperchargedLightning", 2, false);
// Check if the parent of the user is alive, which will be the case if the user is an item and is being held.
var zapTarget = _transformSystem.GetParentUid(args.User);
if (TryComp<MindContainerComponent>(zapTarget, out _))
_electrocutionSystem.TryDoElectrocution(zapTarget, ent, 5, TimeSpan.FromSeconds(3), true,
ignoreInsulation: true);
}
private void Reset(RoundRestartCleanupEvent args)
{
Accumulator = 0;
// It is necessary that the GlimmerTier is reset to the default
// tier on round restart. This system will persist through
// restarts, and an undesired event will fire as a result after the
// start of the new round, causing modulatable PointLights to have
// negative Energy if the tier was higher than Minimal on restart.
LastGlimmerTier = GlimmerTier.Minimal;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
Accumulator += frameTime;
BeamCooldown = Math.Max(0, BeamCooldown - frameTime);
if (Accumulator > UpdateFrequency)
{
var currentGlimmerTier = _glimmerSystem.GetGlimmerTier();
var reactives = EntityQuery<SharedGlimmerReactiveComponent>();
if (currentGlimmerTier != LastGlimmerTier)
{
var glimmerTierDelta = (int) currentGlimmerTier - (int) LastGlimmerTier;
var ev = new GlimmerTierChangedEvent(LastGlimmerTier, currentGlimmerTier, glimmerTierDelta);
foreach (var reactive in reactives)
{
UpdateEntityState(reactive.Owner, reactive, currentGlimmerTier, glimmerTierDelta);
RaiseLocalEvent(reactive.Owner, ev);
}
LastGlimmerTier = currentGlimmerTier;
}
if (currentGlimmerTier == GlimmerTier.Critical)
{
_ghostSystem.MakeVisible(true);
_revenantSystem.MakeVisible(true);
GhostsVisible = true;
foreach (var reactive in reactives)
BeamRandomNearProber(reactive.Owner, 1, 12);
}
else if (GhostsVisible == true)
{
_ghostSystem.MakeVisible(false);
_revenantSystem.MakeVisible(false);
GhostsVisible = false;
}
Accumulator = 0;
}
}
}
/// <summary>
/// This event is fired when the broader glimmer tier has changed,
/// not on every single adjustment to the glimmer count.
///
/// <see cref="GlimmerSystem.GetGlimmerTier"/> has the exact
/// values corresponding to tiers.
/// </summary>
public class GlimmerTierChangedEvent : EntityEventArgs
{
/// <summary>
/// What was the last glimmer tier before this event fired?
/// </summary>
public readonly GlimmerTier LastTier;
/// <summary>
/// What is the current glimmer tier?
/// </summary>
public readonly GlimmerTier CurrentTier;
/// <summary>
/// What is the change in tiers between the last and current tier?
/// </summary>
public readonly int TierDelta;
public GlimmerTierChangedEvent(GlimmerTier lastTier, GlimmerTier currentTier, int tierDelta)
{
LastTier = lastTier;
CurrentTier = currentTier;
TierDelta = tierDelta;
}
}