potentially fixes an issue with cache creation, do not remove mediator on failure
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 381f9a4808...85bedb49e3
@@ -25,7 +25,6 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
private readonly TransientResourceManager _transientResourceManager;
|
private readonly TransientResourceManager _transientResourceManager;
|
||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
private readonly PerformanceCollector _performanceCollector;
|
private readonly PerformanceCollector _performanceCollector;
|
||||||
private readonly ConcurrentQueue<Task<string>> _processingQueue = new();
|
|
||||||
|
|
||||||
public CharacterDataFactory(ILogger<CharacterDataFactory> logger, DalamudUtil dalamudUtil, IpcManager ipcManager,
|
public CharacterDataFactory(ILogger<CharacterDataFactory> logger, DalamudUtil dalamudUtil, IpcManager ipcManager,
|
||||||
TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory, MareMediator mediator,
|
TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory, MareMediator mediator,
|
||||||
@@ -37,13 +36,6 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
_transientResourceManager = transientResourceManager;
|
_transientResourceManager = transientResourceManager;
|
||||||
_fileCacheManager = fileReplacementFactory;
|
_fileCacheManager = fileReplacementFactory;
|
||||||
_performanceCollector = performanceCollector;
|
_performanceCollector = performanceCollector;
|
||||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) =>
|
|
||||||
{
|
|
||||||
while (_processingQueue.TryDequeue(out var result))
|
|
||||||
{
|
|
||||||
result.RunSynchronously();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool CheckForNullDrawObject(IntPtr playerPointer)
|
private unsafe bool CheckForNullDrawObject(IntPtr playerPointer)
|
||||||
@@ -51,14 +43,14 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CharacterData> BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
public async Task BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!_ipcManager.Initialized)
|
if (!_ipcManager.Initialized)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Penumbra is not connected");
|
throw new InvalidOperationException("Penumbra is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerRelatedObject == null) return previousData;
|
if (playerRelatedObject == null) return;
|
||||||
|
|
||||||
bool pointerIsZero = true;
|
bool pointerIsZero = true;
|
||||||
try
|
try
|
||||||
@@ -84,7 +76,7 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
_logger.LogTrace("Pointer was zero for {objectKind}", playerRelatedObject.ObjectKind);
|
_logger.LogTrace("Pointer was zero for {objectKind}", playerRelatedObject.ObjectKind);
|
||||||
previousData.FileReplacements.Remove(playerRelatedObject.ObjectKind);
|
previousData.FileReplacements.Remove(playerRelatedObject.ObjectKind);
|
||||||
previousData.GlamourerString.Remove(playerRelatedObject.ObjectKind);
|
previousData.GlamourerString.Remove(playerRelatedObject.ObjectKind);
|
||||||
return previousData;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousFileReplacements = previousData.FileReplacements.ToDictionary(d => d.Key, d => d.Value);
|
var previousFileReplacements = previousData.FileReplacements.ToDictionary(d => d.Key, d => d.Value);
|
||||||
@@ -92,15 +84,14 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_processingQueue.Clear();
|
await _performanceCollector.LogPerformance(this, "CreateCharacterData>" + playerRelatedObject.ObjectKind, async () =>
|
||||||
return await _performanceCollector.LogPerformance(this, "CreateCharacterData>" + playerRelatedObject.ObjectKind, async () =>
|
|
||||||
{
|
{
|
||||||
return await CreateCharacterData(previousData, playerRelatedObject, token).ConfigureAwait(false);
|
await CreateCharacterData(previousData, playerRelatedObject, token).ConfigureAwait(false);
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_processingQueue.Clear();
|
|
||||||
_logger.LogDebug("Cancelled creating Character data for {object}", playerRelatedObject);
|
_logger.LogDebug("Cancelled creating Character data for {object}", playerRelatedObject);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -111,7 +102,7 @@ public class CharacterDataFactory : MediatorSubscriberBase
|
|||||||
|
|
||||||
previousData.FileReplacements = previousFileReplacements;
|
previousData.FileReplacements = previousFileReplacements;
|
||||||
previousData.GlamourerString = previousGlamourerData;
|
previousData.GlamourerString = previousGlamourerData;
|
||||||
return previousData;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CharacterData> CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
private async Task<CharacterData> CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MareSynchronos.Factories;
|
using MareSynchronos.Factories;
|
||||||
using MareSynchronos.Mediator;
|
using MareSynchronos.Mediator;
|
||||||
using MareSynchronos.Models;
|
using MareSynchronos.Models;
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Managers;
|
namespace MareSynchronos.Managers;
|
||||||
|
|
||||||
@@ -14,7 +12,7 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
private readonly CharacterDataFactory _characterDataFactory;
|
private readonly CharacterDataFactory _characterDataFactory;
|
||||||
private Task? _cacheCreationTask;
|
private Task? _cacheCreationTask;
|
||||||
private readonly Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = new();
|
private readonly Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = new();
|
||||||
private readonly CharacterData _lastCreatedData = new();
|
private readonly CharacterData _playerData = new();
|
||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly CancellationTokenSource _cts = new();
|
||||||
private readonly List<GameObjectHandler> _playerRelatedObjects = new();
|
private readonly List<GameObjectHandler> _playerRelatedObjects = new();
|
||||||
private CancellationTokenSource _palettePlusCts = new();
|
private CancellationTokenSource _palettePlusCts = new();
|
||||||
@@ -43,9 +41,9 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var actualMsg = (ClearCacheForObjectMessage)msg;
|
var actualMsg = (ClearCacheForObjectMessage)msg;
|
||||||
_lastCreatedData.FileReplacements.Remove(actualMsg.ObjectToCreateFor.ObjectKind);
|
_playerData.FileReplacements.Remove(actualMsg.ObjectToCreateFor.ObjectKind);
|
||||||
_lastCreatedData.GlamourerString.Remove(actualMsg.ObjectToCreateFor.ObjectKind);
|
_playerData.GlamourerString.Remove(actualMsg.ObjectToCreateFor.ObjectKind);
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
||||||
@@ -57,9 +55,9 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
private void PalettePlusChanged(PalettePlusMessage msg)
|
private void PalettePlusChanged(PalettePlusMessage msg)
|
||||||
{
|
{
|
||||||
if (!string.Equals(msg.Data, _lastCreatedData.PalettePlusPalette, StringComparison.Ordinal))
|
if (!string.Equals(msg.Data, _playerData.PalettePlusPalette, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_lastCreatedData.PalettePlusPalette = msg.Data ?? string.Empty;
|
_playerData.PalettePlusPalette = msg.Data ?? string.Empty;
|
||||||
|
|
||||||
_palettePlusCts?.Cancel();
|
_palettePlusCts?.Cancel();
|
||||||
_palettePlusCts?.Dispose();
|
_palettePlusCts?.Dispose();
|
||||||
@@ -69,26 +67,26 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
}, token);
|
}, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HeelsOffsetChanged(HeelsOffsetMessage msg)
|
private void HeelsOffsetChanged(HeelsOffsetMessage msg)
|
||||||
{
|
{
|
||||||
if (msg.Offset != _lastCreatedData.HeelsOffset)
|
if (msg.Offset != _playerData.HeelsOffset)
|
||||||
{
|
{
|
||||||
_lastCreatedData.HeelsOffset = msg.Offset;
|
_playerData.HeelsOffset = msg.Offset;
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
||||||
{
|
{
|
||||||
if (!string.Equals(msg.Data, _lastCreatedData.CustomizePlusScale, StringComparison.Ordinal))
|
if (!string.Equals(msg.Data, _playerData.CustomizePlusScale, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_lastCreatedData.CustomizePlusScale = msg.Data ?? string.Empty;
|
_playerData.CustomizePlusScale = msg.Data ?? string.Empty;
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +102,18 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
foreach (var obj in toCreate)
|
foreach (var obj in toCreate)
|
||||||
{
|
{
|
||||||
var data = await _characterDataFactory.BuildCharacterData(_lastCreatedData, obj.Value, _cts.Token).ConfigureAwait(false);
|
await _characterDataFactory.BuildCharacterData(_playerData, obj.Value, _cts.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
|
||||||
|
int maxWaitingTime = 10000;
|
||||||
|
while (!_playerData.IsReady && maxWaitingTime > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
maxWaitingTime -= 100;
|
||||||
|
_logger.LogTrace("Waiting for Cache to be ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -115,7 +122,6 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Cache Creation complete");
|
_logger.LogDebug("Cache Creation complete");
|
||||||
|
|
||||||
}
|
}
|
||||||
}, _cts.Token);
|
}, _cts.Token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
if (downloadToken.IsCancellationRequested)
|
if (downloadToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Detected cancellation");
|
_logger.LogTrace("Detected cancellation");
|
||||||
|
_apiController.CancelDownload(downloadId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,12 +416,6 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
_logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
_logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
||||||
});
|
});
|
||||||
|
|
||||||
_downloadCancellationTokenSource = null;
|
|
||||||
|
|
||||||
_logger.LogDebug("Download was cancelled");
|
|
||||||
_apiController.CancelDownload(downloadId);
|
|
||||||
|
|
||||||
}, downloadToken);
|
}, downloadToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
|
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
|
||||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
var newData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI();
|
var newData = ((CharacterDataCreatedMessage)msg).CharacterData;
|
||||||
if (_lastSentData == null || _lastSentData != null && !string.Equals(newData.DataHash.Value, _lastSentData.DataHash.Value, StringComparison.Ordinal))
|
if (_lastSentData == null || _lastSentData != null && !string.Equals(newData.DataHash.Value, _lastSentData.DataHash.Value, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Pushing data for visible players");
|
_logger.LogDebug("Pushing data for visible players");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>0.7.39</Version>
|
<Version>0.7.40</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class MareMediator : IDisposable
|
|||||||
private readonly ILogger<MareMediator> _logger;
|
private readonly ILogger<MareMediator> _logger;
|
||||||
private readonly PerformanceCollector _performanceCollector;
|
private readonly PerformanceCollector _performanceCollector;
|
||||||
private readonly object _addRemoveLock = new();
|
private readonly object _addRemoveLock = new();
|
||||||
|
private readonly Dictionary<object, DateTime> _lastErrorTime = new();
|
||||||
|
|
||||||
public MareMediator(ILogger<MareMediator> logger, PerformanceCollector performanceCollector)
|
public MareMediator(ILogger<MareMediator> logger, PerformanceCollector performanceCollector)
|
||||||
{
|
{
|
||||||
@@ -69,11 +70,13 @@ public class MareMediator : IDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
lock (_addRemoveLock)
|
if (_lastErrorTime.TryGetValue(subscriber, out var lastErrorTime))
|
||||||
{
|
{
|
||||||
var removed = _subscriberDict[message.GetType()].RemoveWhere(s => s == subscriber);
|
if (lastErrorTime.Add(TimeSpan.FromSeconds(10)) > DateTime.UtcNow) continue;
|
||||||
_logger.LogCritical(ex, "Error executing {type} for subscriber {subscriber}, removed from Mediator: {removeCount}", message.GetType().Name, subscriber.Subscriber.GetType().Name, removed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogCritical(ex, "Error executing {type} for subscriber {subscriber}", message.GetType().Name, subscriber.Subscriber.GetType().Name);
|
||||||
|
_lastErrorTime[subscriber] = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public record NotificationMessage
|
|||||||
(string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : IMessage;
|
(string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : IMessage;
|
||||||
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
||||||
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
||||||
public record CharacterDataCreatedMessage(CharacterData CharacterData) : IMessage;
|
public record CharacterDataCreatedMessage(API.Data.CharacterData CharacterData) : IMessage;
|
||||||
public record PenumbraStartRedrawMessage(IntPtr Address) : IMessage;
|
public record PenumbraStartRedrawMessage(IntPtr Address) : IMessage;
|
||||||
public record PenumbraEndRedrawMessage(IntPtr Address) : IMessage;
|
public record PenumbraEndRedrawMessage(IntPtr Address) : IMessage;
|
||||||
public record HubReconnectingMessage(Exception? Exception) : IMessage;
|
public record HubReconnectingMessage(Exception? Exception) : IMessage;
|
||||||
|
|||||||
@@ -1,31 +1,22 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
|
|
||||||
namespace MareSynchronos.Models;
|
namespace MareSynchronos.Models;
|
||||||
|
|
||||||
[JsonObject(MemberSerialization.OptIn)]
|
|
||||||
public class CharacterData
|
public class CharacterData
|
||||||
{
|
{
|
||||||
[JsonProperty]
|
|
||||||
public Dictionary<ObjectKind, HashSet<FileReplacement>> FileReplacements { get; set; } = new();
|
public Dictionary<ObjectKind, HashSet<FileReplacement>> FileReplacements { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||||
|
|
||||||
public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed);
|
public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed);
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
public string ManipulationString { get; set; } = string.Empty;
|
public string ManipulationString { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
public float HeelsOffset { get; set; } = 0f;
|
public float HeelsOffset { get; set; } = 0f;
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
public string CustomizePlusScale { get; set; } = string.Empty;
|
public string CustomizePlusScale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
public string PalettePlusPalette { get; set; } = string.Empty;
|
public string PalettePlusPalette { get; set; } = string.Empty;
|
||||||
|
|
||||||
public API.Data.CharacterData ToAPI()
|
public API.Data.CharacterData ToAPI()
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class SettingsUi : WindowMediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||||
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiShared_GposeStart());
|
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiShared_GposeStart());
|
||||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiShared_GposeEnd());
|
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiShared_GposeEnd());
|
||||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) => LastCreatedCharacterData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI());
|
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) => LastCreatedCharacterData = ((CharacterDataCreatedMessage)msg).CharacterData);
|
||||||
|
|
||||||
windowSystem.AddWindow(this);
|
windowSystem.AddWindow(this);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user