1175 lines
41 KiB
C#
1175 lines
41 KiB
C#
using System;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Media.Effects;
|
|
using System.Text.Json;
|
|
using System.Xml.Linq;
|
|
|
|
namespace MultiWoWLauncher
|
|
{
|
|
public partial class MainWindow : Window
|
|
{
|
|
// Config file key for WoW path
|
|
private const string WowPathKey = "WowExecutablePath";
|
|
private const string ClearCacheKey = "ClearCache";
|
|
|
|
// AppData config constants
|
|
private const string AppName = "MultiWoWLauncher";
|
|
private const string ConfigFileName = "config.xml";
|
|
private string ConfigFilePath => Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
AppName,
|
|
ConfigFileName);
|
|
|
|
// Class level variables
|
|
private DispatcherTimer _contentRefreshTimer; // Combined refresh timer
|
|
private HttpClient _httpClient;
|
|
private string _playerCountUrl = "http://127.0.0.1/count.php";
|
|
private bool _isStatusUpdating = false;
|
|
private string _newsUrl = "http://127.0.0.1/news.php"; // Change to your news endpoint
|
|
private DateTime _lastNewsUpdate = DateTime.MinValue;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
// Initialize HTTP client
|
|
_httpClient = new HttpClient();
|
|
_httpClient.Timeout = TimeSpan.FromSeconds(10); // Set a reasonable timeout
|
|
|
|
// Load settings from config
|
|
LoadSettingsFromConfig();
|
|
}
|
|
|
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
// Load the embedded video resource
|
|
LoadEmbeddedVideo();
|
|
|
|
// Initial load of content
|
|
_ = UpdatePlayerCount(); // Initial player count update
|
|
LoadNews(); // Initial news load
|
|
|
|
// Initialize the unified content refresh
|
|
InitializeContentRefresh();
|
|
}
|
|
|
|
private void Window_Closed(object sender, EventArgs e)
|
|
{
|
|
// Clean up resources
|
|
if (_contentRefreshTimer != null)
|
|
{
|
|
_contentRefreshTimer.Stop();
|
|
}
|
|
|
|
if (_httpClient != null)
|
|
{
|
|
_httpClient.Dispose();
|
|
}
|
|
}
|
|
|
|
// Load settings from config file
|
|
private void LoadSettingsFromConfig()
|
|
{
|
|
try
|
|
{
|
|
// Load Clear Cache setting
|
|
string clearCacheSetting = GetSettingFromConfig(ClearCacheKey, "False");
|
|
ClearCacheCheckBox.IsChecked = Convert.ToBoolean(clearCacheSetting);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error loading settings: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Save setting to config file
|
|
private void SaveSettingToConfig(string key, string value)
|
|
{
|
|
try
|
|
{
|
|
// Create the config directory if it doesn't exist
|
|
string configDir = Path.GetDirectoryName(ConfigFilePath);
|
|
if (!Directory.Exists(configDir))
|
|
{
|
|
Directory.CreateDirectory(configDir);
|
|
}
|
|
|
|
// Load or create the XML configuration file
|
|
XDocument doc;
|
|
if (File.Exists(ConfigFilePath))
|
|
{
|
|
// Load existing config
|
|
doc = XDocument.Load(ConfigFilePath);
|
|
}
|
|
else
|
|
{
|
|
// Create new config file
|
|
doc = new XDocument(
|
|
new XDeclaration("1.0", "utf-8", "yes"),
|
|
new XElement("configuration",
|
|
new XElement("appSettings")
|
|
)
|
|
);
|
|
}
|
|
|
|
// Get or create the appSettings element
|
|
XElement appSettings = doc.Root.Element("appSettings");
|
|
if (appSettings == null)
|
|
{
|
|
appSettings = new XElement("appSettings");
|
|
doc.Root.Add(appSettings);
|
|
}
|
|
|
|
// Find existing setting
|
|
XElement setting = appSettings.Elements("add")
|
|
.FirstOrDefault(e => (string)e.Attribute("key") == key);
|
|
|
|
if (setting != null)
|
|
{
|
|
// Update existing setting
|
|
setting.Attribute("value").Value = value;
|
|
}
|
|
else
|
|
{
|
|
// Add new setting
|
|
appSettings.Add(new XElement("add",
|
|
new XAttribute("key", key),
|
|
new XAttribute("value", value)
|
|
));
|
|
}
|
|
|
|
// Save the configuration
|
|
doc.Save(ConfigFilePath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Error saving setting to configuration: {ex.Message}",
|
|
"Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
// Get setting from config file
|
|
private string GetSettingFromConfig(string key, string defaultValue = "")
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(ConfigFilePath))
|
|
{
|
|
XDocument doc = XDocument.Load(ConfigFilePath);
|
|
XElement appSettings = doc.Root.Element("appSettings");
|
|
|
|
if (appSettings != null)
|
|
{
|
|
XElement setting = appSettings.Elements("add")
|
|
.FirstOrDefault(e => (string)e.Attribute("key") == key);
|
|
|
|
if (setting != null)
|
|
{
|
|
return (string)setting.Attribute("value");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error loading setting from configuration: {ex.Message}");
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
// Initialize the unified content refresh
|
|
private void InitializeContentRefresh()
|
|
{
|
|
// Create and configure the timer for all content
|
|
_contentRefreshTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromSeconds(10) // Refresh every 10 seconds
|
|
};
|
|
|
|
// Set up the timer event
|
|
_contentRefreshTimer.Tick += async (sender, e) =>
|
|
{
|
|
await RefreshAllContent();
|
|
};
|
|
|
|
// Start the timer
|
|
_contentRefreshTimer.Start();
|
|
|
|
// Update the last updated text immediately
|
|
UpdateLastUpdatedText();
|
|
}
|
|
|
|
// Method to refresh all content
|
|
private async Task RefreshAllContent()
|
|
{
|
|
try
|
|
{
|
|
// Start both refresh tasks concurrently for efficiency
|
|
Task<string> newsTask = FetchNewsContent();
|
|
Task playerCountTask = UpdatePlayerCount();
|
|
|
|
// Wait for news content to be retrieved
|
|
string newsContent = await newsTask;
|
|
|
|
// Update the news panel
|
|
UpdateNewsPanel(newsContent);
|
|
|
|
// Update last refresh time for news
|
|
_lastNewsUpdate = DateTime.Now;
|
|
|
|
// Update the last updated text
|
|
UpdateLastUpdatedText();
|
|
|
|
// Add a subtle visual indicator of refresh
|
|
FlashNewsRefreshIndicator();
|
|
FlashPlayerCountIndicator();
|
|
|
|
// Wait for player count task to complete (should already be done)
|
|
await playerCountTask;
|
|
|
|
Console.WriteLine("All content refreshed at: " + DateTime.Now.ToString("HH:mm:ss"));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error in content refresh: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Update the last updated text
|
|
private void UpdateLastUpdatedText()
|
|
{
|
|
try
|
|
{
|
|
// Get the LastUpdatedText element
|
|
TextBlock lastUpdatedText = FindName("LastUpdatedText") as TextBlock;
|
|
|
|
if (lastUpdatedText != null)
|
|
{
|
|
// Update it with the current time
|
|
lastUpdatedText.Text = $"Updated: {DateTime.Now.ToString("HH:mm:ss")}";
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("LastUpdatedText not found in XAML");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error updating last updated text: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task UpdatePlayerCount()
|
|
{
|
|
// Prevent multiple simultaneous updates
|
|
if (_isStatusUpdating)
|
|
return;
|
|
|
|
_isStatusUpdating = true;
|
|
|
|
try
|
|
{
|
|
// Fetch player count from your local endpoint
|
|
string response = await _httpClient.GetStringAsync(_playerCountUrl);
|
|
|
|
// Try to parse the response as an integer
|
|
if (int.TryParse(response.Trim(), out int playerCount))
|
|
{
|
|
// Update the UI with the player count
|
|
OnlinePlayersText.Text = $"{playerCount:N0} Players Online";
|
|
|
|
// Update the status indicator color based on player count
|
|
UpdateStatusIndicator(playerCount);
|
|
}
|
|
else
|
|
{
|
|
// If we couldn't parse the response as a number
|
|
OnlinePlayersText.Text = "Server Status Unknown";
|
|
SetStatusIndicatorColor(Colors.Gray);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Handle any errors in fetching or parsing the data
|
|
Console.WriteLine($"Error updating player count: {ex.Message}");
|
|
OnlinePlayersText.Text = "Server Offline";
|
|
SetStatusIndicatorColor(Colors.Red);
|
|
}
|
|
finally
|
|
{
|
|
_isStatusUpdating = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateStatusIndicator(int playerCount)
|
|
{
|
|
// Change color based on population
|
|
if (playerCount > 500)
|
|
{
|
|
// High population - green
|
|
SetStatusIndicatorColor(Colors.LimeGreen);
|
|
}
|
|
else if (playerCount > 100)
|
|
{
|
|
// Medium population - yellow
|
|
SetStatusIndicatorColor(Colors.Yellow);
|
|
}
|
|
else if (playerCount > 0)
|
|
{
|
|
// Low population - orange
|
|
SetStatusIndicatorColor(Colors.Orange);
|
|
}
|
|
else
|
|
{
|
|
// No players - red
|
|
SetStatusIndicatorColor(Colors.Red);
|
|
}
|
|
}
|
|
|
|
private void SetStatusIndicatorColor(Color color)
|
|
{
|
|
// Update the status indicator color
|
|
StatusIndicator.Fill = new SolidColorBrush(color);
|
|
|
|
// Update the glow effect
|
|
if (StatusIndicator.Effect is DropShadowEffect effect)
|
|
{
|
|
effect.Color = color;
|
|
}
|
|
}
|
|
|
|
private void FlashPlayerCountIndicator()
|
|
{
|
|
// Find the player count border
|
|
var playerCountBorder = GetPlayerCountBorder();
|
|
if (playerCountBorder == null) return;
|
|
|
|
// Store the original background
|
|
Brush originalBackground = playerCountBorder.Background;
|
|
|
|
// Create a subtle animation
|
|
DispatcherTimer flashTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
|
int flashCount = 0;
|
|
|
|
flashTimer.Tick += (s, e) =>
|
|
{
|
|
flashCount++;
|
|
|
|
if (flashCount == 1)
|
|
{
|
|
// Flash to a slightly lighter color
|
|
playerCountBorder.Background = new SolidColorBrush(Color.FromArgb(80, 0, 0, 0));
|
|
}
|
|
else
|
|
{
|
|
// Restore original background
|
|
playerCountBorder.Background = originalBackground;
|
|
flashTimer.Stop();
|
|
}
|
|
};
|
|
|
|
flashTimer.Start();
|
|
}
|
|
|
|
private Border GetPlayerCountBorder()
|
|
{
|
|
// Find the player count text block
|
|
var playerCountText = FindName("OnlinePlayersText") as TextBlock;
|
|
if (playerCountText == null) return null;
|
|
|
|
// Walk up the visual tree to find the parent Border
|
|
DependencyObject current = playerCountText;
|
|
while (current != null)
|
|
{
|
|
if (current is Border border)
|
|
{
|
|
return border;
|
|
}
|
|
current = VisualTreeHelper.GetParent(current);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void FlashNewsRefreshIndicator()
|
|
{
|
|
// Find the news header border
|
|
var newsHeader = FindNewsHeaderBorder();
|
|
if (newsHeader == null) return;
|
|
|
|
// Store the original background
|
|
Brush originalBackground = newsHeader.Background;
|
|
|
|
// Create a subtle animation
|
|
DispatcherTimer flashTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
|
int flashCount = 0;
|
|
|
|
flashTimer.Tick += (s, e) =>
|
|
{
|
|
flashCount++;
|
|
|
|
if (flashCount == 1)
|
|
{
|
|
// Flash to a slightly lighter color
|
|
newsHeader.Background = new SolidColorBrush(Color.FromRgb(50, 50, 50));
|
|
}
|
|
else
|
|
{
|
|
// Restore original background
|
|
newsHeader.Background = originalBackground;
|
|
flashTimer.Stop();
|
|
}
|
|
};
|
|
|
|
flashTimer.Start();
|
|
}
|
|
|
|
private Border FindNewsHeaderBorder()
|
|
{
|
|
// Find the news panel first
|
|
var newsPanel = FindName("NewsPanel") as StackPanel;
|
|
if (newsPanel == null) return null;
|
|
|
|
// Walk up the visual tree to find the parent Border in Row 0
|
|
DependencyObject current = newsPanel;
|
|
|
|
// Look for parent elements until we find what might be the header border
|
|
while (current != null)
|
|
{
|
|
// Check if this is a Grid
|
|
if (current is Grid grid)
|
|
{
|
|
// Check for a Border in Row 0
|
|
foreach (UIElement child in grid.Children)
|
|
{
|
|
if (child is Border border && Grid.GetRow(child) == 0)
|
|
{
|
|
return border;
|
|
}
|
|
}
|
|
}
|
|
|
|
current = VisualTreeHelper.GetParent(current);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Method to load news
|
|
private async void LoadNews()
|
|
{
|
|
try
|
|
{
|
|
// Only update news if it hasn't been updated in the last hour
|
|
if (DateTime.Now - _lastNewsUpdate > TimeSpan.FromHours(1))
|
|
{
|
|
string newsContent = await FetchNewsContent();
|
|
UpdateNewsPanel(newsContent);
|
|
_lastNewsUpdate = DateTime.Now;
|
|
|
|
// Update the last updated text after initial load
|
|
UpdateLastUpdatedText();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error loading news: {ex.Message}");
|
|
|
|
// Show error in news panel
|
|
var newsPanel = FindName("NewsPanel") as StackPanel;
|
|
if (newsPanel != null)
|
|
{
|
|
newsPanel.Children.Clear();
|
|
newsPanel.Children.Add(CreateNewsErrorItem("Unable to load news updates. Please check your connection."));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method to fetch news content
|
|
private async Task<string> FetchNewsContent()
|
|
{
|
|
try
|
|
{
|
|
// Use the existing HttpClient instead of creating a new one
|
|
string content = await _httpClient.GetStringAsync(_newsUrl);
|
|
return content;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error fetching news: {ex.Message}");
|
|
// Fallback to default news if server is unavailable
|
|
return GetDefaultNews();
|
|
}
|
|
}
|
|
|
|
// Method to parse and update the news panel
|
|
private void UpdateNewsPanel(string newsContent)
|
|
{
|
|
var newsPanel = FindName("NewsPanel") as StackPanel;
|
|
if (newsPanel == null) return;
|
|
|
|
// Clear existing items
|
|
newsPanel.Children.Clear();
|
|
|
|
try
|
|
{
|
|
// Parse the JSON content as an array
|
|
JsonDocument doc = JsonDocument.Parse(newsContent);
|
|
|
|
// The root element should be an array
|
|
if (doc.RootElement.ValueKind == JsonValueKind.Array)
|
|
{
|
|
bool hasNews = false;
|
|
|
|
// Process each news item in the array
|
|
foreach (JsonElement element in doc.RootElement.EnumerateArray())
|
|
{
|
|
// Create a NewsItem object from the JSON element
|
|
NewsItem item = new NewsItem
|
|
{
|
|
Title = GetJsonPropertyString(element, "title", "Untitled"),
|
|
Content = GetJsonPropertyString(element, "content", "No content"),
|
|
Date = GetJsonPropertyString(element, "date", DateTime.Now.ToString("yyyy-MM-dd")),
|
|
Type = GetJsonPropertyString(element, "type", "update")
|
|
};
|
|
|
|
// Add the news item to the panel
|
|
newsPanel.Children.Add(CreateNewsItem(item));
|
|
hasNews = true;
|
|
}
|
|
|
|
// If no news items were found
|
|
if (!hasNews)
|
|
{
|
|
newsPanel.Children.Add(CreateDefaultNewsItem("No news updates available."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the root element is not an array
|
|
newsPanel.Children.Add(CreateNewsErrorItem("Invalid news format: Expected an array of news items."));
|
|
}
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
Console.WriteLine($"Error parsing news JSON: {ex.Message}");
|
|
newsPanel.Children.Add(CreateNewsErrorItem($"Error parsing news data: {ex.Message}"));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error processing news: {ex.Message}");
|
|
newsPanel.Children.Add(CreateNewsErrorItem($"Error loading news content: {ex.Message}"));
|
|
}
|
|
}
|
|
|
|
// Helper method to safely get string property from JSON
|
|
private string GetJsonPropertyString(JsonElement element, string propertyName, string defaultValue)
|
|
{
|
|
if (element.TryGetProperty(propertyName, out JsonElement property) &&
|
|
property.ValueKind == JsonValueKind.String)
|
|
{
|
|
return property.GetString() ?? defaultValue;
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
// Class to represent a news item
|
|
private class NewsItem
|
|
{
|
|
public string Title { get; set; }
|
|
public string Content { get; set; }
|
|
public string Date { get; set; }
|
|
public string Type { get; set; } // e.g., "update", "maintenance", "event"
|
|
}
|
|
|
|
// Create a UI element for a news item
|
|
private UIElement CreateNewsItem(NewsItem item)
|
|
{
|
|
// Container for the entire news item
|
|
Border container = new Border
|
|
{
|
|
Margin = new Thickness(0, 0, 0, 15),
|
|
BorderThickness = new Thickness(0, 0, 0, 1),
|
|
BorderBrush = new SolidColorBrush(Color.FromArgb(50, 255, 255, 255)),
|
|
Padding = new Thickness(0, 0, 0, 10)
|
|
};
|
|
|
|
// Content inside the news item
|
|
StackPanel content = new StackPanel();
|
|
|
|
// News title
|
|
TextBlock titleBlock = new TextBlock
|
|
{
|
|
Text = item.Title,
|
|
FontWeight = FontWeights.Bold,
|
|
FontSize = 14,
|
|
Foreground = new SolidColorBrush(Colors.White),
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Margin = new Thickness(0, 0, 0, 5)
|
|
};
|
|
|
|
// News content
|
|
TextBlock contentBlock = new TextBlock
|
|
{
|
|
Text = item.Content,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(204, 204, 204)),
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Margin = new Thickness(0, 0, 0, 5)
|
|
};
|
|
|
|
// News date
|
|
TextBlock dateBlock = new TextBlock
|
|
{
|
|
Text = item.Date,
|
|
FontStyle = FontStyles.Italic,
|
|
FontSize = 11,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(153, 153, 153)),
|
|
HorizontalAlignment = HorizontalAlignment.Right
|
|
};
|
|
|
|
// Add color indicator based on news type
|
|
Border typeIndicator = new Border
|
|
{
|
|
Width = 3,
|
|
HorizontalAlignment = HorizontalAlignment.Left,
|
|
Margin = new Thickness(-10, 0, 0, 0)
|
|
};
|
|
|
|
// Set color based on news type
|
|
if (item.Type?.ToLower() == "maintenance")
|
|
{
|
|
typeIndicator.Background = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // Orange
|
|
}
|
|
else if (item.Type?.ToLower() == "update")
|
|
{
|
|
typeIndicator.Background = new SolidColorBrush(Color.FromRgb(0, 174, 255)); // Blue
|
|
}
|
|
else if (item.Type?.ToLower() == "event")
|
|
{
|
|
typeIndicator.Background = new SolidColorBrush(Color.FromRgb(124, 252, 0)); // Green
|
|
}
|
|
else
|
|
{
|
|
typeIndicator.Background = new SolidColorBrush(Color.FromRgb(200, 200, 200)); // Grey
|
|
}
|
|
|
|
// Assemble the news item
|
|
content.Children.Add(titleBlock);
|
|
content.Children.Add(contentBlock);
|
|
content.Children.Add(dateBlock);
|
|
|
|
Grid newsGrid = new Grid();
|
|
newsGrid.Children.Add(typeIndicator);
|
|
newsGrid.Children.Add(content);
|
|
|
|
container.Child = newsGrid;
|
|
|
|
return container;
|
|
}
|
|
|
|
// Create a simple news item from plain text
|
|
private UIElement CreateSimpleNewsItem(string content)
|
|
{
|
|
TextBlock textBlock = new TextBlock
|
|
{
|
|
Text = content,
|
|
Foreground = new SolidColorBrush(Colors.White),
|
|
TextWrapping = TextWrapping.Wrap
|
|
};
|
|
|
|
return textBlock;
|
|
}
|
|
|
|
// Create a default news item
|
|
private UIElement CreateDefaultNewsItem(string message)
|
|
{
|
|
TextBlock textBlock = new TextBlock
|
|
{
|
|
Text = message,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(180, 180, 180)),
|
|
FontStyle = FontStyles.Italic,
|
|
TextWrapping = TextWrapping.Wrap
|
|
};
|
|
|
|
return textBlock;
|
|
}
|
|
|
|
// Create an error news item
|
|
private UIElement CreateNewsErrorItem(string errorMessage)
|
|
{
|
|
TextBlock textBlock = new TextBlock
|
|
{
|
|
Text = errorMessage,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(255, 100, 100)),
|
|
FontStyle = FontStyles.Italic,
|
|
TextWrapping = TextWrapping.Wrap
|
|
};
|
|
|
|
return textBlock;
|
|
}
|
|
|
|
// Get default news content if server is unavailable
|
|
private string GetDefaultNews()
|
|
{
|
|
// Create a sample JSON news array
|
|
var defaultNews = new List<NewsItem>
|
|
{
|
|
new NewsItem
|
|
{
|
|
Title = "Welcome to the Multi WoW Launcher!",
|
|
Content = "This launcher is currently in alpha testing. Please report any bugs or issues you encounter.",
|
|
Date = DateTime.Now.ToString("yyyy-MM-dd"),
|
|
Type = "update"
|
|
},
|
|
new NewsItem
|
|
{
|
|
Title = "Upcoming Maintenance",
|
|
Content = "Server maintenance is scheduled for next Tuesday from 2 AM to 6 AM server time.",
|
|
Date = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd"),
|
|
Type = "maintenance"
|
|
},
|
|
new NewsItem
|
|
{
|
|
Title = "Weekend Event: Double XP",
|
|
Content = "Enjoy double XP gains this weekend! Perfect time to level up your alts.",
|
|
Date = DateTime.Now.AddDays(-3).ToString("yyyy-MM-dd"),
|
|
Type = "event"
|
|
}
|
|
};
|
|
|
|
return System.Text.Json.JsonSerializer.Serialize(defaultNews);
|
|
}
|
|
|
|
private void LoadEmbeddedVideo()
|
|
{
|
|
try
|
|
{
|
|
// Get the assembly that contains the embedded resource
|
|
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
|
|
|
// Build the resource name - it includes the namespace
|
|
// Format: YourNamespace.FolderName.FileName.Extension
|
|
string resourceName = "MultiWoWLauncher.Assets.bg.mp4";
|
|
|
|
// Get a stream of the embedded resource
|
|
using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
|
|
{
|
|
if (resourceStream != null)
|
|
{
|
|
// Create a temporary file to hold the video
|
|
string tempFile = Path.Combine(Path.GetTempPath(), $"wow_bg_{Guid.NewGuid()}.mp4");
|
|
|
|
// Copy the embedded resource to the temporary file
|
|
using (FileStream fileStream = new FileStream(tempFile, FileMode.Create))
|
|
{
|
|
resourceStream.CopyTo(fileStream);
|
|
}
|
|
|
|
// Set the MediaElement source to the temporary file
|
|
BackgroundVideo.Source = new Uri(tempFile);
|
|
|
|
// Register for application exit to clean up the temp file
|
|
Application.Current.Exit += (s, e) =>
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(tempFile))
|
|
{
|
|
File.Delete(tempFile);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors on cleanup
|
|
}
|
|
};
|
|
|
|
// Start playing the video
|
|
BackgroundVideo.Play();
|
|
}
|
|
else
|
|
{
|
|
// Resource not found, show fallback background
|
|
ShowFallbackBackground();
|
|
Console.WriteLine("Video resource not found: " + resourceName);
|
|
|
|
// To debug, list all available resources:
|
|
string[] resources = assembly.GetManifestResourceNames();
|
|
Console.WriteLine("Available resources:");
|
|
foreach (string res in resources)
|
|
{
|
|
Console.WriteLine(" " + res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ShowFallbackBackground();
|
|
Console.WriteLine($"Error loading embedded video: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ShowFallbackBackground()
|
|
{
|
|
// Hide video element and show fallback
|
|
BackgroundVideo.Visibility = Visibility.Collapsed;
|
|
|
|
// If you have a fallback background element:
|
|
if (FallbackBackground != null)
|
|
{
|
|
FallbackBackground.Visibility = Visibility.Visible;
|
|
}
|
|
// Otherwise, set a fallback on the parent Grid:
|
|
else
|
|
{
|
|
var parent = BackgroundVideo.Parent as Panel;
|
|
if (parent != null)
|
|
{
|
|
parent.Background = new LinearGradientBrush
|
|
{
|
|
StartPoint = new Point(0, 0),
|
|
EndPoint = new Point(1, 1),
|
|
GradientStops = new GradientStopCollection
|
|
{
|
|
new GradientStop(Color.FromRgb(10, 16, 33), 0),
|
|
new GradientStop(Color.FromRgb(21, 40, 66), 1)
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LoadEmbeddedVideoInMemory()
|
|
{
|
|
try
|
|
{
|
|
// Get the assembly that contains the embedded resource
|
|
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
|
|
|
// Build the resource name with your namespace
|
|
string resourceName = "MultiWoWLauncher.Assets.bg.mp4";
|
|
|
|
// Get a stream of the embedded resource
|
|
using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
|
|
{
|
|
if (resourceStream != null)
|
|
{
|
|
// Create a MemoryStream to hold the video data
|
|
MemoryStream memoryStream = new MemoryStream();
|
|
|
|
// Copy the resource data to the memory stream
|
|
resourceStream.CopyTo(memoryStream);
|
|
memoryStream.Position = 0;
|
|
|
|
// Create a BitmapSource from the memory stream
|
|
var uri = new Uri("pack://application:,,,/" + resourceName);
|
|
var streamResourceInfo = Application.GetResourceStream(uri);
|
|
|
|
if (streamResourceInfo != null)
|
|
{
|
|
BackgroundVideo.Source = uri;
|
|
BackgroundVideo.Play();
|
|
}
|
|
else
|
|
{
|
|
// If direct URI approach fails, try MediaStreamSource (more complex)
|
|
ShowFallbackBackground();
|
|
Console.WriteLine("Could not get resource stream for: " + resourceName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ShowFallbackBackground();
|
|
Console.WriteLine("Video resource not found: " + resourceName);
|
|
|
|
// Debug info - list all resources
|
|
string[] resources = assembly.GetManifestResourceNames();
|
|
Console.WriteLine("Available resources:");
|
|
foreach (string res in resources)
|
|
{
|
|
Console.WriteLine(" " + res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ShowFallbackBackground();
|
|
Console.WriteLine($"Error loading embedded video: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void BackgroundVideo_MediaOpened(object sender, RoutedEventArgs e)
|
|
{
|
|
// When media is successfully opened, play it
|
|
BackgroundVideo.Play();
|
|
}
|
|
|
|
private void BackgroundVideo_MediaEnded(object sender, RoutedEventArgs e)
|
|
{
|
|
// Loop the video
|
|
BackgroundVideo.Position = TimeSpan.Zero;
|
|
BackgroundVideo.Play();
|
|
}
|
|
|
|
private void BackgroundVideo_MediaFailed(object sender, ExceptionRoutedEventArgs e)
|
|
{
|
|
// If video fails to load, show the fallback background
|
|
ShowFallbackBackground();
|
|
Console.WriteLine($"Media failed to load: {e.ErrorException.Message}");
|
|
}
|
|
|
|
private void LaunchWoW_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Try to get WoW path from config
|
|
string wowPath = GetSettingFromConfig(WowPathKey);
|
|
|
|
// Check if path is defined and valid
|
|
if (string.IsNullOrEmpty(wowPath) || !File.Exists(wowPath))
|
|
{
|
|
// Prompt user to select WoW directory
|
|
if (!PromptForWowPath())
|
|
{
|
|
// User cancelled the selection
|
|
return;
|
|
}
|
|
|
|
// Get the path that was just saved
|
|
wowPath = GetSettingFromConfig(WowPathKey);
|
|
}
|
|
|
|
// Check if user wants to clear cache
|
|
bool clearCache = ClearCacheCheckBox.IsChecked ?? false;
|
|
if (clearCache)
|
|
{
|
|
ClearWowCache(wowPath);
|
|
}
|
|
|
|
// Launch WoW and store the process
|
|
Process wowProcess = Process.Start(wowPath);
|
|
|
|
if (wowProcess != null)
|
|
{
|
|
// Give WoW a moment to start
|
|
System.Threading.Thread.Sleep(1500);
|
|
|
|
// Close the launcher
|
|
this.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Error launching World of Warcraft: {ex.Message}",
|
|
"Launch Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
private bool PromptForWowPath()
|
|
{
|
|
MessageBox.Show("World of Warcraft executable path is not set. Please select your WoW.exe file.",
|
|
"Setup Required", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
|
|
var dialog = new Microsoft.Win32.OpenFileDialog
|
|
{
|
|
DefaultExt = ".exe",
|
|
Filter = "World of Warcraft|Wow.exe|All Executable Files (*.exe)|*.exe",
|
|
Title = "Select World of Warcraft Executable",
|
|
CheckFileExists = true
|
|
};
|
|
|
|
// Try to set initial directory to common WoW installation paths
|
|
string[] commonPaths = new[]
|
|
{
|
|
@"C:\Program Files (x86)\World of Warcraft\_retail_",
|
|
@"C:\Program Files\World of Warcraft\_retail_",
|
|
@"D:\World of Warcraft\_retail_",
|
|
@"C:\Games\World of Warcraft\_retail_"
|
|
};
|
|
|
|
foreach (string path in commonPaths)
|
|
{
|
|
if (Directory.Exists(path))
|
|
{
|
|
dialog.InitialDirectory = path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dialog.ShowDialog() == true)
|
|
{
|
|
// User selected a file, save to config
|
|
SaveWowPathToConfig(dialog.FileName);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void ClearCacheCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
|
|
{
|
|
// Save the current state of the checkbox to config
|
|
try
|
|
{
|
|
bool isChecked = ClearCacheCheckBox.IsChecked ?? false;
|
|
SaveSettingToConfig(ClearCacheKey, isChecked.ToString());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Error saving Clear Cache setting: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ClearWowCache(string wowPath)
|
|
{
|
|
try
|
|
{
|
|
// Get the WoW directory from the executable path
|
|
string wowDirectory = Path.GetDirectoryName(wowPath);
|
|
|
|
// Navigate up one level from _retail_ folder if needed
|
|
if (Path.GetFileName(wowDirectory).Equals("_retail_", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
wowDirectory = Directory.GetParent(wowDirectory).FullName;
|
|
}
|
|
|
|
// Common cache folder paths for WoW
|
|
string[] cachePaths = new string[]
|
|
{
|
|
Path.Combine(wowDirectory, "_retail_", "Cache"),
|
|
Path.Combine(wowDirectory, "Cache"),
|
|
Path.Combine(wowDirectory, "_retail_", "WTF", "Cache")
|
|
};
|
|
|
|
foreach (string cachePath in cachePaths)
|
|
{
|
|
if (Directory.Exists(cachePath))
|
|
{
|
|
try
|
|
{
|
|
// Delete all files in the cache directory
|
|
foreach (string file in Directory.GetFiles(cachePath, "*", SearchOption.AllDirectories))
|
|
{
|
|
File.Delete(file);
|
|
}
|
|
|
|
// Delete all subdirectories
|
|
foreach (string dir in Directory.GetDirectories(cachePath))
|
|
{
|
|
Directory.Delete(dir, true);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// continue
|
|
}
|
|
}
|
|
|
|
// Save WoW path to config
|
|
private void SaveWowPathToConfig(string wowExecutablePath)
|
|
{
|
|
try
|
|
{
|
|
SaveSettingToConfig(WowPathKey, wowExecutablePath);
|
|
|
|
MessageBox.Show($"World of Warcraft path set successfully to:\n{wowExecutablePath}",
|
|
"Path Saved", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MessageBox.Show($"Error saving WoW path to configuration: {ex.Message}",
|
|
"Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
}
|
|
}
|
|
|
|
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (e.ChangedButton == MouseButton.Left)
|
|
{
|
|
this.DragMove();
|
|
}
|
|
}
|
|
|
|
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
this.Close();
|
|
}
|
|
|
|
// Method for manual refresh
|
|
public async void ManuallyRefreshContent()
|
|
{
|
|
await RefreshAllContent();
|
|
}
|
|
|
|
// Optional: Add this method for a refresh button
|
|
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var button = sender as Button;
|
|
if (button != null)
|
|
{
|
|
button.IsEnabled = false;
|
|
}
|
|
|
|
try
|
|
{
|
|
await RefreshAllContent();
|
|
}
|
|
finally
|
|
{
|
|
if (button != null)
|
|
{
|
|
button.IsEnabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper method to find a TextBlock by its Text property
|
|
private TextBlock FindTextBlockByText(string text)
|
|
{
|
|
return FindVisualChild<TextBlock>(this, tb => tb.Text == text);
|
|
}
|
|
|
|
// Generic method to find visual children
|
|
private T FindVisualChild<T>(DependencyObject parent, Func<T, bool> condition) where T : DependencyObject
|
|
{
|
|
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
|
|
|
for (int i = 0; i < childCount; i++)
|
|
{
|
|
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
|
|
|
if (child is T typedChild && condition(typedChild))
|
|
{
|
|
return typedChild;
|
|
}
|
|
|
|
T result = FindVisualChild<T>(child, condition);
|
|
if (result != null)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
} |