using System.Diagnostics.CodeAnalysis; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; namespace Robust.Server.GameObjects; public sealed partial class PointLightSystem : SharedPointLightSystem { [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private MetaDataSystem _metadata = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnLightGetState); //SubscribeLocalEvent(OnInserted); SubscribeLocalEvent(OnInserted); SubscribeLocalEvent(OnRemoved); SubscribeLocalEvent(OnLightStartup); SubscribeLocalEvent(OnLightShutdown); SubscribeLocalEvent(OnFlagRemoveAttempt); } private void OnLightStartup(Entity ent, ref ComponentStartup args) { UpdatePriority(ent.Owner, ent.Comp, MetaData(ent.Owner)); SetContainerOccluded(ent.Owner, IsCurrentContainerOrParentOccluder(ent.Owner), ent.Comp); _lightTree.QueueTreeUpdate(ent.Owner, ent.Comp); } private void OnLightShutdown(Entity ent, ref ComponentShutdown args) { UpdatePriority(ent.Owner, ent.Comp, MetaData(ent.Owner)); _lightTree.QueueTreeUpdate(ent.Owner, ent.Comp); } private void OnFlagRemoveAttempt(Entity ent, ref MetaFlagRemoveAttemptEvent args) { if (IsHighPriority(ent.Comp)) args.ToRemove &= ~MetaDataFlags.PvsPriority; } private bool IsHighPriority(SharedPointLightComponent comp) { return comp is { Enabled: true, CastShadows: true, Radius: > 7, LifeStage: <= ComponentLifeStage.Running }; } private void OnInserted(EntityUid uid, PointLightComponent component, EntGotInsertedIntoContainerMessage args) { SetContainerOccluded(uid, IsContainerOrParentOccluder(args.Container), component); } private void OnRemoved(EntityUid uid, PointLightComponent component, EntGotRemovedFromContainerMessage args) { SetContainerOccluded(uid, IsCurrentContainerOrParentOccluder(uid), component); } private void OnLightGetState(EntityUid uid, PointLightComponent component, ref ComponentGetState args) { args.State = new PointLightComponentState() { Color = component.Color, Enabled = component.Enabled, Energy = component.Energy, Offset = component.Offset, Radius = component.Radius, Softness = component.Softness, Falloff = component.Falloff, CurveFactor = component.CurveFactor, CastShadows = component.CastShadows, ContainerOccluded = component.ContainerOccluded, }; } protected override void UpdatePriority(EntityUid uid, SharedPointLightComponent comp, MetaDataComponent meta) { _metadata.SetFlag((uid, meta), MetaDataFlags.PvsPriority, IsHighPriority(comp)); } public override SharedPointLightComponent EnsureLight(EntityUid uid) { return EnsureComp(uid); } public override bool ResolveLight(EntityUid uid, [NotNullWhen(true)] ref SharedPointLightComponent? component) { if (component is not null) return true; TryComp(uid, out var comp); component = comp; return component != null; } public override bool TryGetLight(EntityUid uid, [NotNullWhen(true)] out SharedPointLightComponent? component) { if (TryComp(uid, out var comp)) { component = comp; return true; } component = null; return false; } public override bool RemoveLightDeferred(EntityUid uid) { return RemCompDeferred(uid); } /// /// Recursively check if a container or any parent container occludes light. /// This is probably a terribly unoptimized way to get the occlusion right /// /// True if any container should block light. private bool IsContainerOrParentOccluder(BaseContainer container) { if (container.OccludesLight) return true; if (!_container.TryGetContainingContainer(container.Owner, out var nextContainer)) return false; return IsContainerOrParentOccluder(nextContainer); } private bool IsCurrentContainerOrParentOccluder(EntityUid uid) { return _container.TryGetContainingContainer(uid, out var container) && IsContainerOrParentOccluder(container); } }