emo-client/Saradomin/ViewModel/Controls/SingleplayerViewModel.cs

240 lines
9.1 KiB
C#

using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using Glitonea.Mvvm;
using Glitonea.Mvvm.Messaging;
using Saradomin.Infrastructure;
using Saradomin.Infrastructure.Services;
using Saradomin.Model.Settings.Launcher;
using Saradomin.Utilities;
using static Saradomin.Utilities.SingleplayerManagement;
namespace Saradomin.ViewModel.Controls
{
public class SingleplayerViewModel : ViewModelBase
{
private readonly ISingleplayerUpdateService _singleplayerUpdateService;
private readonly ISettingsService _settingsService;
private readonly IJavaUpdateService _javaUpdateService;
public string SingleplayerDownloadText { get; private set; } =
Directory.Exists(CrossPlatform.GetSingleplayerHome()) ? "Update Singleplayer" : "Download Singleplayer";
public bool CanLaunch { get; private set; } = File.Exists(CrossPlatform.LocateSingleplayerExecutable());
public TextBox SingleplayerLogsTextBox { get; }
public bool ShowLogPanel { get; private set; }
public LauncherSettings Launcher => _settingsService.Launcher;
private string _oldServerAddress, _oldManagementAddress;
private Process _serverProcess;
public SingleplayerViewModel(ISettingsService settingsService,
IJavaUpdateService javaUpdateService,
ISingleplayerUpdateService iSingleplayerUpdateService)
{
_settingsService = settingsService;
_javaUpdateService = javaUpdateService;
_singleplayerUpdateService = iSingleplayerUpdateService;
_singleplayerUpdateService.SingleplayerDownloadProgressChanged += OnSingleplayerDownloadProgressChanged;
SingleplayerLogsTextBox = new TextBox
{
Foreground = new SolidColorBrush(Colors.Black),
FontSize = 12,
Margin = new Thickness(8, 0, 0, 0),
IsReadOnly = true,
BorderThickness = new Thickness(0),
Background = Brushes.Transparent,
AcceptsReturn = true,
TextWrapping = TextWrapping.Wrap,
};
Message.Subscribe<ClientClosedMessage>(this, OnClientClosed);
}
private void OnClientClosed(ClientClosedMessage _)
{
if (_serverProcess == null) return;
_serverProcess.Kill();
_serverProcess = null;
CanLaunch = true;
_settingsService.Client.GameServerAddress = _oldServerAddress;
_settingsService.Client.ManagementServerAddress = _oldManagementAddress;
}
private void OnSingleplayerDownloadProgressChanged(object sender, Tuple<float, bool> e)
{
float progress = e.Item1;
bool finished = e.Item2;
if (finished)
{
SingleplayerDownloadText = "Update Singleplayer";
ApplyLatestBackup(PrintLog);
PrintLog($"Singleplayer Download complete");
PrintLog($"");
CanLaunch = true;
return;
}
if (progress >= 1f)
{
SingleplayerDownloadText = "Extracting...";
return;
}
SingleplayerDownloadText = $"Downloading... {progress * 100:F2}%";
}
public void DownloadSingleplayer()
{
if (File.Exists(CrossPlatform.LocateSingleplayerExecutable()) && !CanLaunch) return; // While the button could be disabled, this looks nicer visually (since we update the download progress in the button text)
CanLaunch = false;
MakeBackup();
PrintLog($"Starting singleplayer download...");
_singleplayerUpdateService.DownloadSingleplayer();
}
private void MakeBackup()
{
PrintLog("Starting backup of player saves and economy files..");
SingleplayerManagement.MakeBackup(PrintLog);
PrintLog($"Done backing up player saves and economy files");
PrintLog($"");
}
public void OpenBackupFolder()
{
if (!Directory.Exists(CrossPlatform.GetSingleplayerBackupsHome()))
Directory.CreateDirectory(CrossPlatform.GetSingleplayerBackupsHome());
CrossPlatform.OpenFolder(CrossPlatform.GetSingleplayerBackupsHome());
}
[Pure]
public static bool IsServerTerminationLog(string log)
{
return Regex.IsMatch(log, @"^\[\d{2}:\d{2}:\d{2}\]: \[SystemTermination\] Server successfully terminated!\s*$");
}
private void PrintLog(string message)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) message = Regex.Replace(message, @"\e\[[0-9;]*m", string.Empty);
ShowLogPanel = true;
Dispatcher.UIThread.Post(() => { SingleplayerLogsTextBox.Text += message + Environment.NewLine; },
DispatcherPriority.Background);
if (IsServerTerminationLog(message)) CanLaunch = true;
}
public async void LaunchSingleplayer()
{
string javaVersionOutput = CrossPlatform.RunCommandAndGetOutput(
$"\"{Launcher.JavaExecutableLocation}\" -version"
);
if (!javaVersionOutput.Contains("11"))
{
PrintLog("You don't have Java 11 set! Saradomin will grab it's own copy..");
await _javaUpdateService.DownloadAndSetJava11(_settingsService);
}
CanLaunch = false;
PrintLog("Starting Singleplayer.. The Singleplayer client will launch when 2009scape is ready. Sit tight!");
_oldServerAddress = _settingsService.Client.GameServerAddress;
_oldManagementAddress = _settingsService.Client.ManagementServerAddress;
_settingsService.Client.GameServerAddress = _settingsService.Client.ManagementServerAddress = "localhost";
new Task(() => LaunchServerAndClient(Launcher.JavaExecutableLocation, PrintLog)).Start();
}
private void LaunchServerAndClient (string javaExecutableLocation, Action<string> log)
{
var serverJar = CrossPlatform.GetSingleplayerHome() + @"/game/server.jar";
if (IsPortInUse(43595))
{
log("Port 43595 is in use. Cannot start the server again.");
return;
}
_serverProcess = CrossPlatform.StartJavaProcess(javaExecutableLocation, serverJar, "2G", log, null);
while (!IsPortInUse(43595)) Thread.Sleep(1000);
Message.Broadcast<ClientLaunchRequestedMessage>();
}
private static bool IsPortInUse(int port)
{
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners();
return tcpConnInfoArray.Any(endpoint => endpoint.Port == port);
}
private void LaunchFaq()
{
CrossPlatform.LaunchURL("https://2009scape.org/site/game_guide/singleplayer.html");
}
private void LaunchForums()
{
CrossPlatform.LaunchURL("https://forum.2009scape.org/viewforum.php?f=8-support");
}
public bool Cheats
{
get => ParseConf<bool>("noauth_default_admin");
set => WriteConf("noauth_default_admin", value);
}
public bool FakePlayers
{
get => ParseConf<bool>("enable_bots", true);
set => WriteConf("enable_bots", value);
}
public bool GEAutoBuySell
{
get => ParseConf<bool>("i_want_to_cheat");
set => WriteConf("i_want_to_cheat", value);
}
public bool Debug
{
get => ParseConf<bool>("debug");
set => WriteConf("debug", value);
}
public async void ResetToDefaults()
{
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(5);
MakeBackup();
PrintLog("Grabbing the default config from the latest singleplayer codebase..");
try
{
string defaultConf = await httpClient.GetStringAsync("https://gitlab.com/2009scape/singleplayer/windows/-/raw/master/game/worldprops/default.conf");
await File.WriteAllTextAsync(CrossPlatform.GetSingleplayerHome() + "/game/worldprops/default.conf",
defaultConf);
}
catch (Exception ex)
{
PrintLog($"Couldn't reset game properties");
PrintLog(ex.Message);
return;
}
Cheats = GEAutoBuySell = Debug = false; // Force update UI
FakePlayers = true;
PrintLog("Saved new config.");
PrintLog("");
}
}
}