Added more null checks and redid active broadcasting cache.
This commit is contained in:
@@ -15,8 +15,8 @@ using LightlessSync.UtilsEnum.Enum;
|
|||||||
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
|
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
@@ -32,10 +32,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
private readonly LightlessMediator _mediator;
|
private readonly LightlessMediator _mediator;
|
||||||
public LightlessMediator Mediator => _mediator;
|
public LightlessMediator Mediator => _mediator;
|
||||||
|
|
||||||
private bool mEnabled = false;
|
private bool _mEnabled = false;
|
||||||
private bool _needsLabelRefresh = false;
|
private bool _needsLabelRefresh = false;
|
||||||
private AddonNamePlate* mpNameplateAddon = null;
|
private AddonNamePlate* _mpNameplateAddon = null;
|
||||||
private readonly AtkTextNode*[] mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
|
private readonly AtkTextNode*[] _mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
|
||||||
private readonly int[] _cachedNameplateTextWidths = new int[AddonNamePlate.NumNamePlateObjects];
|
private readonly int[] _cachedNameplateTextWidths = new int[AddonNamePlate.NumNamePlateObjects];
|
||||||
private readonly int[] _cachedNameplateTextHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
private readonly int[] _cachedNameplateTextHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
||||||
private readonly int[] _cachedNameplateContainerHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
private readonly int[] _cachedNameplateContainerHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
||||||
@@ -44,10 +44,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
internal const uint mNameplateNodeIDBase = 0x7D99D500;
|
internal const uint mNameplateNodeIDBase = 0x7D99D500;
|
||||||
private const string DefaultLabelText = "LightFinder";
|
private const string DefaultLabelText = "LightFinder";
|
||||||
private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn;
|
private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn;
|
||||||
private const int ContainerOffsetX = 50;
|
private const int _containerOffsetX = 50;
|
||||||
private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon);
|
private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon);
|
||||||
|
|
||||||
private volatile HashSet<string> _activeBroadcastingCids = [];
|
private ImmutableHashSet<string> _activeBroadcastingCids = [];
|
||||||
|
|
||||||
public NameplateHandler(ILogger<NameplateHandler> logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator, IClientState clientState, PairManager pairManager)
|
public NameplateHandler(ILogger<NameplateHandler> logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator, IClientState clientState, PairManager pairManager)
|
||||||
{
|
{
|
||||||
@@ -74,17 +74,17 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
DisableNameplate();
|
DisableNameplate();
|
||||||
DestroyNameplateNodes();
|
DestroyNameplateNodes();
|
||||||
_mediator.Unsubscribe<PriorityFrameworkUpdateMessage>(this);
|
_mediator.Unsubscribe<PriorityFrameworkUpdateMessage>(this);
|
||||||
mpNameplateAddon = null;
|
_mpNameplateAddon = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EnableNameplate()
|
internal void EnableNameplate()
|
||||||
{
|
{
|
||||||
if (!mEnabled)
|
if (!_mEnabled)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_addonLifecycle.RegisterListener(AddonEvent.PostDraw, "NamePlate", NameplateDrawDetour);
|
_addonLifecycle.RegisterListener(AddonEvent.PostDraw, "NamePlate", NameplateDrawDetour);
|
||||||
mEnabled = true;
|
_mEnabled = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
internal void DisableNameplate()
|
internal void DisableNameplate()
|
||||||
{
|
{
|
||||||
if (mEnabled)
|
if (_mEnabled)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -107,7 +107,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
_logger.LogError($"Unknown error while unregistering nameplate listener:\n{e}");
|
_logger.LogError($"Unknown error while unregistering nameplate listener:\n{e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
mEnabled = false;
|
_mEnabled = false;
|
||||||
HideAllNameplateNodes();
|
HideAllNameplateNodes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,15 +116,15 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
|
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
|
||||||
|
|
||||||
if (mpNameplateAddon != pNameplateAddon)
|
if (_mpNameplateAddon != pNameplateAddon)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < mTextNodes.Length; ++i) mTextNodes[i] = null;
|
for (int i = 0; i < _mTextNodes.Length; ++i) _mTextNodes[i] = null;
|
||||||
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
|
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
|
||||||
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
|
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
|
||||||
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
|
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
|
||||||
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
||||||
mpNameplateAddon = pNameplateAddon;
|
_mpNameplateAddon = pNameplateAddon;
|
||||||
if (mpNameplateAddon != null) CreateNameplateNodes();
|
if (_mpNameplateAddon != null) CreateNameplateNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateNameplateNodes();
|
UpdateNameplateNodes();
|
||||||
@@ -139,6 +139,11 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var pNameplateResNode = nameplateObject.Value.NameContainer;
|
var pNameplateResNode = nameplateObject.Value.NameContainer;
|
||||||
|
if (pNameplateResNode == null)
|
||||||
|
continue;
|
||||||
|
if (pNameplateResNode->ChildNode == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
var pNewNode = AtkNodeHelpers.CreateOrphanTextNode(mNameplateNodeIDBase + (uint)i, TextFlags.Edge | TextFlags.Glare);
|
var pNewNode = AtkNodeHelpers.CreateOrphanTextNode(mNameplateNodeIDBase + (uint)i, TextFlags.Edge | TextFlags.Glare);
|
||||||
|
|
||||||
if (pNewNode != null)
|
if (pNewNode != null)
|
||||||
@@ -150,7 +155,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
pLastChild->PrevSiblingNode = (AtkResNode*)pNewNode;
|
pLastChild->PrevSiblingNode = (AtkResNode*)pNewNode;
|
||||||
nameplateObject.Value.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
|
nameplateObject.Value.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
|
||||||
pNewNode->AtkResNode.SetUseDepthBasedPriority(true);
|
pNewNode->AtkResNode.SetUseDepthBasedPriority(true);
|
||||||
mTextNodes[i] = pNewNode;
|
_mTextNodes[i] = pNewNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,12 +163,12 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
private void DestroyNameplateNodes()
|
private void DestroyNameplateNodes()
|
||||||
{
|
{
|
||||||
var pCurrentNameplateAddon = (AddonNamePlate*)_gameGui.GetAddonByName("NamePlate", 1).Address;
|
var pCurrentNameplateAddon = (AddonNamePlate*)_gameGui.GetAddonByName("NamePlate", 1).Address;
|
||||||
if (mpNameplateAddon == null || mpNameplateAddon != pCurrentNameplateAddon)
|
if (_mpNameplateAddon == null || _mpNameplateAddon != pCurrentNameplateAddon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < AddonNamePlate.NumNamePlateObjects; ++i)
|
for (int i = 0; i < AddonNamePlate.NumNamePlateObjects; ++i)
|
||||||
{
|
{
|
||||||
var pTextNode = mTextNodes[i];
|
var pTextNode = _mTextNodes[i];
|
||||||
var pNameplateNode = GetNameplateComponentNode(i);
|
var pNameplateNode = GetNameplateComponentNode(i);
|
||||||
if (pTextNode != null && pNameplateNode != null)
|
if (pTextNode != null && pNameplateNode != null)
|
||||||
{
|
{
|
||||||
@@ -175,7 +180,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
pTextNode->AtkResNode.NextSiblingNode->PrevSiblingNode = pTextNode->AtkResNode.PrevSiblingNode;
|
pTextNode->AtkResNode.NextSiblingNode->PrevSiblingNode = pTextNode->AtkResNode.PrevSiblingNode;
|
||||||
pNameplateNode->Component->UldManager.UpdateDrawNodeList();
|
pNameplateNode->Component->UldManager.UpdateDrawNodeList();
|
||||||
pTextNode->AtkResNode.Destroy(true);
|
pTextNode->AtkResNode.Destroy(true);
|
||||||
mTextNodes[i] = null;
|
_mTextNodes[i] = null;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -192,7 +197,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
private void HideAllNameplateNodes()
|
private void HideAllNameplateNodes()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < mTextNodes.Length; ++i)
|
for (int i = 0; i < _mTextNodes.Length; ++i)
|
||||||
{
|
{
|
||||||
HideNameplateTextNode(i);
|
HideNameplateTextNode(i);
|
||||||
}
|
}
|
||||||
@@ -200,22 +205,34 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
private void UpdateNameplateNodes()
|
private void UpdateNameplateNodes()
|
||||||
{
|
{
|
||||||
var framework = Framework.Instance();
|
var currentAddon = (AddonNamePlate*)_gameGui.GetAddonByName("NamePlate", 1).Address;
|
||||||
var ui3DModule = framework->GetUIModule()->GetUI3DModule();
|
if (_mpNameplateAddon == null || currentAddon == null || currentAddon != _mpNameplateAddon)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var framework = Framework.Instance();
|
||||||
|
|
||||||
|
var ui3DModule = framework->GetUIModule()->GetUI3DModule();
|
||||||
if (ui3DModule == null)
|
if (ui3DModule == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i)
|
var vec = ui3DModule->NamePlateObjectInfoPointers;
|
||||||
|
if (vec.IsEmpty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var safeCount = System.Math.Min(
|
||||||
|
ui3DModule->NamePlateObjectInfoCount,
|
||||||
|
vec.Length
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < safeCount; ++i)
|
||||||
{
|
{
|
||||||
if (ui3DModule->NamePlateObjectInfoPointers.IsEmpty) continue;
|
var config = _configService.Current;
|
||||||
|
|
||||||
var objectInfoPtr = ui3DModule->NamePlateObjectInfoPointers[i];
|
var objectInfoPtr = vec[i];
|
||||||
|
if (objectInfoPtr == null)
|
||||||
if (objectInfoPtr == null) continue;
|
continue;
|
||||||
|
|
||||||
var objectInfo = objectInfoPtr.Value;
|
var objectInfo = objectInfoPtr.Value;
|
||||||
|
|
||||||
if (objectInfo == null || objectInfo->GameObject == null)
|
if (objectInfo == null || objectInfo->GameObject == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -223,62 +240,61 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
if (nameplateIndex < 0 || nameplateIndex >= AddonNamePlate.NumNamePlateObjects)
|
if (nameplateIndex < 0 || nameplateIndex >= AddonNamePlate.NumNamePlateObjects)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var pNode = mTextNodes[nameplateIndex];
|
var pNode = _mTextNodes[nameplateIndex];
|
||||||
if (pNode == null)
|
if (pNode == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (mpNameplateAddon == null)
|
// CID gating
|
||||||
continue;
|
|
||||||
|
|
||||||
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject);
|
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject);
|
||||||
|
|
||||||
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_configService.Current.LightfinderLabelShowOwn && (objectInfo->GameObject->GetGameObjectId() == _clientState.LocalPlayer.GameObjectId))
|
var local = _clientState.LocalPlayer;
|
||||||
|
if (!config.LightfinderLabelShowOwn && local != null &&
|
||||||
|
objectInfo->GameObject->GetGameObjectId() == local.GameObjectId)
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_configService.Current.LightfinderLabelShowPaired && VisibleUserIds.Any(u => u == objectInfo->GameObject->GetGameObjectId()))
|
var visibleUserIds = VisibleUserIds;
|
||||||
{
|
var hidePaired = !config.LightfinderLabelShowPaired;
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
|
||||||
continue;
|
var goId = (ulong)objectInfo->GameObject->GetGameObjectId();
|
||||||
}
|
if (hidePaired && visibleUserIds.Contains(goId))
|
||||||
|
|
||||||
var nameplateObject = mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
|
|
||||||
nameplateObject.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
|
|
||||||
|
|
||||||
var pNameplateIconNode = nameplateObject.MarkerIcon;
|
|
||||||
var pNameplateResNode = nameplateObject.NameContainer;
|
|
||||||
var pNameplateTextNode = nameplateObject.NameText;
|
|
||||||
bool IsVisible = pNameplateIconNode->AtkResNode.IsVisible() || (pNameplateResNode->IsVisible() && pNameplateTextNode->AtkResNode.IsVisible()) || _configService.Current.LightfinderLabelShowHidden;
|
|
||||||
pNode->AtkResNode.ToggleVisibility(IsVisible);
|
|
||||||
|
|
||||||
if (nameplateObject.RootComponentNode == null ||
|
|
||||||
nameplateObject.NameContainer == null ||
|
|
||||||
nameplateObject.NameText == null)
|
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nameplateObject = _mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
|
||||||
|
var root = nameplateObject.RootComponentNode;
|
||||||
var nameContainer = nameplateObject.NameContainer;
|
var nameContainer = nameplateObject.NameContainer;
|
||||||
var nameText = nameplateObject.NameText;
|
var nameText = nameplateObject.NameText;
|
||||||
|
var marker = nameplateObject.MarkerIcon;
|
||||||
|
|
||||||
if (nameContainer == null || nameText == null)
|
if (root == null || nameContainer == null || nameText == null)
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root->Component->UldManager.UpdateDrawNodeList();
|
||||||
|
|
||||||
|
bool isVisible =
|
||||||
|
((marker != null) && marker->AtkResNode.IsVisible()) ||
|
||||||
|
(nameContainer->IsVisible() && nameText->AtkResNode.IsVisible()) ||
|
||||||
|
config.LightfinderLabelShowHidden;
|
||||||
|
|
||||||
|
pNode->AtkResNode.ToggleVisibility(isVisible);
|
||||||
|
if (!isVisible)
|
||||||
|
continue;
|
||||||
|
|
||||||
var labelColor = UIColors.Get("Lightfinder");
|
var labelColor = UIColors.Get("Lightfinder");
|
||||||
var edgeColor = UIColors.Get("LightfinderEdge");
|
var edgeColor = UIColors.Get("LightfinderEdge");
|
||||||
var config = _configService.Current;
|
|
||||||
|
|
||||||
var scaleMultiplier = System.Math.Clamp(config.LightfinderLabelScale, 0.5f, 2.0f);
|
var scaleMultiplier = System.Math.Clamp(config.LightfinderLabelScale, 0.5f, 2.0f);
|
||||||
var baseScale = config.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
var baseScale = config.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
||||||
@@ -545,7 +561,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
}
|
}
|
||||||
private void HideNameplateTextNode(int i)
|
private void HideNameplateTextNode(int i)
|
||||||
{
|
{
|
||||||
var pNode = mTextNodes[i];
|
var pNode = _mTextNodes[i];
|
||||||
if (pNode != null)
|
if (pNode != null)
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
@@ -555,10 +571,10 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
private AddonNamePlate.NamePlateObject? GetNameplateObject(int i)
|
private AddonNamePlate.NamePlateObject? GetNameplateObject(int i)
|
||||||
{
|
{
|
||||||
if (i < AddonNamePlate.NumNamePlateObjects &&
|
if (i < AddonNamePlate.NumNamePlateObjects &&
|
||||||
mpNameplateAddon != null &&
|
_mpNameplateAddon != null &&
|
||||||
mpNameplateAddon->NamePlateObjectArray[i].RootComponentNode != null)
|
_mpNameplateAddon->NamePlateObjectArray[i].RootComponentNode != null)
|
||||||
{
|
{
|
||||||
return mpNameplateAddon->NamePlateObjectArray[i];
|
return _mpNameplateAddon->NamePlateObjectArray[i];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -576,6 +592,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
||||||
.Select(u => (ulong)u.PlayerCharacterId)];
|
.Select(u => (ulong)u.PlayerCharacterId)];
|
||||||
|
|
||||||
|
|
||||||
public void FlagRefresh()
|
public void FlagRefresh()
|
||||||
{
|
{
|
||||||
_needsLabelRefresh = true;
|
_needsLabelRefresh = true;
|
||||||
@@ -592,18 +609,13 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
||||||
{
|
{
|
||||||
var newSet = cids.ToHashSet();
|
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
|
||||||
|
if (ReferenceEquals(_activeBroadcastingCids, newSet) || _activeBroadcastingCids.SetEquals(newSet))
|
||||||
var changed = !_activeBroadcastingCids.SetEquals(newSet);
|
|
||||||
if (!changed)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_activeBroadcastingCids.Clear();
|
// single atomic swap <20> readers always see a consistent snapshot
|
||||||
foreach (var cid in newSet)
|
_activeBroadcastingCids = newSet;
|
||||||
_activeBroadcastingCids.Add(cid);
|
|
||||||
|
|
||||||
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(",", _activeBroadcastingCids));
|
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(",", _activeBroadcastingCids));
|
||||||
|
|
||||||
FlagRefresh();
|
FlagRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user