GPose Together fixes
safeguard pose/world data generation loops safeguard more fix gpose together charas getting their collection nuked going outside gpose
This commit is contained in:
@@ -29,7 +29,7 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
foreach (var chara in _handledCharaData)
|
foreach (var chara in _handledCharaData)
|
||||||
{
|
{
|
||||||
_ = RevertHandledChara(chara, reapplyPose: false);
|
_ = RevertHandledChara(chara);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,11 +56,11 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
foreach (var chara in _handledCharaData)
|
foreach (var chara in _handledCharaData)
|
||||||
{
|
{
|
||||||
_ = RevertHandledChara(chara, reapplyPose: false);
|
_ = RevertHandledChara(chara);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RevertChara(string name, Guid? cPlusId, bool reapplyPose = true)
|
public async Task RevertChara(string name, Guid? cPlusId)
|
||||||
{
|
{
|
||||||
Guid applicationId = Guid.NewGuid();
|
Guid applicationId = Guid.NewGuid();
|
||||||
await _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).ConfigureAwait(false);
|
await _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).ConfigureAwait(false);
|
||||||
@@ -75,20 +75,20 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
|||||||
await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, CancellationToken.None).ConfigureAwait(false);
|
await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> RevertHandledChara(string name, bool reapplyPose = true)
|
public async Task<bool> RevertHandledChara(string name)
|
||||||
{
|
{
|
||||||
var handled = _handledCharaData.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal));
|
var handled = _handledCharaData.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal));
|
||||||
if (handled == null) return false;
|
if (handled == null) return false;
|
||||||
_handledCharaData.Remove(handled);
|
_handledCharaData.Remove(handled);
|
||||||
await _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus, reapplyPose)).ConfigureAwait(false);
|
await _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus)).ConfigureAwait(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RevertHandledChara(HandledCharaDataEntry? handled, bool reapplyPose = true)
|
public Task RevertHandledChara(HandledCharaDataEntry? handled)
|
||||||
{
|
{
|
||||||
if (handled == null) return Task.CompletedTask;
|
if (handled == null) return Task.CompletedTask;
|
||||||
_handledCharaData.Remove(handled);
|
_handledCharaData.Remove(handled);
|
||||||
return _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus, reapplyPose));
|
return _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
|
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
|
||||||
|
|||||||
@@ -131,8 +131,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
_lastCreatedCharaData = (playerData, charaDataDownloadDto);
|
_lastCreatedCharaData = (playerData, charaDataDownloadDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastFullPoseData = null;
|
ForceResendOwnData();
|
||||||
_lastWorldData = null;
|
|
||||||
|
|
||||||
if (_lastCreatedCharaData != null)
|
if (_lastCreatedCharaData != null)
|
||||||
await _apiController.GposeLobbyPushCharacterData(_lastCreatedCharaData.Value.Dto)
|
await _apiController.GposeLobbyPushCharacterData(_lastCreatedCharaData.Value.Dto)
|
||||||
@@ -251,8 +250,6 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
node["OffHand"]![bone.Key]!["Rotation"] = $"{bone.Value.RotationX.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationY.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationZ.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationW.ToString(CultureInfo.InvariantCulture)}";
|
node["OffHand"]![bone.Key]!["Rotation"] = $"{bone.Value.RotationX.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationY.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationZ.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationW.ToString(CultureInfo.InvariantCulture)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogTrace(node.ToJsonString(new System.Text.Json.JsonSerializerOptions() { WriteIndented = true }));
|
|
||||||
|
|
||||||
return node.ToJsonString();
|
return node.ToJsonString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,34 +367,47 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
if (!_dalamudUtil.IsInGpose) continue;
|
if (!_dalamudUtil.IsInGpose) continue;
|
||||||
if (_usersInLobby.Count == 0) continue;
|
if (_usersInLobby.Count == 0) continue;
|
||||||
|
|
||||||
var chara = await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false);
|
try
|
||||||
if (_dalamudUtil.IsInGpose)
|
|
||||||
{
|
{
|
||||||
chara = (IPlayerCharacter?)(await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(chara.Name.TextValue, _dalamudUtil.IsInGpose).ConfigureAwait(false));
|
var chara = await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false);
|
||||||
|
if (_dalamudUtil.IsInGpose)
|
||||||
|
{
|
||||||
|
chara = (IPlayerCharacter?)(await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(chara.Name.TextValue, _dalamudUtil.IsInGpose).ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
if (chara == null || chara.Address == nint.Zero) continue;
|
||||||
|
|
||||||
|
var poseJson = await _brio.GetPoseAsync(chara.Address).ConfigureAwait(false);
|
||||||
|
if (string.IsNullOrEmpty(poseJson)) continue;
|
||||||
|
|
||||||
|
var lastFullData = _poseGenerationExecutions++ >= 12 ? null : _lastFullPoseData;
|
||||||
|
lastFullData = _forceResendFullPose ? _lastFullPoseData : lastFullData;
|
||||||
|
|
||||||
|
var poseData = CreatePoseDataFromJson(poseJson, lastFullData);
|
||||||
|
if (!poseData.IsDelta)
|
||||||
|
{
|
||||||
|
_lastFullPoseData = poseData;
|
||||||
|
_lastDeltaPoseData = null;
|
||||||
|
_poseGenerationExecutions = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deltaIsSame = _lastDeltaPoseData != null &&
|
||||||
|
(poseData.Bones.Keys.All(k => _lastDeltaPoseData.Value.Bones.ContainsKey(k)
|
||||||
|
&& poseData.Bones.Values.All(k => _lastDeltaPoseData.Value.Bones.ContainsValue(k))));
|
||||||
|
|
||||||
|
if (_forceResendFullPose || ((poseData.Bones.Any() || poseData.MainHand.Any() || poseData.OffHand.Any())
|
||||||
|
&& (!poseData.IsDelta || (poseData.IsDelta && !deltaIsSame))))
|
||||||
|
{
|
||||||
|
_forceResendFullPose = false;
|
||||||
|
await _apiController.GposeLobbyPushPoseData(poseData).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poseData.IsDelta)
|
||||||
|
_lastDeltaPoseData = poseData;
|
||||||
}
|
}
|
||||||
if (chara == null || chara.Address == nint.Zero) continue;
|
catch (Exception ex)
|
||||||
|
|
||||||
var poseJson = await _brio.GetPoseAsync(chara.Address).ConfigureAwait(false);
|
|
||||||
if (string.IsNullOrEmpty(poseJson)) continue;
|
|
||||||
|
|
||||||
var poseData = CreatePoseDataFromJson(poseJson, _poseGenerationExecutions++ >= 12 ? null : _lastFullPoseData);
|
|
||||||
if (!poseData.IsDelta)
|
|
||||||
{
|
{
|
||||||
_lastFullPoseData = poseData;
|
Logger.LogWarning(ex, "Error during Pose Data Generation");
|
||||||
_lastDeltaPoseData = null;
|
|
||||||
_poseGenerationExecutions = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool deltaIsSame = _lastDeltaPoseData != null &&
|
|
||||||
(poseData.Bones.Keys.All(k => _lastDeltaPoseData.Value.Bones.ContainsKey(k)
|
|
||||||
&& poseData.Bones.Values.All(k => _lastDeltaPoseData.Value.Bones.ContainsValue(k))));
|
|
||||||
|
|
||||||
if ((poseData.Bones.Any() || poseData.MainHand.Any() || poseData.OffHand.Any())
|
|
||||||
&& (!poseData.IsDelta || (poseData.IsDelta && !deltaIsSame)))
|
|
||||||
await _apiController.GposeLobbyPushPoseData(poseData).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (poseData.IsDelta)
|
|
||||||
_lastDeltaPoseData = poseData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,78 +420,86 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
// if there are no players in lobby, don't do anything
|
// if there are no players in lobby, don't do anything
|
||||||
if (_usersInLobby.Count == 0) continue;
|
if (_usersInLobby.Count == 0) continue;
|
||||||
|
|
||||||
// get own player data
|
try
|
||||||
var player = (Dalamud.Game.ClientState.Objects.Types.ICharacter?)(await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false));
|
|
||||||
if (player == null) continue;
|
|
||||||
WorldData worldData;
|
|
||||||
if (_dalamudUtil.IsInGpose)
|
|
||||||
{
|
{
|
||||||
player = await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(player.Name.TextValue, true).ConfigureAwait(false);
|
// get own player data
|
||||||
|
var player = (Dalamud.Game.ClientState.Objects.Types.ICharacter?)(await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false));
|
||||||
if (player == null) continue;
|
if (player == null) continue;
|
||||||
worldData = (await _brio.GetTransformAsync(player.Address).ConfigureAwait(false));
|
WorldData worldData;
|
||||||
}
|
if (_dalamudUtil.IsInGpose)
|
||||||
else
|
|
||||||
{
|
|
||||||
var rotQuaternion = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), player.Rotation);
|
|
||||||
worldData = new()
|
|
||||||
{
|
{
|
||||||
PositionX = player.Position.X,
|
player = await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(player.Name.TextValue, true).ConfigureAwait(false);
|
||||||
PositionY = player.Position.Y,
|
if (player == null) continue;
|
||||||
PositionZ = player.Position.Z,
|
worldData = (await _brio.GetTransformAsync(player.Address).ConfigureAwait(false));
|
||||||
RotationW = rotQuaternion.W,
|
|
||||||
RotationX = rotQuaternion.X,
|
|
||||||
RotationY = rotQuaternion.Y,
|
|
||||||
RotationZ = rotQuaternion.Z,
|
|
||||||
ScaleX = 1,
|
|
||||||
ScaleY = 1,
|
|
||||||
ScaleZ = 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var loc = await _dalamudUtil.GetMapDataAsync().ConfigureAwait(false);
|
|
||||||
worldData.LocationInfo = loc;
|
|
||||||
|
|
||||||
if (worldData != _lastWorldData)
|
|
||||||
{
|
|
||||||
await _apiController.GposeLobbyPushWorldData(worldData).ConfigureAwait(false);
|
|
||||||
_lastWorldData = worldData;
|
|
||||||
Logger.LogTrace("WorldData (gpose: {gpose}): {data}", _dalamudUtil.IsInGpose, worldData);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in _usersInLobby)
|
|
||||||
{
|
|
||||||
if (!entry.Value.HasWorldDataUpdate || _dalamudUtil.IsInGpose) continue;
|
|
||||||
|
|
||||||
var entryWorldData = entry.Value.WorldData!.Value;
|
|
||||||
|
|
||||||
if (worldData.LocationInfo.MapId == entryWorldData.LocationInfo.MapId && worldData.LocationInfo.DivisionId == entryWorldData.LocationInfo.DivisionId
|
|
||||||
&& (worldData.LocationInfo.HouseId != entryWorldData.LocationInfo.HouseId
|
|
||||||
|| worldData.LocationInfo.WardId != entryWorldData.LocationInfo.WardId
|
|
||||||
|| entryWorldData.LocationInfo.ServerId != worldData.LocationInfo.ServerId))
|
|
||||||
{
|
|
||||||
if (entry.Value.SpawnedVfxId == null)
|
|
||||||
{
|
|
||||||
// spawn if it doesn't exist yet
|
|
||||||
entry.Value.LastWorldPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ);
|
|
||||||
entry.Value.SpawnedVfxId = await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.SpawnObject(entry.Value.LastWorldPosition.Value,
|
|
||||||
Quaternion.Identity, Vector3.One, 0.5f, 0.1f, 0.5f, 0.9f)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// move object via lerp if it does exist
|
|
||||||
var newPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ);
|
|
||||||
if (newPosition != entry.Value.LastWorldPosition)
|
|
||||||
{
|
|
||||||
entry.Value.UpdateStart = DateTime.UtcNow;
|
|
||||||
entry.Value.TargetWorldPosition = newPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.DespawnObject(entry.Value.SpawnedVfxId)).ConfigureAwait(false);
|
var rotQuaternion = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), player.Rotation);
|
||||||
entry.Value.SpawnedVfxId = null;
|
worldData = new()
|
||||||
|
{
|
||||||
|
PositionX = player.Position.X,
|
||||||
|
PositionY = player.Position.Y,
|
||||||
|
PositionZ = player.Position.Z,
|
||||||
|
RotationW = rotQuaternion.W,
|
||||||
|
RotationX = rotQuaternion.X,
|
||||||
|
RotationY = rotQuaternion.Y,
|
||||||
|
RotationZ = rotQuaternion.Z,
|
||||||
|
ScaleX = 1,
|
||||||
|
ScaleY = 1,
|
||||||
|
ScaleZ = 1
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var loc = await _dalamudUtil.GetMapDataAsync().ConfigureAwait(false);
|
||||||
|
worldData.LocationInfo = loc;
|
||||||
|
|
||||||
|
if (_forceResendWorldData || worldData != _lastWorldData)
|
||||||
|
{
|
||||||
|
_forceResendWorldData = false;
|
||||||
|
await _apiController.GposeLobbyPushWorldData(worldData).ConfigureAwait(false);
|
||||||
|
_lastWorldData = worldData;
|
||||||
|
Logger.LogTrace("WorldData (gpose: {gpose}): {data}", _dalamudUtil.IsInGpose, worldData);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in _usersInLobby)
|
||||||
|
{
|
||||||
|
if (!entry.Value.HasWorldDataUpdate || _dalamudUtil.IsInGpose || entry.Value.WorldData == null) continue;
|
||||||
|
|
||||||
|
var entryWorldData = entry.Value.WorldData!.Value;
|
||||||
|
|
||||||
|
if (worldData.LocationInfo.MapId == entryWorldData.LocationInfo.MapId && worldData.LocationInfo.DivisionId == entryWorldData.LocationInfo.DivisionId
|
||||||
|
&& (worldData.LocationInfo.HouseId != entryWorldData.LocationInfo.HouseId
|
||||||
|
|| worldData.LocationInfo.WardId != entryWorldData.LocationInfo.WardId
|
||||||
|
|| entryWorldData.LocationInfo.ServerId != worldData.LocationInfo.ServerId))
|
||||||
|
{
|
||||||
|
if (entry.Value.SpawnedVfxId == null)
|
||||||
|
{
|
||||||
|
// spawn if it doesn't exist yet
|
||||||
|
entry.Value.LastWorldPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ);
|
||||||
|
entry.Value.SpawnedVfxId = await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.SpawnObject(entry.Value.LastWorldPosition.Value,
|
||||||
|
Quaternion.Identity, Vector3.One, 0.5f, 0.1f, 0.5f, 0.9f)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// move object via lerp if it does exist
|
||||||
|
var newPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ);
|
||||||
|
if (newPosition != entry.Value.LastWorldPosition)
|
||||||
|
{
|
||||||
|
entry.Value.UpdateStart = DateTime.UtcNow;
|
||||||
|
entry.Value.TargetWorldPosition = newPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.DespawnObject(entry.Value.SpawnedVfxId)).ConfigureAwait(false);
|
||||||
|
entry.Value.SpawnedVfxId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Error during World Data Generation");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,6 +541,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void OnEnterGpose()
|
private void OnEnterGpose()
|
||||||
{
|
{
|
||||||
|
ForceResendOwnData();
|
||||||
ResetOwnData();
|
ResetOwnData();
|
||||||
foreach (var data in _usersInLobby.Values)
|
foreach (var data in _usersInLobby.Values)
|
||||||
{
|
{
|
||||||
@@ -533,6 +552,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void OnExitGpose()
|
private void OnExitGpose()
|
||||||
{
|
{
|
||||||
|
ForceResendOwnData();
|
||||||
ResetOwnData();
|
ResetOwnData();
|
||||||
foreach (var data in _usersInLobby.Values)
|
foreach (var data in _usersInLobby.Values)
|
||||||
{
|
{
|
||||||
@@ -540,10 +560,18 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool _forceResendFullPose = false;
|
||||||
|
private bool _forceResendWorldData = false;
|
||||||
|
|
||||||
|
private void ForceResendOwnData()
|
||||||
|
{
|
||||||
|
_forceResendFullPose = true;
|
||||||
|
_forceResendWorldData = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void ResetOwnData()
|
private void ResetOwnData()
|
||||||
{
|
{
|
||||||
_lastFullPoseData = null;
|
|
||||||
_lastDeltaPoseData = null;
|
|
||||||
_poseGenerationExecutions = 0;
|
_poseGenerationExecutions = 0;
|
||||||
_lastCreatedCharaData = null;
|
_lastCreatedCharaData = null;
|
||||||
}
|
}
|
||||||
@@ -654,8 +682,6 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
|||||||
if (_usersInLobby.ContainsKey(userData.UID))
|
if (_usersInLobby.ContainsKey(userData.UID))
|
||||||
OnUserLeaveLobby(userData);
|
OnUserLeaveLobby(userData);
|
||||||
_usersInLobby[userData.UID] = new(userData);
|
_usersInLobby[userData.UID] = new(userData);
|
||||||
_lastFullPoseData = null;
|
|
||||||
_lastWorldData = null;
|
|
||||||
_ = PushCharacterDownloadDto();
|
_ = PushCharacterDownloadDto();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1013,7 +1013,7 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
|
|||||||
if (string.IsNullOrEmpty(handledActor)) return;
|
if (string.IsNullOrEmpty(handledActor)) return;
|
||||||
UiBlockingComputation = Task.Run(async () =>
|
UiBlockingComputation = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await _characterHandler.RevertHandledChara(handledActor, false).ConfigureAwait(false);
|
await _characterHandler.RevertHandledChara(handledActor).ConfigureAwait(false);
|
||||||
var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(handledActor, true).ConfigureAwait(false);
|
var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(handledActor, true).ConfigureAwait(false);
|
||||||
if (gposeChara != null)
|
if (gposeChara != null)
|
||||||
await _ipcManager.Brio.DespawnActorAsync(gposeChara.Address).ConfigureAwait(false);
|
await _ipcManager.Brio.DespawnActorAsync(gposeChara.Address).ConfigureAwait(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user