From bbeb29786c55c7d8553b67c16af7ce6c5a492661 Mon Sep 17 00:00:00 2001 From: Jay Long Date: Mon, 26 Jan 2026 01:22:11 -0600 Subject: [PATCH] Refactored Code organization, stable connection to VRChat & OLlama cloud, fixed KeyActions, OSCActions. --- Actions/Actions.cs | 479 - LineHandlers/AbstractLineHandler.cs | 56 - PlayerManagement/PlayerManagement.cs | 265 - PlayerManagement/TailgrabPannel.xaml | 208 - PlayerManagement/TailgrabPannel.xaml.cs | 397 - README.md | 34 +- config.json | 94 - docs/amplitude.cache.json | 1163 + libs/JsonSubTypes.dll | Bin 0 -> 20992 bytes libs/Newtonsoft.Json.dll | Bin 0 -> 712464 bytes libs/Otp.NET.dll | Bin 0 -> 17920 bytes libs/Polly.Core.dll | Bin 0 -> 236344 bytes libs/Polly.dll | Bin 0 -> 294712 bytes libs/VRChat.API.deps.json | 110 + libs/VRChat.API.dll | Bin 0 -> 1935872 bytes libs/VRChat.API.pdb | Bin 0 -> 426984 bytes libs/VRChat.API.xml | 47975 ++++++++++++++++ src/Actions/Actions.cs | 331 + src/AvatarManagement/AvatarManagement.cs | 261 + src/Clients/Ollama/Ollama.cs | 293 + src/Clients/VRChat/VRChat.cs | 297 + src/Common/AnsiColor.cs | 82 + src/Common/Checksum.cs | 29 + src/LineHandlers/AbstractLineHandler.cs | 88 + .../LineHandlers}/AvatarChangeHandler.cs | 10 +- .../LineHandlers}/AvatarUnpackHandler.cs | 5 +- src/LineHandlers/EmojiHandler.cs | 38 + .../LineHandlers}/LoggingLineHandler.cs | 6 +- .../LineHandlers}/OnPlayerJoinHandler.cs | 7 +- .../LineHandlers}/OnPlayerNetworkHandler.cs | 4 +- .../LineHandlers}/PenNetworkIdHandler.cs | 12 +- .../LineHandlers}/PrintHandler.cs | 9 +- .../LineHandlers}/QuitHandler.cs | 20 +- .../LineHandlers}/StickerHandler.cs | 9 +- .../LineHandlers}/VTKHandler.cs | 7 +- .../LineHandlers}/WarnKickHandler.cs | 7 +- src/LineHandlers/WorldChangeHandler.cs | 42 + src/Models/AvatarInfo.cs | 33 + src/Models/GroupInfo.cs | 31 + src/Models/ProfileEvaluation.cs | 29 + src/Models/TailgrabDBContext.cs | 76 + src/Models/UserInfo.cs | 31 + NLog.config => src/NLog.config | 0 src/PlayerManagement/PlayerManagement.cs | 508 + src/PlayerManagement/TailgrabPannel.xaml | 479 + src/PlayerManagement/TailgrabPannel.xaml.cs | 832 + Program.cs => src/Program.cs | 218 +- src/Properties/Resources.Designer.cs | 113 + src/Properties/Resources.resx | 136 + {Resources => src/Resources}/tailgrab.ico | Bin {Resources => src/Resources}/tailgrab.png | 0 src/Resources/tailgrab_large.ico | Bin 0 -> 55986 bytes src/ServiceRegistry.cs | 95 + src/config.json | 137 + .../configuration}/ConfigurationManager.cs | 159 +- src/efpt.config.json | 61 + pen-network-id.csv => src/pen-network-id.csv | 0 tailgrab.csproj | 96 +- tailgrab.csproj.user | 9 + 59 files changed, 53717 insertions(+), 1664 deletions(-) delete mode 100644 Actions/Actions.cs delete mode 100644 LineHandlers/AbstractLineHandler.cs delete mode 100644 PlayerManagement/PlayerManagement.cs delete mode 100644 PlayerManagement/TailgrabPannel.xaml delete mode 100644 PlayerManagement/TailgrabPannel.xaml.cs delete mode 100644 config.json create mode 100644 docs/amplitude.cache.json create mode 100644 libs/JsonSubTypes.dll create mode 100644 libs/Newtonsoft.Json.dll create mode 100644 libs/Otp.NET.dll create mode 100644 libs/Polly.Core.dll create mode 100644 libs/Polly.dll create mode 100644 libs/VRChat.API.deps.json create mode 100644 libs/VRChat.API.dll create mode 100644 libs/VRChat.API.pdb create mode 100644 libs/VRChat.API.xml create mode 100644 src/Actions/Actions.cs create mode 100644 src/AvatarManagement/AvatarManagement.cs create mode 100644 src/Clients/Ollama/Ollama.cs create mode 100644 src/Clients/VRChat/VRChat.cs create mode 100644 src/Common/AnsiColor.cs create mode 100644 src/Common/Checksum.cs create mode 100644 src/LineHandlers/AbstractLineHandler.cs rename {LineHandlers => src/LineHandlers}/AvatarChangeHandler.cs (72%) rename {LineHandlers => src/LineHandlers}/AvatarUnpackHandler.cs (85%) create mode 100644 src/LineHandlers/EmojiHandler.cs rename {LineHandlers => src/LineHandlers}/LoggingLineHandler.cs (58%) rename {LineHandlers => src/LineHandlers}/OnPlayerJoinHandler.cs (81%) rename {LineHandlers => src/LineHandlers}/OnPlayerNetworkHandler.cs (85%) rename {LineHandlers => src/LineHandlers}/PenNetworkIdHandler.cs (74%) rename {LineHandlers => src/LineHandlers}/PrintHandler.cs (76%) rename {LineHandlers => src/LineHandlers}/QuitHandler.cs (53%) rename {LineHandlers => src/LineHandlers}/StickerHandler.cs (76%) rename {LineHandlers => src/LineHandlers}/VTKHandler.cs (76%) rename {LineHandlers => src/LineHandlers}/WarnKickHandler.cs (77%) create mode 100644 src/LineHandlers/WorldChangeHandler.cs create mode 100644 src/Models/AvatarInfo.cs create mode 100644 src/Models/GroupInfo.cs create mode 100644 src/Models/ProfileEvaluation.cs create mode 100644 src/Models/TailgrabDBContext.cs create mode 100644 src/Models/UserInfo.cs rename NLog.config => src/NLog.config (100%) create mode 100644 src/PlayerManagement/PlayerManagement.cs create mode 100644 src/PlayerManagement/TailgrabPannel.xaml create mode 100644 src/PlayerManagement/TailgrabPannel.xaml.cs rename Program.cs => src/Program.cs (58%) create mode 100644 src/Properties/Resources.Designer.cs create mode 100644 src/Properties/Resources.resx rename {Resources => src/Resources}/tailgrab.ico (100%) rename {Resources => src/Resources}/tailgrab.png (100%) create mode 100644 src/Resources/tailgrab_large.ico create mode 100644 src/ServiceRegistry.cs create mode 100644 src/config.json rename {configuration => src/configuration}/ConfigurationManager.cs (68%) create mode 100644 src/efpt.config.json rename pen-network-id.csv => src/pen-network-id.csv (100%) create mode 100644 tailgrab.csproj.user diff --git a/Actions/Actions.cs b/Actions/Actions.cs deleted file mode 100644 index 39bd8ca..0000000 --- a/Actions/Actions.cs +++ /dev/null @@ -1,479 +0,0 @@ -using System.Runtime.InteropServices; -using BuildSoft.VRChat.Osc; -using BuildSoft.VRChat.Osc.Avatar; -using NLog; - -namespace Tailgrab.Actions -{ - public interface IAction - { - void PerformAction(); - } - - - public class DelayAction : IAction - { - public int DelayMilliseconds { get; set; } - public Logger logger = LogManager.GetCurrentClassLogger(); - - public DelayAction(int delayMilliseconds) - { - DelayMilliseconds = delayMilliseconds; - logger.Warn($"Added DelayAction: Will delay for : '{DelayMilliseconds}' milliseconds."); - - } - - public void PerformAction() - { - if( DelayMilliseconds <= 0 ) - { - return; - } - - Thread.Sleep(DelayMilliseconds); - } - } - - - public class KeystrokesAction : IAction - { - [DllImport ("User32.dll")] - public static extern IntPtr SetForegroundWindow (IntPtr hWnd); - - [DllImport("user32.dll", SetLastError = true)] - private static extern uint SendInput(uint nInputs, [In] INPUT[] pInputs, int cbSize); - - // Additional native methods for reliably setting foreground focus - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [DllImport("kernel32.dll")] - private static extern uint GetCurrentThreadId(); - - [DllImport("user32.dll", SetLastError = true)] - private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); - - [DllImport("user32.dll")] - private static extern bool BringWindowToTop(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); - - [DllImport("user32.dll")] - private static extern IntPtr SetFocus(IntPtr hWnd); - - private const uint INPUT_KEYBOARD = 1; - private const uint KEYEVENTF_KEYUP = 0x0002; - private const uint KEYEVENTF_UNICODE = 0x0004; - private const int SW_RESTORE = 9; - - public Logger logger = LogManager.GetCurrentClassLogger(); - - public string WindowTitle { get; set; } - public string Keys { get; set; } - - public KeystrokesAction(string windowTitle, string keys) - { - WindowTitle = windowTitle; - Keys = keys; - - logger.Warn($"Added KeystrokesAction: Window Title: '{WindowTitle}' with Keys: {Keys}."); - } - - public void PerformAction() - { - if( WindowTitle == null || Keys == null ) - { - logger.Warn($"KeystrokesAction: Window Title: '{WindowTitle}' or Keys: {Keys} not supplied."); - return; - } - - System.Diagnostics.Process? targetProcess = null; - - try - { - var all = System.Diagnostics.Process.GetProcesses(); - foreach (var p in all) - { - try - { - if (p.MainWindowHandle == System.IntPtr.Zero) continue; - var title = p.MainWindowTitle; - if (string.IsNullOrEmpty(title)) continue; - if (title.IndexOf(WindowTitle, StringComparison.CurrentCultureIgnoreCase) >= 0) - { - targetProcess = p; - break; - } - } - catch - { - // Access denied or process exited -- ignore and continue - } - } - - // Fallback: try by process name if no title match - if (targetProcess == null) - { - var byName = System.Diagnostics.Process.GetProcessesByName(WindowTitle); - if (byName.Length > 0) targetProcess = byName[0]; - } - - if (targetProcess != null) - { - IntPtr handle = targetProcess.MainWindowHandle; - - logger.Debug($"KeystrokesAction: Sending to process '{targetProcess.ProcessName}' (PID {targetProcess.Id}) Title: '{targetProcess.MainWindowTitle}' Keys: {Keys}."); - - // Attempt to reliably bring the target window to the foreground - uint targetThread = GetWindowThreadProcessId(handle, out _); - uint currentThread = GetCurrentThreadId(); - bool attached = false; - - try - { - // Attach input threads so SetForegroundWindow works reliably - attached = AttachThreadInput(currentThread, targetThread, true); - - ShowWindow(handle, SW_RESTORE); - BringWindowToTop(handle); - SetForegroundWindow(handle); - SetFocus(handle); - - // Use SendInput with unicode characters for reliable keystroke delivery - SendUnicodeString(Keys); - } - finally - { - if (attached) - { - AttachThreadInput(currentThread, targetThread, false); - } - } - } - else - { - logger.Warn($"KeystrokesAction: Window with title containing '{WindowTitle}' not found."); - } - } - catch (Exception ex) - { - logger.Error(ex, "KeystrokesAction: Error while attempting to find target process/window"); - } - } - - private void SendUnicodeString(string s) - { - foreach (var ch in s) - { - SendUnicodeChar(ch); - } - } - - // New: parse SendKeys-style notation and send via SendInput. - // Supports modifiers '^' (Ctrl), '%' (Alt), '+' (Shift), grouping with '()' and braced keys like '{ENTER}', '{F1}', etc. - private void SendKeysNotation(string keys) - { - if (string.IsNullOrEmpty(keys)) return; - - for (int i = 0; i < keys.Length; i++) - { - char c = keys[i]; - - // Handle modifiers prefixing a token - if (c == '^' || c == '%' || c == '+') - { - var mods = new List(); - // collect consecutive modifier symbols - while (i < keys.Length && (keys[i] == '^' || keys[i] == '%' || keys[i] == '+')) - { - if (keys[i] == '^') mods.Add(0x11); // VK_CONTROL - if (keys[i] == '%') mods.Add(0x12); // VK_MENU (Alt) - if (keys[i] == '+') mods.Add(0x10); // VK_SHIFT - i++; - } - - if (i >= keys.Length) break; - - // Determine the token: group '(... )', braced '{...}', or single char - if (keys[i] == '(') - { - int start = i + 1; - int end = keys.IndexOf(')', start); - if (end == -1) end = keys.Length - 1; - string group = keys.Substring(start, end - start); - foreach (var ch in group) - { - SendCharWithModifiers(ch, mods); - } - i = end; - } - else if (keys[i] == '{') - { - int start = i + 1; - int end = keys.IndexOf('}', start); - if (end == -1) end = keys.Length - 1; - string token = keys.Substring(start, end - start); - SendTokenWithModifiers(token, mods); - i = end; - } - else - { - SendCharWithModifiers(keys[i], mods); - } - - continue; - } - - // Braced token or single character - if (c == '{') - { - int start = i + 1; - int end = keys.IndexOf('}', start); - if (end == -1) end = keys.Length - 1; - string token = keys.Substring(start, end - start); - SendTokenWithModifiers(token, new List()); - i = end; - } - else - { - // Regular char - SendUnicodeChar(c); - } - } - } - - private void SendCharWithModifiers(char ch, List mods) - { - // press mods - foreach (var m in mods) - { - SendVirtualKeyDown(m); - } - - // send character - SendUnicodeChar(ch); - - // release mods in reverse order - for (int j = mods.Count - 1; j >= 0; j--) - { - SendVirtualKeyUp(mods[j]); - } - } - - private void SendTokenWithModifiers(string token, List mods) - { - // Normalize token - var t = token.ToUpperInvariant(); - - // Mapping of common SendKeys tokens to virtual-key codes - Dictionary map = new Dictionary - { - {"ENTER", 0x0D}, - {"TAB", 0x09}, - {"BACKSPACE", 0x08}, - {"BS", 0x08}, - {"BKSP", 0x08}, - {"ESC", 0x1B}, - {"LEFT", 0x25}, - {"UP", 0x26}, - {"RIGHT", 0x27}, - {"DOWN", 0x28}, - {"HOME", 0x24}, - {"END", 0x23}, - {"PGUP", 0x21}, - {"PRIOR", 0x21}, - {"PGDN", 0x22}, - {"NEXT", 0x22}, - {"INSERT", 0x2D}, - {"DELETE", 0x2E}, - {"DEL", 0x2E}, - {"SPACE", 0x20}, - {"F1", 0x70}, {"F2", 0x71}, {"F3", 0x72}, {"F4", 0x73}, {"F5", 0x74}, - {"F6", 0x75}, {"F7", 0x76}, {"F8", 0x77}, {"F9", 0x78}, {"F10", 0x79}, - {"F11", 0x7A}, {"F12", 0x7B}, - {"LWIN", 0x5B}, {"RWIN", 0x5C}, {"APPS", 0x5D} - }; - - if (map.TryGetValue(t, out ushort vk)) - { - // press mods - foreach (var m in mods) SendVirtualKeyDown(m); - - // send vk - SendVirtualKey(vk); - - // release mods - for (int j = mods.Count - 1; j >= 0; j--) SendVirtualKeyUp(mods[j]); - } - else - { - // If token length == 1, send that character - if (token.Length == 1) - { - SendCharWithModifiers(token[0], mods); - } - else - { - // For unknown tokens, attempt to send the text literally - foreach (var ch in token) - { - SendCharWithModifiers(ch, mods); - } - } - } - } - - private void SendVirtualKeyDown(ushort vk) - { - INPUT[] inputs = new INPUT[1]; - inputs[0].type = INPUT_KEYBOARD; - inputs[0].U.ki.wVk = vk; - inputs[0].U.ki.wScan = 0; - inputs[0].U.ki.dwFlags = 0; - inputs[0].U.ki.time = 0; - inputs[0].U.ki.dwExtraInfo = System.IntPtr.Zero; - SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))); - } - - private void SendVirtualKeyUp(ushort vk) - { - INPUT[] inputs = new INPUT[1]; - inputs[0].type = INPUT_KEYBOARD; - inputs[0].U.ki.wVk = vk; - inputs[0].U.ki.wScan = 0; - inputs[0].U.ki.dwFlags = KEYEVENTF_KEYUP; - inputs[0].U.ki.time = 0; - inputs[0].U.ki.dwExtraInfo = System.IntPtr.Zero; - SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))); - } - - private void SendVirtualKey(ushort vk) - { - SendVirtualKeyDown(vk); - SendVirtualKeyUp(vk); - } - - private void SendUnicodeChar(char ch) - { - INPUT[] inputs = new INPUT[2]; - - inputs[0].type = INPUT_KEYBOARD; - inputs[0].U.ki.wVk = 0; - inputs[0].U.ki.wScan = (ushort)ch; - inputs[0].U.ki.dwFlags = KEYEVENTF_UNICODE; - inputs[0].U.ki.time = 0; - inputs[0].U.ki.dwExtraInfo = System.IntPtr.Zero; - - inputs[1].type = INPUT_KEYBOARD; - inputs[1].U.ki.wVk = 0; - inputs[1].U.ki.wScan = (ushort)ch; - inputs[1].U.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; - inputs[1].U.ki.time = 0; - inputs[1].U.ki.dwExtraInfo = System.IntPtr.Zero; - - var result = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))); - // if result == 0 you can call Marshal.GetLastWin32Error() to inspect failure - } - - [StructLayout(LayoutKind.Sequential)] - private struct INPUT - { - public uint type; - public InputUnion U; - } - - [StructLayout(LayoutKind.Explicit)] - private struct InputUnion - { - [FieldOffset(0)] public MOUSEINPUT mi; - [FieldOffset(0)] public KEYBDINPUT ki; - [FieldOffset(0)] public HARDWAREINPUT hi; - } - - [StructLayout(LayoutKind.Sequential)] - private struct MOUSEINPUT - { - public int dx; - public int dy; - public uint mouseData; - public uint dwFlags; - public uint time; - public System.IntPtr dwExtraInfo; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KEYBDINPUT - { - public ushort wVk; - public ushort wScan; - public uint dwFlags; - public uint time; - public System.IntPtr dwExtraInfo; - } - - [StructLayout(LayoutKind.Sequential)] - private struct HARDWAREINPUT - { - public uint uMsg; - public ushort wParamL; - public ushort wParamH; - } - } - - - public class OSCAction : IAction - { - public Logger logger = LogManager.GetCurrentClassLogger(); - - private OscAvatarConfig? oscAvatarConfig = OscAvatarConfig.CreateAtCurrent(); - - public string ParameterName { get; set; } - - public OscType OscTypeValue { get; set; } - - public string Value { get; set; } - - public OSCAction(string parameterName, OscType type, string value) - { - ParameterName = parameterName; - OscTypeValue = type; - Value = value; - - logger.Warn($"Added OSCAction: Parameter: '{ParameterName}'; Type: {OscTypeValue}; Value: {Value}."); - - } - - public void PerformAction() - { - var parameterName = ParameterName; - var value = Value; - if (string.IsNullOrEmpty(parameterName) || string.IsNullOrEmpty(value)) - { - return; - } - - switch (OscTypeValue) - { - case OscType.Bool: - if (bool.TryParse(value, out bool boolValue)) - { - OscParameter.SendValue(parameterName, boolValue); - } - break; - case OscType.Int: - if (int.TryParse(value, out int intValue)) - { - OscParameter.SendValue(parameterName, intValue); - } - break; - case OscType.Float: - if (float.TryParse(value, out float floatValue)) - { - OscParameter.SendValue(parameterName, floatValue); - } - break; - } - } - } -} diff --git a/LineHandlers/AbstractLineHandler.cs b/LineHandlers/AbstractLineHandler.cs deleted file mode 100644 index c251f30..0000000 --- a/LineHandlers/AbstractLineHandler.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Tailgrab.Actions; -using System.Text.RegularExpressions; -using NLog; - -namespace Tailgrab.LineHandler -{ - public interface ILineHandler - { - void AddAction(IAction action); - - bool HandleLine(string line); - - void LogOutputColor( string color ); - } - - - public abstract class AbstractLineHandler: ILineHandler - { - protected string Pattern { get; } - protected Regex regex; - public List Actions = new List(); - public bool LogOutput { get; set; } = true; - public string LogOutputColor { get; set; } = "37m"; // Default to white - public string COLOR_PREFIX => $"\u001b[{LogOutputColor}"; - public static readonly string COLOR_RESET = "\u001b[0m"; - public static Logger logger = LogManager.GetCurrentClassLogger(); - - - protected AbstractLineHandler(string matchPattern) - { - Pattern = matchPattern; - regex = new Regex(Pattern); - } - - public abstract bool HandleLine(string line); - - public void AddAction(IAction action) - { - Actions.Add(action); - } - - protected void ExecuteActions() - { - foreach (var action in Actions) - { - action.PerformAction(); - } - } - - void ILineHandler.LogOutputColor(string color) - { - LogOutputColor = color; - } - } - -} diff --git a/PlayerManagement/PlayerManagement.cs b/PlayerManagement/PlayerManagement.cs deleted file mode 100644 index 1a593b0..0000000 --- a/PlayerManagement/PlayerManagement.cs +++ /dev/null @@ -1,265 +0,0 @@ -using NLog; -using Tailgrab.LineHandler; - -namespace Tailgrab.PlayerManagement -{ - - public class PlayerEvent - { - public enum EventType - { - Join, - Leave, - Sticker, - AvatarChange, - Moderation - } - - public DateTime EventTime { get; set; } = DateTime.Now; - public EventType Type { get; set; } - public string EventDescription { get; set; } - - public PlayerEvent(EventType type, string eventDescription) - { - Type = type; - EventDescription = eventDescription; - } - } - - public class Player - { - public string UserId { get; set; } - public string DisplayName { get; set; } - public string AvatarName { get; set; } - public int NetworkId { get; set; } - public DateTime InstanceStartTime { get; set; } - public DateTime? InstanceEndTime { get; set; } - public List Events { get; set; } = new List(); - - public Player(string userId, string displayName) - { - UserId = userId; - DisplayName = displayName; - AvatarName = ""; - InstanceStartTime = DateTime.Now; - } - - public void AddEvent(PlayerEvent playerEvent) - { - Events.Add(playerEvent); - } - } - - public class PlayerChangedEventArgs : EventArgs - { - public enum ChangeType - { - Added, - Updated, - Removed, - Cleared - } - - public ChangeType Type { get; } - public Player Player { get; } - - public PlayerChangedEventArgs(ChangeType type, Player player) - { - Type = type; - Player = player; - } - } - - public static class PlayerManager - { - private static Dictionary playersByNetworkId = new Dictionary(); - private static Dictionary playersByUserId = new Dictionary(); - private static Dictionary playersByDisplayName = new Dictionary(); - private static Dictionary avatarByDisplayName = new Dictionary(); - public static readonly string COLOR_PREFIX_YELLOW = $"\u001b[33;1m"; - public static readonly string COLOR_PREFIX_GREEN = $"\u001b[32;1m"; - public static readonly string COLOR_RESET = "\u001b[0m"; - public static Logger logger = LogManager.GetCurrentClassLogger(); - - // Event for UI and other listeners - public static event EventHandler? PlayerChanged; - - private static void OnPlayerChanged(PlayerChangedEventArgs.ChangeType changeType, Player player) - { - try - { - PlayerChanged?.Invoke(null, new PlayerChangedEventArgs(changeType, player)); - } - catch (Exception ex) - { - logger.Error(ex, "Error raising PlayerChanged event"); - } - } - - public static void PlayerJoined(string userId, string displayName, AbstractLineHandler handler) - { - if (!playersByUserId.ContainsKey(userId)) - { - Player newPlayer = new Player(userId, displayName); - playersByUserId[userId] = newPlayer; - playersByDisplayName[displayName] = newPlayer; - if( handler.LogOutput ) - { - logger.Info($"{COLOR_PREFIX_GREEN}Player Joined: {displayName} (ID: {userId}){COLOR_RESET}"); - } - - if( avatarByDisplayName.TryGetValue(displayName, out string? avatarName)) - { - SetAvatarForPlayer(displayName, avatarName); - AddPlayerEventByDisplayName(displayName, PlayerEvent.EventType.AvatarChange, $"Joined with Avatar: {avatarName}"); - if( handler.LogOutput ) - { - logger.Info($"{COLOR_PREFIX_GREEN}\tAvatar on Join: {avatarName}{COLOR_RESET}"); - } - } - - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Added, newPlayer); - } - else - { - // If existing, treat as update (display name may have changed etc.) - var existing = playersByUserId[userId]; - if (existing.DisplayName != displayName) - { - // remove old display-name mapping if present - if (!string.IsNullOrEmpty(existing.DisplayName)) - { - playersByDisplayName.Remove(existing.DisplayName); - } - - existing.DisplayName = displayName; - playersByDisplayName[displayName] = existing; - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Updated, existing); - } - } - } - - public static void PlayerLeft(string displayName, AbstractLineHandler handler ) - { - if (playersByDisplayName.TryGetValue(displayName, out Player? player)) - { - player.InstanceEndTime = DateTime.Now; - // Raise event with updated player before removing from internal dictionaries - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Removed, player); - - playersByDisplayName.Remove(displayName); - playersByNetworkId.Remove(player.NetworkId); - playersByUserId.Remove(player.UserId); - if( handler.LogOutput ) - { - PrintPlayerInfo(player); - } - } - } - - public static Player? GetPlayerByNetworkId(int networkId) - { - playersByNetworkId.TryGetValue(networkId, out Player? player); - return player; - } - - public static Player? GetPlayerByUserId(string userId) - { - playersByUserId.TryGetValue(userId, out Player? player); - return player; - } - - public static Player? GetPlayerByDisplayName(string displayName) - { - playersByDisplayName.TryGetValue(displayName, out Player? player); - return player; - } - - public static Player? AssignPlayerNetworkId(string displayName, int networkId) - { - if( playersByDisplayName.TryGetValue(displayName, out Player? player)) - { - player.NetworkId = networkId; - playersByNetworkId[networkId] = player; - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Updated, player); - } - - return player; - } - - public static IEnumerable GetAllPlayers() - { - return playersByUserId.Values; - } - - public static void ClearAllPlayers(AbstractLineHandler handler) - { - - foreach( var player in playersByUserId.Values ) - { - player.InstanceEndTime = DateTime.Now; - if( handler.LogOutput ) - { - PrintPlayerInfo(player); - } - // Notify removed for each player - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Removed, player); - } - - playersByNetworkId.Clear(); - playersByUserId.Clear(); - playersByDisplayName.Clear(); - - // Also a global cleared notification (consumers may want to reset) - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Cleared, new Player("","") { InstanceStartTime = DateTime.MinValue }); - } - - public static int GetPlayerCount() - { - return playersByUserId.Count; - } - - public static void LogAllPlayers(AbstractLineHandler handler) - { - if( handler.LogOutput ) - { - foreach( var player in playersByUserId.Values ) - { - PrintPlayerInfo(player); - } - } - } - - public static Player? AddPlayerEventByDisplayName(string displayName, PlayerEvent.EventType eventType, string eventDescription) - { - if( playersByDisplayName.TryGetValue(displayName, out Player? player)) - { - PlayerEvent newEvent = new PlayerEvent(eventType, eventDescription); - player.AddEvent(newEvent); - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Updated, player); - return player; - } - - return null; - } - - public static void SetAvatarForPlayer(string displayName, string avatarId) - { - avatarByDisplayName[displayName] = avatarId; - if (playersByDisplayName.TryGetValue(displayName, out var p)) - { - p.AvatarName = avatarId; - OnPlayerChanged(PlayerChangedEventArgs.ChangeType.Updated, p); - } - } - - private static void PrintPlayerInfo(Player player) - { - logger.Info($"{COLOR_PREFIX_YELLOW}Player Left: {player.DisplayName} (ID: {player.UserId}, NetworkID: {player.NetworkId}){COLOR_RESET}"); - foreach( var ev in player.Events ) - { - logger.Info($"{COLOR_PREFIX_YELLOW}\tEvent: {ev.EventTime} - {ev.Type} - {ev.EventDescription}{COLOR_RESET}"); - } - } - } -} diff --git a/PlayerManagement/TailgrabPannel.xaml b/PlayerManagement/TailgrabPannel.xaml deleted file mode 100644 index be3cff0..0000000 --- a/PlayerManagement/TailgrabPannel.xaml +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - -