mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Merge pull request #77 from All-Of-Us-Mods/dev
- Added Vanilla Button Click Event (and respective Cancel Event) - Added accessibility settings for sabotage flashes and blares - Added an in-game slider for resizing ability buttons
This commit is contained in:
12 files changed
+403
-13
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.3.4</VersionPrefix>
|
||||
<VersionPrefix>0.3.5</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
@@ -17,7 +18,7 @@ public static class ExampleEventHandlers
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
// You can register event handlers with the MiraEventManager class.
|
||||
// You can register event handlers with the MiraEventManager class
|
||||
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
||||
@@ -37,6 +38,21 @@ public static class ExampleEventHandlers
|
||||
MiraEventManager.UnregisterEventHandler(handle);
|
||||
}
|
||||
|
||||
// If you want to check vanilla buttons, use something like this!
|
||||
[RegisterEvent]
|
||||
public static void VanillaButtonClickHandler(VanillaButtonClickEvent @event)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Button.buttonLabelText.text} is being used!");
|
||||
if (@event.PlayerTarget != null)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.PlayerTarget.Data.PlayerName} is being targeted!");
|
||||
}
|
||||
else if (@event.VentTarget != null)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"Vent with id {@event.VentTarget.Id.ToString(CultureInfo.InvariantCulture)} is being targeted!");
|
||||
}
|
||||
}
|
||||
|
||||
// If you want to add extra votes to a player, do something like this.
|
||||
[RegisterEvent]
|
||||
public static void StartMeetingEvent(StartMeetingEvent _)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using MiraAPI.Hud;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Vanilla Button type.</typeparam>
|
||||
public sealed class VanillaButtonCancelledEvent<T> : MiraEvent where T : AbilityButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Vanilla Button whose click was cancelled.
|
||||
/// </summary>
|
||||
public T Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="button">The Mira Button whose click was cancelled.</param>
|
||||
public VanillaButtonCancelledEvent(T button)
|
||||
{
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
|
||||
/// </summary>
|
||||
public sealed class VanillaButtonCancelledEvent : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Vanilla Button whose click was cancelled.
|
||||
/// </summary>
|
||||
public AbilityButton Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="button">The Vanilla Button whose click was cancelled.</param>
|
||||
public VanillaButtonCancelledEvent(AbilityButton button)
|
||||
{
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using MiraAPI.Hud;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Vanilla Button type.</typeparam>
|
||||
public sealed class VanillaButtonClickEvent<T> : MiraCancelableEvent where T : AbilityButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Vanilla Button that was clicked.
|
||||
/// </summary>
|
||||
public T Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generic click event that is invoked before this button-specific event.
|
||||
/// </summary>
|
||||
public VanillaButtonClickEvent GenericClickEvent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="button">The Mira Button that was clicked.</param>
|
||||
/// <param name="genericClickEvent">The generic click event invoked before button-specific events.</param>
|
||||
public VanillaButtonClickEvent(T button, VanillaButtonClickEvent genericClickEvent)
|
||||
{
|
||||
GenericClickEvent = genericClickEvent;
|
||||
Button = button;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
|
||||
/// </summary>
|
||||
public sealed class VanillaButtonClickEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Vanilla Button that was clicked.
|
||||
/// </summary>
|
||||
public AbilityButton Button { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target player, if any, of the Vanilla Button that was clicked.
|
||||
/// </summary>
|
||||
public PlayerControl? PlayerTarget { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target vent, if any, of the Vanilla Button that was clicked.
|
||||
/// </summary>
|
||||
public Vent? VentTarget { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="button">The Vanilla Button that was clicked.</param>
|
||||
/// <param name="playerTarget">The Player target, if any.</param>
|
||||
/// <param name="ventTarget">The Vent target, if any.</param>
|
||||
public VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null)
|
||||
{
|
||||
Button = button;
|
||||
PlayerTarget = playerTarget;
|
||||
VentTarget = ventTarget;
|
||||
}
|
||||
}
|
||||
@@ -69,4 +69,4 @@ public class BaseKeybind
|
||||
{
|
||||
Handler -= action;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace MiraAPI.Keybinds;
|
||||
public class MiraKeybind : BaseKeybind
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets name of the keybind
|
||||
/// Gets name of the keybind.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.LocalSettings.Attributes;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Mira API Config File Handler
|
||||
/// Mira API Config File Handler.
|
||||
/// </summary>
|
||||
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
{
|
||||
@@ -21,22 +23,67 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
HideIconOnHover = false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value stored for scaling buttons properly.
|
||||
/// </summary>
|
||||
public static float OldButtonScaleFactor { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Open()
|
||||
{
|
||||
base.Open();
|
||||
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnOptionChanged(ConfigEntryBase configEntry)
|
||||
{
|
||||
base.OnOptionChanged(configEntry);
|
||||
if (configEntry == ButtonUIFactorSlider)
|
||||
{
|
||||
if (HudManager.InstanceExists)
|
||||
{
|
||||
HudManagerPatches.ResizeUI(1f / OldButtonScaleFactor);
|
||||
HudManagerPatches.ResizeUI(ButtonUIFactorSlider.Value);
|
||||
}
|
||||
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Visuals/UI", "Show Modifiers HUD on Left Side", false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to show keybinds on buttons.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds on Buttons", true);
|
||||
// This would be placed in the keybinds menu, but it crashes for Epic Games users. - Atony
|
||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Visuals/UI", "Show Keybinds on Buttons", true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scale of the buttons.
|
||||
/// </summary>
|
||||
[LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
|
||||
public ConfigEntry<float> ButtonUIFactorSlider { get; private set; } =
|
||||
config.Bind("Visuals/UI", "Button Scale Factor", 0.75f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to show the red flash from sabotages.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> EnableSabotageFlashes { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Flashes", true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to enable the sabotage sound effects or not.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> EnableSabotageBlares { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Blare", true);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections;
|
||||
using BepInEx.Unity.IL2CPP.Utils.Collections;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.LocalSettings;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Accessibility;
|
||||
[HarmonyPatch]
|
||||
public static class HudManagerFlashPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartReactorFlash))]
|
||||
[HarmonyPrefix]
|
||||
public static bool ReactorFlashPrefix(HudManager __instance)
|
||||
{
|
||||
if (__instance.ReactorFlash == null)
|
||||
{
|
||||
__instance.ReactorFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartOxyFlash))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OxygenFlashPrefix(HudManager __instance)
|
||||
{
|
||||
if (__instance.OxyFlash == null)
|
||||
{
|
||||
__instance.OxyFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static IEnumerator CoReactorFlash()
|
||||
{
|
||||
if (!HudManager.InstanceExists)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
var hudManager = HudManager.Instance;
|
||||
WaitForSeconds wait = new WaitForSeconds(1f);
|
||||
bool light = false;
|
||||
|
||||
hudManager.FullScreen.color = new Color(1f, 0f, 0f, 0.37254903f);
|
||||
while (true)
|
||||
{
|
||||
var settins = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
|
||||
hudManager.FullScreen.gameObject.SetActive(settins.EnableSabotageFlashes.Value && !hudManager.FullScreen.gameObject.activeSelf);
|
||||
if (settins.EnableSabotageBlares.Value)
|
||||
{
|
||||
SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null);
|
||||
}
|
||||
if (hudManager.lightFlashHandle == null)
|
||||
{
|
||||
hudManager.lightFlashHandle = DestroyableSingleton<DualshockLightManager>.Instance.AllocateLight();
|
||||
hudManager.lightFlashHandle.color = new Color(1f, 0f, 0f, 1f);
|
||||
hudManager.lightFlashHandle.intensity = 1f;
|
||||
}
|
||||
light = !light;
|
||||
hudManager.lightFlashHandle.color.SetAlpha(light ? 1f : 0f);
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Patches to invoke vent related MiraEvents.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class VanillaButtonPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.DoClick))]
|
||||
public static bool DoClickPrefix(AbilityButton __instance)
|
||||
{
|
||||
// Invoke the generic button click event.
|
||||
PlayerControl? playerTarget = null;
|
||||
Vent? ventTarget = null;
|
||||
var role = PlayerControl.LocalPlayer.Data.Role;
|
||||
if (role.Role is RoleTypes.Tracker)
|
||||
{
|
||||
playerTarget = role.Cast<TrackerRole>().currentTarget;
|
||||
}
|
||||
else if (role.Role is RoleTypes.GuardianAngel)
|
||||
{
|
||||
playerTarget = role.Cast<GuardianAngelRole>().currentTarget;
|
||||
}
|
||||
else if (role.Role is RoleTypes.Engineer)
|
||||
{
|
||||
ventTarget = role.Cast<EngineerRole>().currentTarget;
|
||||
}
|
||||
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget, ventTarget);
|
||||
MiraEventManager.InvokeEvent(genericEvent);
|
||||
if (genericEvent.IsCancelled)
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
|
||||
}
|
||||
|
||||
if (!genericEvent.IsCancelled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(SecondaryAbilityButton), nameof(SecondaryAbilityButton.DoClick))]
|
||||
public static bool SecondaryDoClickPrefix(AbilityButton __instance)
|
||||
{
|
||||
// Invoke the generic button click event.
|
||||
PlayerControl? playerTarget = null;
|
||||
var role = PlayerControl.LocalPlayer.Data.Role;
|
||||
if (role.Role is RoleTypes.Detective)
|
||||
{
|
||||
playerTarget = role.Cast<DetectiveRole>().currentTarget;
|
||||
}
|
||||
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget);
|
||||
MiraEventManager.InvokeEvent(genericEvent);
|
||||
if (genericEvent.IsCancelled)
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
|
||||
}
|
||||
|
||||
if (!genericEvent.IsCancelled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
@@ -29,6 +30,84 @@ public static class HudManagerPatches
|
||||
|
||||
internal static List<TextMeshPro> ModdedKeybindIcons = new();
|
||||
|
||||
public static IEnumerator CoResizeUI()
|
||||
{
|
||||
while (!HudManager.Instance)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
|
||||
}
|
||||
|
||||
public static void ResizeUI(float scaleFactor)
|
||||
{
|
||||
var baseButtons = HudManager.Instance.transform.FindChild("Buttons");
|
||||
if (baseButtons != null)
|
||||
{
|
||||
foreach (var aspect in baseButtons.GetComponentsInChildren<AspectPosition>(true))
|
||||
{
|
||||
if (aspect.gameObject == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (aspect.gameObject.transform.parent.name == "TopRight")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (aspect.gameObject.transform.parent.transform.parent.name == "TopRight")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
|
||||
aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor);
|
||||
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var button in HudManager.Instance.GetComponentsInChildren<ActionButton>(true))
|
||||
{
|
||||
if (button.gameObject == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
button.gameObject.SetActive(!button.isActiveAndEnabled);
|
||||
button.gameObject.transform.localScale *= scaleFactor;
|
||||
button.gameObject.SetActive(!button.isActiveAndEnabled);
|
||||
}
|
||||
|
||||
if (baseButtons != null)
|
||||
{
|
||||
foreach (var arrange in baseButtons.GetComponentsInChildren<GridArrange>(true))
|
||||
{
|
||||
if (!arrange.gameObject || !arrange.transform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
|
||||
arrange.CellSize = new Vector2(scaleFactor, scaleFactor);
|
||||
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
|
||||
if (arrange.isActiveAndEnabled && arrange.gameObject.transform.childCount != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
arrange.ArrangeChilds();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Error($"Error arranging child objects in GridArrange: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Trigger hudstart on current custom gamemode
|
||||
@@ -149,6 +228,7 @@ public static class HudManagerPatches
|
||||
var comp = buttonObj.GetComponent<ActionButton>();
|
||||
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
|
||||
}
|
||||
Coroutines.Start(CoResizeUI());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -39,7 +39,7 @@ public static class LogicRoleSelectionHnsPatch
|
||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
||||
{
|
||||
for (;;)
|
||||
while (true)
|
||||
{
|
||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
||||
int count = roleAssignmentData2.Count;
|
||||
@@ -122,7 +122,7 @@ public static class LogicRoleSelectionHnsPatch
|
||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
||||
{
|
||||
for (;;)
|
||||
while (true)
|
||||
{
|
||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
||||
int count = roleAssignmentData2.Count;
|
||||
|
||||
@@ -36,7 +36,7 @@ public static class LogicRoleSelectionNormalPatch
|
||||
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
|
||||
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
|
||||
{
|
||||
for (;;)
|
||||
while (true)
|
||||
{
|
||||
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
|
||||
int count = roleAssignmentData2.Count;
|
||||
|
||||
Reference in new issue
Block a user