using System; using Robust.Server.GameStates; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.ViewVariables; namespace Robust.Server.GameObjects { public sealed partial class VisibilitySystem : SharedVisibilitySystem { [Dependency] private PvsSystem _pvs = default!; [Dependency] private IViewVariablesManager _vvManager = default!; private EntityQuery _xformQuery; private EntityQuery _metaQuery; private EntityQuery _visibilityQuery; public override void Initialize() { base.Initialize(); _xformQuery = GetEntityQuery(); _metaQuery = GetEntityQuery(); _visibilityQuery = GetEntityQuery(); SubscribeLocalEvent(OnParentChange); EntityManager.EntityInitialized += OnEntityInit; _vvManager.GetTypeHandler() .AddPath(nameof(VisibilityComponent.Layer), (_, comp) => comp.Layer, (uid, value, comp) => { if (!Resolve(uid, ref comp)) return; SetLayer((uid, comp), value); }); } public override void Shutdown() { base.Shutdown(); EntityManager.EntityInitialized -= OnEntityInit; } public override void AddLayer(Entity ent, ushort layer, bool refresh = true) { ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp(ent.Owner); if ((layer & ent.Comp.Layer) == layer) return; ent.Comp.Layer |= layer; if (refresh) RefreshVisibility(ent); } public override void RemoveLayer(Entity ent, ushort layer, bool refresh = true) { if (!_visibilityQuery.Resolve(ent.Owner, ref ent.Comp, false)) return; if ((layer & ent.Comp.Layer) != layer) return; ent.Comp.Layer &= (ushort)~layer; if (refresh) RefreshVisibility(ent); } public override void SetLayer(Entity ent, ushort layer, bool refresh = true) { ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp(ent.Owner); if (ent.Comp.Layer == layer) return; ent.Comp.Layer = layer; if (refresh) RefreshVisibility(ent); } private void OnParentChange(ref EntParentChangedMessage ev) { RefreshVisibility(ev.Entity); } private void OnEntityInit(Entity ent) { RefreshVisibility(ent.Owner, null, ent.Comp); } public override void RefreshVisibility(EntityUid uid, VisibilityComponent? visibilityComponent = null, MetaDataComponent? meta = null) { RefreshVisibility((uid, visibilityComponent, meta)); } public override void RefreshVisibility(Entity ent) { if (!_metaQuery.Resolve(ent, ref ent.Comp2, false)) return; // Iterate up through parents and calculate the cumulative visibility mask. var mask = GetParentVisibilityMask(ent); // Iterate down through children and propagate mask changes. RecursivelyApplyVisibility(ent.Owner, mask, ent.Comp2); } private void RecursivelyApplyVisibility(EntityUid uid, ushort mask, MetaDataComponent meta) { if (meta.VisibilityMask == mask) return; var xform = _xformQuery.GetComponent(uid); meta.VisibilityMask = mask; _pvs.SyncMetadata(meta); foreach (var child in xform._children) { if (!_metaQuery.TryGetComponent(child, out var childMeta)) continue; var childMask = mask; if (_visibilityQuery.TryGetComponent(child, out var childVis)) childMask |= childVis.Layer; RecursivelyApplyVisibility(child, childMask, childMeta); } } private ushort GetParentVisibilityMask(Entity ent) { ushort visMask = 1; // apparently some content expects everything to have the first bit/flag set to true. if (_visibilityQuery.Resolve(ent.Owner, ref ent.Comp, false)) visMask |= ent.Comp.Layer; // Include parent vis masks if (_xformQuery.TryGetComponent(ent.Owner, out var xform) && xform.ParentUid.IsValid()) visMask |= GetParentVisibilityMask(xform.ParentUid); return visMask; } } }