Merge branch 'ava11' into 'master'

Update the entire project to Avalonia 11

See merge request 2009scape/Saradomin-Launcher!54
This commit is contained in:
Natalia Cichoń 2024-03-29 18:28:43 +00:00
commit 27daeff0ed
45 changed files with 752 additions and 1095 deletions

@ -1 +1 @@
Subproject commit 132a10de55d6ef4f16abc5daab6179d2e9cad5ee
Subproject commit 9fb6de8da53dbdde9d782ce4ab1c36c61d98ff71

View file

@ -4,8 +4,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saradomin", "Saradomin\Sara
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Glitonea", "Glitonea\Glitonea.csproj", "{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{6A66E5A7-9297-410F-B061-EB386679E430}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -20,9 +18,5 @@ Global
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2884654-2246-4AC0-B53B-5F1DF33DEDD7}.Release|Any CPU.Build.0 = Release|Any CPU
{6A66E5A7-9297-410F-B061-EB386679E430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A66E5A7-9297-410F-B061-EB386679E430}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A66E5A7-9297-410F-B061-EB386679E430}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A66E5A7-9297-410F-B061-EB386679E430}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -1,8 +1,6 @@
<Application x:Class="Saradomin.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Saradomin.View.Controls"
xmlns:default="clr-namespace:Avalonia.Themes.Default;assembly=Avalonia.Themes.Default">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<Color x:Key="DarkBackgroundColor">#3E3529</Color>
<Color x:Key="DarkTranslucentBackgroundColor">#CC3E3529</Color>
@ -93,8 +91,7 @@
</Application.Resources>
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseDark.xaml" />
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml" />
<SimpleTheme />
<StyleInclude Source="avares://Saradomin/Resources/Styles/TopBarNavigation.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/CloseButton.axaml" />
@ -102,7 +99,9 @@
<StyleInclude Source="avares://Saradomin/Resources/Styles/GroupBox.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/CheckBox.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/ComboBox.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/Expander.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/TextBox.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/TextBlock.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/ScrollBar.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/NumericUpDown.axaml" />
<StyleInclude Source="avares://Saradomin/Resources/Styles/Hyperlink.axaml" />

View file

@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Glitonea;
using Glitonea.Mvvm;
using PropertyChanged;
using Saradomin.View.Windows;

View file

@ -0,0 +1,10 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure
{
public record MainViewLoadedMessage : Message;
public record NotificationBoxStateChangedMessage(bool WasOpened) : Message;
public record SettingsModifiedMessage(string SettingName) : Message;
public record ClientClosedMessage : Message;
public record ClientLaunchRequestedMessage : Message;
}

View file

@ -1,8 +0,0 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure.Messaging;
public class ClientClosedMessage : Message
{
}

View file

@ -1,8 +0,0 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure.Messaging;
public class ClientLaunchRequestedMessage : Message
{
}

View file

@ -1,8 +0,0 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure.Messaging
{
public class MainViewLoadedMessage : Message
{
}
}

View file

@ -1,14 +0,0 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure.Messaging
{
public class NotificationBoxStateChangedMessage : Message
{
public bool WasOpened { get; }
public NotificationBoxStateChangedMessage(bool wasOpened)
{
WasOpened = wasOpened;
}
}
}

View file

@ -1,14 +0,0 @@
using Glitonea.Mvvm.Messaging;
namespace Saradomin.Infrastructure.Messaging
{
public class SettingsModifiedMessage : Message
{
public string SettingName { get; }
public SettingsModifiedMessage(string settingName)
{
SettingName = settingName;
}
}
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.IO;
using System.Linq;
using System.Net.Http;

View file

@ -1,5 +1,5 @@
using System.ComponentModel;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
namespace Saradomin.Model.Settings
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View file

@ -16,8 +16,8 @@
</Canvas>
<ContentPresenter Grid.Column="1"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextBlock.FontSize="13"
TextElement.Foreground="{TemplateBinding Foreground}"
TextElement.FontSize="13"
Margin="2,0,0,0"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
@ -45,8 +45,8 @@
</Canvas>
<ContentPresenter Grid.Column="1"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextBlock.FontSize="13"
TextElement.Foreground="{TemplateBinding Foreground}"
TextElement.FontSize="13"
Margin="2,0,0,0"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"

View file

@ -0,0 +1,34 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="ListBox.PluginList Expander">
<Setter Property="ContentTransition" Value="{x:Null}" />
</Style>
<Style Selector="ListBox.PluginList Expander /template/ ToggleButton#PART_toggle /template/ Border">
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<Style Selector="ListBox.PluginList Expander /template/ ToggleButton#PART_toggle">
<Setter Property="Foreground" Value="{StaticResource LightForegroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList Expander /template/ Path">
<Setter Property="Fill" Value="{StaticResource DarkForegroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList Expander:pointerover /template/ ToggleButton#PART_toggle /template/ Border">
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<Style Selector="ListBox.PluginList Expander:pointerover /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="Fill" Value="{StaticResource AccentBrush}" />
</Style>
<Style Selector="ListBox.PluginList Expander:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="Fill" Value="{StaticResource AccentBrush}" />
</Style>
<Style Selector="ListBox.PluginList Expander:expanded /template/ ToggleButton#PART_toggle">
<Setter Property="Foreground" Value="{StaticResource AccentBrush}" />
</Style>
</Styles>

View file

@ -1,34 +1,38 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="ListBox.PluginList &gt; ListBoxItem">
<Style Selector="ListBox.PluginList > ListBoxItem">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource LightForegroundBrush}" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="ListBox.PluginList > ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:selected /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:pointerover /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:nth-child(2n) /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:nth-child(2n) /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource SemiLightMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:nth-child(2n):selected /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:nth-child(2n):selected /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource SemiLightMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:nth-child(2n):pressed /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:nth-child(2n):pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource SemiLightMediumBackgroundBrush}" />
</Style>
<Style Selector="ListBox.PluginList &gt; ListBoxItem:nth-child(2n):pointerover /template/ ContentPresenter">
<Style Selector="ListBox.PluginList > ListBoxItem:nth-child(2n):pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{StaticResource SemiLightMediumBackgroundBrush}" />
</Style>
</Styles>

View file

@ -9,6 +9,10 @@
<Setter Property="Padding" Value="1" />
<Setter Property="ClipValueToMinMax" Value="True" />
</Style>
<Style Selector="NumericUpDown.NormalNumericUpDown /template/ TextBox#PART_TextBox /template/ ScrollViewer#PART_ScrollViewer /template/ ScrollBar">
<Setter Property="Visibility" Value="Hidden" />
</Style>
<!-- https://github.com/AvaloniaUI/Avalonia/issues/2098
https://github.com/AvaloniaUI/Avalonia/issues/1635 -->

View file

@ -21,8 +21,8 @@
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextBlock.FontSize="13"
TextBlock.Foreground="{TemplateBinding Foreground}" />
TextElement.FontSize="13"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter>
@ -49,8 +49,8 @@
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextBlock.FontSize="13"
TextBlock.Foreground="{TemplateBinding Foreground}" />
TextElement.FontSize="13"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</ControlTemplate>
</Setter>

View file

@ -1,51 +1,46 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="ScrollBar:vertical">
<Setter Property="Width" Value="10" />
<Setter Property="Width" Value="9" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{StaticResource MediumBackgroundBrush}"
ClipToBounds="True">
<Grid RowDefinitions="Auto,*,Auto"
Background="{StaticResource DarkBackgroundBrush}">
<Track Grid.Row="1"
Grid.Column="1"
Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
ViewportSize="{TemplateBinding ViewportSize}"
Orientation="{TemplateBinding Orientation}"
IsDirectionReversed="True">
<Border Background="{StaticResource DarkBackgroundBrush}"
Padding="1,0,0,0">
<Track Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
ViewportSize="{TemplateBinding ViewportSize}"
Orientation="{TemplateBinding Orientation}"
DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
IsDirectionReversed="True">
<Track.Thumb>
<Thumb Name="thumb"
Width="4"
Margin="0,10,0,10" />
</Track>
</Grid>
HorizontalAlignment="Stretch" />
</Track.Thumb>
</Track>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ScrollViewer.MainViewScrollViewer /template/ ScrollBar:vertical">
<Setter Property="Width" Value="10" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{StaticResource MediumBackgroundBrush}"
ClipToBounds="True"
CornerRadius="0,6,0,0">
<Grid RowDefinitions="Auto,*,Auto"
Background="{StaticResource DarkBackgroundBrush}">
<Track Grid.Row="1"
Grid.Column="1"
Minimum="{TemplateBinding Minimum}"
<Border CornerRadius="0,4,0,0"
Background="{StaticResource DarkBackgroundBrush}">
<Grid>
<Track Minimum="{TemplateBinding Minimum}"
Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value, Mode=TwoWay}"
ViewportSize="{TemplateBinding ViewportSize}"
Orientation="{TemplateBinding Orientation}"
DeferThumbDrag="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
IsDirectionReversed="True">
<Thumb Name="thumb"
Width="4"
Margin="0,10,0,10" />
<Track.Thumb>
<Thumb Name="thumb"
HorizontalAlignment="Stretch" />
</Track.Thumb>
</Track>
</Grid>
</Border>
@ -57,6 +52,11 @@
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="Thumb#thumb /template/ Border">
<Setter Property="Margin" Value="0,6" />
</Style>
<Style Selector="Thumb#thumb:pointerover">
<Setter Property="Background" Value="{StaticResource MouseOverBrush}" />
</Style>

View file

@ -0,0 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TextBlock.NewsRenderView">
<Setter Property="Foreground" Value="Black" />
</Style>
</Styles>

View file

@ -10,7 +10,7 @@
<Setter Property="Margin" Value="4,0,0,0" />
</Style>
<Style Selector="TabControl.PageSelector &gt; TabItem">
<Style Selector="TabControl.PageSelector > TabItem">
<Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="Light" />
<Setter Property="Height" Value="20" />
@ -22,19 +22,23 @@
<Setter Property="Margin" Value="0,0,0,0" />
<Setter Property="Padding" Value="6,0,6,1" />
</Style>
<Style Selector="TabControl.PageSelector &gt; TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.PageSelector /template/ Border">
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="TabControl.PageSelector > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkMediumBackgroundBrush}" />
</Style>
<Style Selector="TabControl.PageSelector &gt; TabItem:focus /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.PageSelector > TabItem:focus /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkBackgroundBrush}" />
</Style>
<Style Selector="TabControl.PageSelector &gt; TabItem:selected">
<Style Selector="TabControl.PageSelector > TabItem:selected">
<Setter Property="Foreground" Value="{StaticResource AccentBrush}" />
</Style>
<Style Selector="TabControl.PageSelector &gt; TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.PageSelector > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DarkBackgroundBrush}" />
</Style>
</Styles>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Version>1.6.0</Version>
<Version>1.6.1</Version>
<NoWarn>IL2026;CS0067</NoWarn>
<ApplicationIcon>Resources\Icons\saradomin.ico</ApplicationIcon>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
@ -24,12 +24,13 @@
</Target>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia" Version="11.0.10" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageReference Include="Avalonia.Themes.Simple" Version="11.0.10" />
<PackageReference Include="GitLabApiClient" Version="1.8.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.60" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="NSubsys" Version="1.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -42,13 +43,6 @@
<ProjectReference Include="..\Glitonea\Glitonea.csproj" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Resources\Fonts\runescape_uf.ttf" />
<AvaloniaResource Include="Resources\Images\previewbg.png" />
<AvaloniaResource Include="Resources\Images\club.png" />
<AvaloniaResource Include="Resources\Fonts\runescape_plain.ttf" />
</ItemGroup>
<ItemGroup>
<Compile Update="Views\Windows\MainWindow.axaml.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>

View file

@ -3,7 +3,6 @@ using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Win32;
using Mono.Unix;

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Web;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
using Avalonia.Media;
using HtmlAgilityPack;
@ -12,54 +13,40 @@ namespace Saradomin.Utilities
public class HtmlRenderer
{
private int _listDescent = 0;
private bool seenNonWhiteSpace = false;
private Stack<StackPanel> _panels = new();
private StackPanel _container;
private string _prefix;
private InlineCollection _inlines;
private HtmlNode _rootNode;
public StackPanel CurrentLine => _panels.Peek();
public HtmlRenderer(StackPanel container, HtmlNode rootNode)
public HtmlRenderer(HtmlNode rootNode)
{
_container = container;
_rootNode = rootNode;
}
public void RenderToContainer()
public InlineCollection Render()
{
_panels.Push(_container);
Visit(_rootNode);
_panels.Pop();
_inlines = new();
{
Visit(_rootNode);
}
return _inlines;
}
private void InNewBlockDo(Action<StackPanel> action)
private void InNewBlockDo(Action action)
{
var line = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Left
};
_panels.Push(line);
action(line);
_container.Children.Add(_panels.Pop());
action();
_inlines.Add(new LineBreak());
}
private void CreateNewInlineTextElement(
string text,
FontWeight weight = FontWeight.Normal,
double fontSize = 12,
TextWrapping wrapping = TextWrapping.Wrap)
double fontSize = 12
)
{
CurrentLine.Children.Add(new TextBlock
_inlines.Add(new Run(text)
{
Foreground = new SolidColorBrush(Colors.Black),
FontSize = fontSize,
FontWeight = weight,
TextWrapping = wrapping,
Text = text,
Margin = new Thickness(_listDescent * 8, 0, 0, 0)
FontSize = fontSize,
});
}
@ -77,7 +64,7 @@ namespace Saradomin.Utilities
{
_listDescent++;
{
InNewBlockDo((_) =>
InNewBlockDo(() =>
{
foreach (var child in htmlNode.ChildNodes)
{
@ -90,15 +77,29 @@ namespace Saradomin.Utilities
private void VisitListEntry(HtmlNode htmlNode)
{
foreach (var child in htmlNode.ChildNodes)
_inlines.Add(new Run("• "));
for (var i = 0; i < htmlNode.ChildNodes.Count; i++)
{
var child = htmlNode.ChildNodes[i];
if (i != 0)
{
_prefix = " ";
}
Visit(child);
_prefix = null;
}
_prefix = null;
_inlines.Add(new LineBreak());
}
private void VisitParagraph(HtmlNode htmlNode)
{
InNewBlockDo((_) =>
InNewBlockDo(() =>
{
foreach (var child in htmlNode.ChildNodes)
{
@ -115,21 +116,14 @@ namespace Saradomin.Utilities
var text = HttpUtility.HtmlDecode(
htmlNode.GetDirectInnerText().Trim(' ').Trim('\n')
);
if (!seenNonWhiteSpace && string.IsNullOrWhiteSpace(htmlNode.InnerText)) break;
seenNonWhiteSpace = true;
if (htmlNode.ParentNode.Name == "li")
{
if (!string.IsNullOrWhiteSpace(text))
{
text = $"• {text}";
}
}
CreateNewInlineTextElement(text);
CreateNewInlineTextElement($"{_prefix}{text}");
break;
case "br":
_inlines.Add(new LineBreak());
break;
case "strong":
case "b":
VisitBold(htmlNode);
break;

View file

@ -2,159 +2,159 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
namespace Saradomin.Utilities;
public static class SingleplayerManagement
namespace Saradomin.Utilities
{
private static readonly string[] DirsToBackup =
public static class SingleplayerManagement
{
"game/data/players",
"game/data/serverstore",
"game/worldprops"
};
private static readonly string[] FilesToBackup =
{
"game/data/eco/grandexchange.db"
};
public static void MakeBackup(Action<string> log)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string newBackupDir = Path.Combine(
CrossPlatform.GetSingleplayerBackupsHome(),
timestamp
);
foreach (string dir in DirsToBackup)
private static readonly string[] DirsToBackup =
{
string sourcePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), dir);
if (!Directory.Exists(sourcePath))
"game/data/players",
"game/data/serverstore",
"game/worldprops"
};
private static readonly string[] FilesToBackup =
{
"game/data/eco/grandexchange.db"
};
public static void MakeBackup(Action<string> log)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string newBackupDir = Path.Combine(
CrossPlatform.GetSingleplayerBackupsHome(),
timestamp
);
foreach (string dir in DirsToBackup)
{
log($" Skipping backup of {dir} because it doesn't exist (Full path attempted: {sourcePath})");
continue;
string sourcePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), dir);
if (!Directory.Exists(sourcePath))
{
log($" Skipping backup of {dir} because it doesn't exist (Full path attempted: {sourcePath})");
continue;
}
string destPath = Path.Combine(newBackupDir, dir);
CopyDirectory(sourcePath, destPath);
}
string destPath = Path.Combine(newBackupDir, dir);
CopyDirectory(sourcePath, destPath);
foreach (string file in FilesToBackup)
{
string sourceFilePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), file);
if (!File.Exists(sourceFilePath))
{
log($" Skipping backup of {file} because it doesn't exist (Full path attempted: {sourceFilePath})");
continue;
}
string destFilePath = Path.Combine(newBackupDir, file);
string directoryForFile = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(directoryForFile))
Directory.CreateDirectory(directoryForFile);
File.Copy(sourceFilePath, destFilePath, true);
}
}
foreach (string file in FilesToBackup)
private static void CopyDirectory(string sourceDir, string destinationDir)
{
string sourceFilePath = Path.Combine(CrossPlatform.GetSingleplayerHome(), file);
if (!File.Exists(sourceFilePath))
if (!Directory.Exists(destinationDir))
Directory.CreateDirectory(destinationDir);
foreach (string filePath in Directory.GetFiles(sourceDir))
{
log($" Skipping backup of {file} because it doesn't exist (Full path attempted: {sourceFilePath})");
continue;
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destinationDir, fileName);
File.Copy(filePath, destFilePath, true);
}
string destFilePath = Path.Combine(newBackupDir, file);
string directoryForFile = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(directoryForFile))
Directory.CreateDirectory(directoryForFile);
File.Copy(sourceFilePath, destFilePath, true);
}
}
private static void CopyDirectory(string sourceDir, string destinationDir)
{
if (!Directory.Exists(destinationDir))
Directory.CreateDirectory(destinationDir);
foreach (string filePath in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(filePath);
string destFilePath = Path.Combine(destinationDir, fileName);
File.Copy(filePath, destFilePath, true);
}
foreach (string subDirPath in Directory.GetDirectories(sourceDir))
{
string subDirName = Path.GetFileName(subDirPath);
string destSubDirPath = Path.Combine(destinationDir, subDirName);
// Recurse!
CopyDirectory(subDirPath, destSubDirPath);
}
}
public static string FindMostRecentBackupDirectory(IEnumerable<string> directories)
{
return directories.Select(Path.GetFileName).MaxBy(name => name);
}
public static void ApplyLatestBackup(Action<string> log)
{
var backupHome = CrossPlatform.GetSingleplayerBackupsHome();
var singleplayerHome = CrossPlatform.GetSingleplayerHome();
// Get all backup directories
string[] backupDirectories = Directory.Exists(backupHome) ? Directory.GetDirectories(backupHome) : Array.Empty<string>();
string mostRecentBackupDirName = FindMostRecentBackupDirectory(backupDirectories);
if (mostRecentBackupDirName == null)
{
log(" No backups found.");
return;
}
string mostRecentBackupFullPath = Path.Combine(backupHome, mostRecentBackupDirName);
// Get the list of files in the most recent backup
var filesInBackup = Directory.GetFiles(
mostRecentBackupFullPath,
"*",
SearchOption.AllDirectories
);
foreach (var file in filesInBackup)
{
// Calculate the file's destination path in the singleplayer home directory
var relativePath = file.Substring(mostRecentBackupFullPath.Length)
.TrimStart(Path.DirectorySeparatorChar);
var destFilePath = Path.Combine(singleplayerHome, relativePath);
// Ensure the destination directory exists
var destDirectory = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(destDirectory))
foreach (string subDirPath in Directory.GetDirectories(sourceDir))
{
Directory.CreateDirectory(destDirectory);
string subDirName = Path.GetFileName(subDirPath);
string destSubDirPath = Path.Combine(destinationDir, subDirName);
// Recurse!
CopyDirectory(subDirPath, destSubDirPath);
}
}
public static string FindMostRecentBackupDirectory(IEnumerable<string> directories)
{
return directories.Select(Path.GetFileName).MaxBy(name => name);
}
public static void ApplyLatestBackup(Action<string> log)
{
var backupHome = CrossPlatform.GetSingleplayerBackupsHome();
var singleplayerHome = CrossPlatform.GetSingleplayerHome();
// Get all backup directories
string[] backupDirectories = Directory.Exists(backupHome) ? Directory.GetDirectories(backupHome) : Array.Empty<string>();
string mostRecentBackupDirName = FindMostRecentBackupDirectory(backupDirectories);
if (mostRecentBackupDirName == null)
{
log(" No backups found.");
return;
}
// Copy the file to the destination, overwriting any existing file
File.Copy(file, destFilePath, true);
string mostRecentBackupFullPath = Path.Combine(backupHome, mostRecentBackupDirName);
// Get the list of files in the most recent backup
var filesInBackup = Directory.GetFiles(
mostRecentBackupFullPath,
"*",
SearchOption.AllDirectories
);
foreach (var file in filesInBackup)
{
// Calculate the file's destination path in the singleplayer home directory
var relativePath = file.Substring(mostRecentBackupFullPath.Length)
.TrimStart(Path.DirectorySeparatorChar);
var destFilePath = Path.Combine(singleplayerHome, relativePath);
// Ensure the destination directory exists
var destDirectory = Path.GetDirectoryName(destFilePath);
if (!Directory.Exists(destDirectory))
{
Directory.CreateDirectory(destDirectory);
}
// Copy the file to the destination, overwriting any existing file
File.Copy(file, destFilePath, true);
}
log($" Backup from {mostRecentBackupDirName} has been successfully applied.");
}
log($" Backup from {mostRecentBackupDirName} has been successfully applied.");
}
private static Dictionary<string, string> _confCache;
private static string ConfPath => Path.Combine(CrossPlatform.GetSingleplayerHome(), "game", "worldprops", "default.conf");
public static Dictionary<string, string> GrabConfCache()
{
return File.ReadLines(ConfPath)
.Where(line => line.Contains('=') && !line.TrimStart().StartsWith("#"))
.Select(l => l.Split(new[] { '#', '=' }, 3))
.ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
}
private static Dictionary<string, string> _confCache;
private static string ConfPath => Path.Combine(CrossPlatform.GetSingleplayerHome(), "game", "worldprops", "default.conf");
public static Dictionary<string, string> GrabConfCache()
{
return File.ReadLines(ConfPath)
.Where(line => line.Contains('=') && !line.TrimStart().StartsWith("#"))
.Select(l => l.Split(new[] { '#', '=' }, 3))
.ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
}
public static T ParseConf<T>(string key, T defaultValue = default)
{
if (!File.Exists(ConfPath)) return defaultValue;
_confCache ??= GrabConfCache();
if (!_confCache.TryGetValue(key, out var value)) return defaultValue;
return (T)Convert.ChangeType(value, typeof(T));
}
public static T ParseConf<T>(string key, T defaultValue = default)
{
if (!File.Exists(ConfPath)) return defaultValue;
_confCache ??= GrabConfCache();
if (!_confCache.TryGetValue(key, out var value)) return defaultValue;
return (T)Convert.ChangeType(value, typeof(T));
}
public static void WriteConf(string key, object value)
{
_confCache[key] = value.ToString().ToLowerInvariant(); // Update cache
var lines = File.ReadAllLines(ConfPath).Select(l =>
l.StartsWith(key + " =")
? $"{key} = {_confCache[key]}" + (l.Contains("#") ? " " + l.Substring(l.IndexOf('#')) : "")
: l);
File.WriteAllLines(ConfPath, lines);
public static void WriteConf(string key, object value)
{
_confCache[key] = value.ToString().ToLowerInvariant(); // Update cache
var lines = File.ReadAllLines(ConfPath).Select(l =>
l.StartsWith(key + " =")
? $"{key} = {_confCache[key]}" + (l.Contains("#") ? " " + l.Substring(l.IndexOf('#')) : "")
: l);
File.WriteAllLines(ConfPath, lines);
}
}
}

View file

@ -6,102 +6,127 @@
xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
xmlns:vm="clr-namespace:Saradomin.ViewModel.Controls"
DataContext="{mvvm:DataContextSource vm:PluginManagerViewModel}">
<Grid RowDefinitions="*,Auto">
<Border Grid.Row="1"
Height="2"
Margin="1,2"
BorderBrush="{StaticResource DarkMediumBorderBrush}"
BorderThickness="0,1,0,0" />
<Border Grid.Row="0">
<Grid RowDefinitions="Auto,*">
<Border Grid.Row="0"
Height="350"
Margin="0,2,0,0"
BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1,1,0,1"
ClipToBounds="True">
<Grid>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="#BBFFFFFF"
IsVisible="{Binding !!!PluginList.Count}"
Text="Fetching plugin metadata..."
ZIndex="9999"
Width="425" />
<ListBox Classes="PluginList"
Items="{Binding PluginList}"
SelectionMode="Single"
ClipToBounds="True"
IsVisible="{Binding PluginList.Count}"
VirtualizationMode="Simple">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:PluginInfo}">
<Grid ColumnDefinitions="250,50,100" RowDefinitions="*" >
<TextBlock Grid.Column="1"
Margin="2,5,2,0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Foreground="#BBFFFFFF"
FontSize="11"
Text="{Binding Version}" />
<Button Grid.Row="0" Grid.Column="2"
Margin="-125,0,2,2"
BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.UpdateLocalPlugin}"
VerticalAlignment="Top"
Content="Update"
IsVisible="{Binding CanUpdate}"
IsEnabled="{Binding !$parent[local:PluginManagerView].DataContext.IsTransactionInProgress}" />
<Button Grid.Row="0" Grid.Column="2"
Margin="0,0,2,2"
BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.InstallRemotePlugin}"
CommandParameter="{Binding}"
VerticalAlignment="Top"
Content="Install"
IsVisible="{Binding !Installed}"
IsEnabled="{Binding !$parent[local:PluginManagerView].DataContext.IsTransactionInProgress}" />
<Button Grid.Row="0" Grid.Column="2"
Margin="0,0,2,2"
BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.UninstallLocalPlugin}"
CommandParameter="{Binding}"
VerticalAlignment="Top"
Content="Uninstall"
IsVisible="{Binding Installed}"
IsEnabled="{Binding !$parent[local:PluginManagerView].DataContext.IsTransactionInProgress}" />
<Expander Grid.Column="0" Grid.Row="0" Header="{Binding Name}">
<StackPanel>
<TextBlock
Margin="2,0,0,0"
VerticalAlignment="Center"
Foreground="#BBFFFFFF"
FontSize="11"
FontStyle="Oblique"
Text="{Binding Author}" />
<Grid Margin="0,0,0,4"
RowDefinitions="Auto,*">
<Grid Grid.Row="0"
Margin="0,0,0,4">
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{StaticResource DarkBackgroundBrush}">
<Run Text="Status:" />
<Run Text="{Binding CurrentStatusMessage}" />
</TextBlock>
<TextBlock
Margin="2,5,0,4"
VerticalAlignment="Bottom"
Foreground="#BBFFFFFF"
TextWrapping="Wrap"
FontSize="11"
Text="{Binding Description}" />
</StackPanel>
</Expander>
<Button Classes="Hyperlink"
Content="check for updates"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding CheckForUpdates}"
IsEnabled="{Binding !IsTransactionInProgress}" />
</Grid>
<TextBlock Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource DarkForegroundBrush}"
IsVisible="{Binding !!!PluginList.Count}"
Text="Reloading plugin registry..." />
<ListBox Grid.Row="1"
Classes="PluginList"
ItemsSource="{Binding PluginList}"
SelectionMode="Single"
ClipToBounds="True"
BorderThickness="1"
BorderBrush="{StaticResource DarkBorderBrush}"
IsVisible="{Binding PluginList.Count}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<!-- There won't be too many plugins and virtualization hides any
expanded items, so by using this instead of VirtualizingStackPanel
helps avoid UI inconsistency. -->
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:PluginInfo}">
<Grid ColumnDefinitions="*,Auto,Auto">
<Expander Grid.Column="0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Header="{Binding}">
<Expander.HeaderTemplate>
<DataTemplate>
<Grid ColumnDefinitions="*,Auto">
<Grid Grid.Column="0"
HorizontalAlignment="Stretch"
ColumnDefinitions="Auto, Auto">
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding Name}" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="#BBFFFFFF"
FontSize="11"
Text="{Binding Version, StringFormat='{} [v{0}]'}" />
</Grid>
<Grid Grid.Column="1"
IsEnabled="{Binding !$parent[local:PluginManagerView].DataContext.IsTransactionInProgress}"
VerticalAlignment="Center"
Margin="0,2,4,2">
<Button BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.UpdateLocalPlugin}"
VerticalAlignment="Top"
Content="Update"
IsVisible="{Binding CanUpdate}" />
<Button BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.InstallRemotePlugin}"
CommandParameter="{Binding}"
VerticalAlignment="Top"
Content="Install"
IsVisible="{Binding !Installed}" />
<Button BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding $parent[local:PluginManagerView].DataContext.UninstallLocalPlugin}"
CommandParameter="{Binding}"
VerticalAlignment="Top"
Content="Uninstall"
IsVisible="{Binding Installed}" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander.HeaderTemplate>
<StackPanel>
<TextBlock Margin="4,0,0,0"
Foreground="#BBFFFFFF"
FontSize="11"
FontStyle="Oblique"
Text="{Binding Author}" />
<TextBlock Margin="4,0,0,4"
MaxWidth="400"
FontSize="11"
HorizontalAlignment="Left"
Foreground="#BBFFFFFF"
TextWrapping="Wrap"
TextAlignment="DetectFromContent"
Text="{Binding Description}" />
</StackPanel>
</Expander>
</Grid>
</Border>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View file

@ -1,21 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using PropertyChanged;
namespace Saradomin.View.Controls
{
[DoNotNotify]
public class PluginManagerView : UserControl
public partial class PluginManagerView : UserControl
{
public PluginManagerView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -2,8 +2,6 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Saradomin.View.Controls"
xmlns:converters="clr-namespace:Glitonea.Mvvm.Converters;assembly=Glitonea"
xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
xmlns:vm="clr-namespace:Saradomin.ViewModel.Controls"
DataContext="{mvvm:DataContextSource vm:SettingsViewModel}">
@ -70,7 +68,7 @@
<ComboBox Grid.Row="1"
Margin="2,0,0,2"
Items="{Binding ServerProfiles}"
ItemsSource="{Binding ServerProfiles}"
SelectedItem="{Binding ServerProfile, Converter={StaticResource EnumDescriptionConverter}, Mode=TwoWay}">
<ComboBox.Styles>
<Style Selector="ComboBoxItem">
@ -119,11 +117,13 @@
</HeaderedContentControl>
</DockPanel>
<DockPanel Grid.Column="1">
<DockPanel Grid.Column="1"
DockPanel.Dock="Top">
<HeaderedContentControl Classes="GroupBox"
DockPanel.Dock="Top"
Header="client settings">
<Grid RowDefinitions="Auto,*">
<Grid RowDefinitions="Auto,*"
Margin="0,0,0,4">
<Grid Grid.Row="0" ColumnDefinitions="Auto, *">
<Grid Grid.Column="0"
RowDefinitions="Auto, Auto">
@ -177,33 +177,15 @@
Value="{Binding Client.Fps, Mode=TwoWay}" />
</Grid>
</Grid>
<HeaderedContentControl Grid.Row="1"
Margin="0,4,0,4"
Padding="3,0,7,0"
BorderThickness="0"
Classes="GroupBox"
Header="plugin management">
<DockPanel>
<controls:PluginManagerView Margin="-2,2" Name="PmView"/>
<Button
Margin="-141,-17,0,2"
Width="140"
Height="17"
BorderBrush="{StaticResource DarkBorderBrush}"
BorderThickness="1"
Classes="OutsideNavigator"
Command="{Binding #PmView.DataContext.CheckForUpdates}"
VerticalAlignment="Top"
Opacity="1"
FontSize="11"
Content="check for updates"
ZIndex="9999"
IsEnabled="{Binding !#PmView.DataContext.IsTransactionInProgress}" />
</DockPanel>
</HeaderedContentControl>
</Grid>
</HeaderedContentControl>
<HeaderedContentControl Classes="GroupBox"
Header="plugin management"
DockPanel.Dock="Top">
<controls:PluginManagerView MaxHeight="346"/>
</HeaderedContentControl>
</DockPanel>
</Grid>
</UserControl>

View file

@ -5,16 +5,11 @@ using PropertyChanged;
namespace Saradomin.View.Controls
{
[DoNotNotify]
public class SettingsView : UserControl
public partial class SettingsView : UserControl
{
public SettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -5,16 +5,11 @@ using PropertyChanged;
namespace Saradomin.View.Controls
{
[DoNotNotify]
public class SingleplayerView : UserControl
public partial class SingleplayerView : UserControl
{
public SingleplayerView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -2,16 +2,15 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Saradomin.View.Controls"
xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
xmlns:glitonea="clr-namespace:Glitonea.Controls;assembly=Glitonea"
xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
xmlns:vm="clr-namespace:Saradomin.ViewModel.Windows"
Name="MainWindow"
Title="{Binding Title}"
DataContext="{mvvm:DataContextSource vm:MainWindowViewModel}"
Title="{Binding Title, Mode=TwoWay}"
Width="800"
Height="550"
Background="{StaticResource WindowBackgroundBrush}"
CanResize="False"
DataContext="{mvvm:DataContextSource vm:MainWindowViewModel}"
Icon="avares://Saradomin/Resources/Icons/saradomin.ico"
SystemDecorations="None"
WindowStartupLocation="CenterScreen">
@ -43,7 +42,7 @@
FontSize="14"
FontWeight="Light"
Foreground="{StaticResource AccentBrush}"
Text="{Binding #MainWindow.Title}"
Text="{Binding Title, Mode=TwoWay}"
TextAlignment="Center" />
<Button Grid.Column="2"
@ -86,15 +85,15 @@
</Grid>
<TabControl Grid.Row="1"
Grid.RowSpan="1"
Background="Transparent"
BorderBrush="{StaticResource DarkBorderBrush}"
Padding="0"
Classes="PageSelector">
<TabItem Width="95"
HorizontalContentAlignment="Center"
CornerRadius="0,0,0,8"
Header="what's new">
<Border Margin="4,0,4,0"
<Border Margin="4,4,4,0"
Background="{StaticResource MediumBackgroundBrush}"
BorderBrush="{StaticResource SemiLightMediumBorderBrush}"
BorderThickness="1,1,1,0"
@ -103,9 +102,15 @@
<ScrollViewer HorizontalAlignment="Stretch"
Classes="MainViewScrollViewer"
ClipToBounds="True"
Content="{Binding ContentContainer}"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Visible" />
VerticalScrollBarVisibility="Visible">
<TextBlock Classes="NewsRenderView"
MaxWidth="768"
Inlines="{Binding HtmlInlines}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
TextWrapping="Wrap"/>
</ScrollViewer>
</Border>
</TabItem>
@ -113,7 +118,7 @@
HorizontalContentAlignment="Center"
CornerRadius="0,0,0,0"
Header="settings">
<Border Margin="4,0,4,0"
<Border Margin="4,4,4,0"
Background="{StaticResource MediumBackgroundBrush}"
BorderBrush="{StaticResource SemiLightMediumBorderBrush}"
BorderThickness="1,1,1,0"
@ -121,7 +126,8 @@
<ScrollViewer Classes="MainViewScrollViewer"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Visible">
<controls:SettingsView VerticalAlignment="Stretch" />
<controls:SettingsView VerticalAlignment="Stretch"
MaxHeight="500"/>
</ScrollViewer>
</Border>
</TabItem>
@ -130,7 +136,7 @@
HorizontalContentAlignment="Center"
CornerRadius="0,0,8,0"
Header="singleplayer">
<Border Margin="4,0,4,0"
<Border Margin="4,4,4,0"
Background="{StaticResource MediumBackgroundBrush}"
BorderBrush="{StaticResource SemiLightMediumBorderBrush}"
BorderThickness="1,1,1,0"

View file

@ -1,23 +1,17 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Glitonea.Mvvm.Messaging;
using PropertyChanged;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
namespace Saradomin.View.Windows
{
[DoNotNotify]
public class MainWindow : Window
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
protected override void OnOpened(EventArgs e)
@ -26,11 +20,6 @@ namespace Saradomin.View.Windows
.Broadcast();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void TitleBar_MouseDown(object _, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)

View file

@ -1,10 +1,9 @@
<glitonea:WindowEx x:Class="Saradomin.View.Windows.NotificationBox"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:glitonea="clr-namespace:Glitonea;assembly=Glitonea"
xmlns:glitonea="clr-namespace:Glitonea.Controls;assembly=Glitonea"
xmlns:mvvm="clr-namespace:Glitonea.Mvvm;assembly=Glitonea"
xmlns:windows="clr-namespace:Saradomin.ViewModel.Windows"
Name="NotificationBox"
Title="{Binding Title}"
Height="120"
MaxWidth="400"

View file

@ -2,28 +2,24 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Glitonea;
using Glitonea.Controls;
using Glitonea.Extensions;
using PropertyChanged;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
namespace Saradomin.View.Windows
{
[DoNotNotify]
public class NotificationBox : WindowEx
public partial class NotificationBox : WindowEx
{
private static Queue<NotificationBox> _notificationQueue = new();
public static NotificationBox Current { get; private set; }
public static readonly StyledProperty<string> MessageProperty = new(
public static readonly StyledProperty<string> MessageProperty = AvaloniaProperty.Register<NotificationBox, string>(
nameof(Message),
typeof(NotificationBox),
new StyledPropertyMetadata<string>(
"This is supposed to be a message.\n" +
"Someone messed up, though, so have this placeholder instead."
)
"This is supposed to be a message.\n" +
"Someone messed up, though, so have this placeholder instead."
);
public string Message
@ -87,10 +83,5 @@ namespace Saradomin.View.Windows
.Broadcast();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -1,10 +1,11 @@
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Glitonea.Mvvm;
using Glitonea.Mvvm.Messaging;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
using Saradomin.Infrastructure.Services;
using Saradomin.Model;
using Saradomin.View.Windows;
@ -18,6 +19,7 @@ namespace Saradomin.ViewModel.Controls
public bool IsTransactionInProgress { get; private set; }
public ObservableCollection<PluginInfo> PluginList { get; private set; } = new();
public string CurrentStatusMessage { get; set; } = "Idle...";
public PluginManagerViewModel(
IPluginManagementService pluginManagementService,
@ -27,26 +29,36 @@ namespace Saradomin.ViewModel.Controls
_pluginDownloadService = pluginDownloadService;
Message.Subscribe<MainViewLoadedMessage>(this, MainViewLoaded);
PropertyChanged += ViewModelPropertyChanged;
}
public async Task RefreshPluginCollections()
{
PluginList.Clear();
var remotePlugins =
await _pluginDownloadService
.GetAllMetadata(_pluginManagementService.PluginRepositoryPath, false, false);
var remotePlugins =await _pluginDownloadService.GetAllMetadata(
_pluginManagementService.PluginRepositoryPath,
false,
false
);
PluginList = new ObservableCollection<PluginInfo>(remotePlugins.OrderByDescending(x => x.Installed));
}
public async Task InstallRemotePlugin(PluginInfo pluginInfo)
public async Task InstallRemotePlugin(object parameter)
{
if (parameter is not PluginInfo pluginInfo)
return;
if (IsTransactionInProgress)
return;
try
{
IsTransactionInProgress = true;
UpdateStatusMessage($"Installing {pluginInfo.Name}...");
await _pluginDownloadService.DownloadPluginFiles(
pluginInfo.Name,
@ -68,8 +80,11 @@ namespace Saradomin.ViewModel.Controls
}
}
public async Task UninstallLocalPlugin(PluginInfo pluginInfo)
public async Task UninstallLocalPlugin(object parameter)
{
if (parameter is not PluginInfo pluginInfo)
return;
if (IsTransactionInProgress)
return;
@ -77,10 +92,11 @@ namespace Saradomin.ViewModel.Controls
{
IsTransactionInProgress = true;
await _pluginManagementService.UninstallPlugin(pluginInfo.Name);
UpdateStatusMessage($"Uninstalling {pluginInfo.Name}...");
await _pluginManagementService.UninstallPlugin(pluginInfo.Name);
pluginInfo.Installed = false;
PluginList.Add(pluginInfo);
PluginList = new(PluginList.OrderByDescending(x => x.Installed));
}
catch (Exception e)
@ -100,16 +116,19 @@ namespace Saradomin.ViewModel.Controls
{
if (IsTransactionInProgress)
return;
IsTransactionInProgress = true;
try
{
var updatablePlugins =
await _pluginDownloadService.GetAllMetadata(
_pluginManagementService.PluginRepositoryPath,
true,
true
);
IsTransactionInProgress = true;
UpdateStatusMessage($"Checking for updates...");
var updatablePlugins = await _pluginDownloadService.GetAllMetadata(
_pluginManagementService.PluginRepositoryPath,
true,
true
);
foreach (var plugin in updatablePlugins)
{
@ -125,8 +144,11 @@ namespace Saradomin.ViewModel.Controls
}
}
public async Task UpdateLocalPlugin (PluginInfo info)
public async Task UpdateLocalPlugin(object parameter)
{
if (parameter is not PluginInfo pluginInfo)
return;
if (IsTransactionInProgress)
return;
@ -134,15 +156,17 @@ namespace Saradomin.ViewModel.Controls
{
IsTransactionInProgress = true;
await _pluginDownloadService.DownloadPluginFiles(info.Name, _pluginManagementService.PluginRepositoryPath);
UpdateStatusMessage($"Updating {pluginInfo.Name}...");
await _pluginDownloadService.DownloadPluginFiles(pluginInfo.Name, _pluginManagementService.PluginRepositoryPath);
await RefreshPluginCollections();
var newInfo = PluginList.First(x => x.Name == info.Name);
if (newInfo.Version != info.Version)
var newInfo = PluginList.First(x => x.Name == pluginInfo.Name);
if (newInfo.Version != pluginInfo.Version)
{
NotificationBox.DisplayNotification(
"Success!",
$"Successfully updated {info.Name} to version {newInfo.Version}!"
$"Successfully updated {pluginInfo.Name} to version {newInfo.Version}!"
);
}
}
@ -159,9 +183,30 @@ namespace Saradomin.ViewModel.Controls
}
}
private void UpdateStatusMessage(string message)
{
CurrentStatusMessage = message;
}
private async void MainViewLoaded(MainViewLoadedMessage _)
{
await RefreshPluginCollections();
}
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(IsTransactionInProgress):
{
if (!IsTransactionInProgress)
{
UpdateStatusMessage("Idle...");
}
break;
}
}
}
}
}

View file

@ -1,22 +1,18 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Glitonea.Extensions;
using Glitonea.Mvvm;
using Glitonea.Mvvm.Messaging;
using Glitonea.Utilities;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
using Saradomin.Infrastructure.Services;
using Saradomin.Model.Settings.Client;
using Saradomin.Model.Settings.Launcher;
using Saradomin.Utilities;
using Saradomin.View.Windows;
namespace Saradomin.ViewModel.Controls
{
@ -78,20 +74,34 @@ namespace Saradomin.ViewModel.Controls
Message.Subscribe<MainViewLoadedMessage>(this, OnMainViewLoaded);
}
public void LaunchScapeWebsite()
=> CrossPlatform.LaunchURL("https://2009scape.org");
private void LaunchScapeWebsite()
{
CrossPlatform.LaunchURL("https://2009scape.org");
}
public void OpenPluginTutorial()
=> CrossPlatform.LaunchURL("https://gitlab.com/2009scape/tools/client-plugins");
private void OpenPluginTutorial()
{
CrossPlatform.LaunchURL("https://gitlab.com/2009scape/tools/client-plugins");
}
public void LaunchProjectWebsite()
=> CrossPlatform.LaunchURL("https://gitlab.com/2009scape/Saradomin-Launcher");
private void LaunchProjectWebsite()
public async Task BrowseForJavaExecutable()
{
CrossPlatform.LaunchURL("https://gitlab.com/2009scape/Saradomin-Launcher");
var window = Application.Current!.GetMainWindow();
var pickerOptions = new FilePickerOpenOptions
{
Title = "Browse for Java...",
AllowMultiple = false,
SuggestedStartLocation =await window!.StorageProvider.TryGetFolderFromPathAsync(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
)
};
var storageFiles = await window.StorageProvider.OpenFilePickerAsync(pickerOptions);
if (storageFiles.Count > 0)
{
Launcher.JavaExecutableLocation = storageFiles[0].Path.AbsolutePath;
}
}
private void OnMainViewLoaded(MainViewLoadedMessage _)
@ -103,22 +113,5 @@ namespace Saradomin.ViewModel.Controls
{
_settingsService.SaveAll();
}
private async Task BrowseForJavaExecutable()
{
var ofd = new OpenFileDialog
{
Title = "Browse for Java...",
AllowMultiple = false,
Directory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
};
var paths = await ofd.ShowAsync(Application.Current.GetMainWindow());
if (paths != null && paths.Length > 0)
{
Launcher.JavaExecutableLocation = paths[0];
}
}
}
}

View file

@ -15,226 +15,226 @@ using Avalonia.Media;
using Avalonia.Threading;
using Glitonea.Mvvm;
using Glitonea.Mvvm.Messaging;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
using Saradomin.Infrastructure.Services;
using Saradomin.Model.Settings.Launcher;
using Saradomin.Utilities;
using Saradomin.View.Windows;
using static Saradomin.Utilities.SingleplayerManagement;
namespace Saradomin.ViewModel.Controls;
public class SingleplayerViewModel : ViewModelBase
namespace Saradomin.ViewModel.Controls
{
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)
public class SingleplayerViewModel : ViewModelBase
{
_settingsService = settingsService;
_javaUpdateService = javaUpdateService;
_singleplayerUpdateService = iSingleplayerUpdateService;
_singleplayerUpdateService.SingleplayerDownloadProgressChanged += OnSingleplayerDownloadProgressChanged;
private readonly ISingleplayerUpdateService _singleplayerUpdateService;
private readonly ISettingsService _settingsService;
private readonly IJavaUpdateService _javaUpdateService;
SingleplayerLogsTextBox = new TextBox
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)
{
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,
};
_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);
}
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)
private void OnClientClosed(ClientClosedMessage _)
{
SingleplayerDownloadText = "Update Singleplayer";
ApplyLatestBackup(PrintLog);
PrintLog($"Singleplayer Download complete");
PrintLog($"");
if (_serverProcess == null) return;
_serverProcess.Kill();
_serverProcess = null;
CanLaunch = true;
return;
_settingsService.Client.GameServerAddress = _oldServerAddress;
_settingsService.Client.ManagementServerAddress = _oldManagementAddress;
}
if (progress >= 1f)
private void OnSingleplayerDownloadProgressChanged(object sender, Tuple<float, bool> e)
{
SingleplayerDownloadText = "Extracting...";
return;
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}%";
}
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();
}
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($"");
}
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());
}
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*$");
}
[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);
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);
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;
if (IsServerTerminationLog(message)) CanLaunch = true;
}
_serverProcess = CrossPlatform.StartJavaProcess(javaExecutableLocation, serverJar, "2G", log, null);
while (!IsPortInUse(43595)) Thread.Sleep(1000);
Message.Broadcast<ClientLaunchRequestedMessage>();
}
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
private static bool IsPortInUse(int port)
{
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)
var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
var tcpConnInfoArray = ipGlobalProperties.GetActiveTcpListeners();
return tcpConnInfoArray.Any(endpoint => endpoint.Port == port);
}
private void LaunchFaq()
{
PrintLog($"Couldn't reset game properties");
PrintLog(ex.Message);
return;
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("");
}
Cheats = GEAutoBuySell = Debug = false; // Force update UI
FakePlayers = true;
PrintLog("Saved new config.");
PrintLog("");
}
}

View file

@ -5,11 +5,12 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Metadata;
using Glitonea.Mvvm;
using Glitonea.Mvvm.Messaging;
using HtmlAgilityPack;
using Saradomin.Infrastructure.Messaging;
using Saradomin.Infrastructure;
using Saradomin.Infrastructure.Services;
using Saradomin.Model.Settings.Launcher;
using Saradomin.Utilities;
@ -33,7 +34,7 @@ namespace Saradomin.ViewModel.Windows
public string LaunchText { get; private set; } = "Play!";
public bool DimContent { get; private set; }
public StackPanel ContentContainer { get; private set; }
public InlineCollection HtmlInlines { get; private set; }
public MainWindowViewModel(IClientLaunchService launchService,
IClientUpdateService updateService,
@ -51,11 +52,6 @@ namespace Saradomin.ViewModel.Windows
_settingsService = settingsService;
Launcher = _settingsService.Launcher;
ContentContainer = new StackPanel
{
Margin = new(4)
};
Message.Subscribe<MainViewLoadedMessage>(this, MainViewLoaded);
Message.Subscribe<NotificationBoxStateChangedMessage>(this, NotificatationBoxStateChanged);
Message.Subscribe<ClientLaunchRequestedMessage>(this, ClientLaunchRequested);
@ -94,9 +90,8 @@ namespace Saradomin.ViewModel.Windows
node = doc.DocumentNode;
}
ContentContainer.MaxWidth = 760;
var renderer = new HtmlRenderer(ContentContainer, node);
renderer.RenderToContainer();
var renderer = new HtmlRenderer(node);
HtmlInlines = renderer.Render();
}
}
@ -105,7 +100,7 @@ namespace Saradomin.ViewModel.Windows
DimContent = msg.WasOpened;
}
public void LaunchPage(string parameter)
public void LaunchPage(object parameter)
{
var url = parameter switch
{
@ -121,7 +116,7 @@ namespace Saradomin.ViewModel.Windows
}
[DependsOn(nameof(CanLaunch))]
public bool CanExecuteLaunchSequence(object param)
public bool CanExecuteLaunchSequence(object parameter)
=> CanLaunch;
//Stub to maintain compatibility with AXAML

View file

@ -1,295 +0,0 @@
namespace UnitTests.Saradomin.Utilities.SingleplayerManagement;
[TestFixture]
public class BackupManagerTests
{
[Test]
public void WithValidDirectories_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230101_120000",
"/backups/20230102_120000",
"/backups/20230103_120000"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230103_120000".Equals(mostRecent));
}
[Test]
public void WithDifferentYears_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20210101_120000",
"/backups/20220101_120000",
"/backups/20230101_120000"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230101_120000".Equals(mostRecent));
}
[Test]
public void WithDifferentMonths_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230101_120000",
"/backups/20230201_120000",
"/backups/20230301_120000"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230301_120000".Equals(mostRecent));
}
[Test]
public void WithDifferentDays_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230301_120000",
"/backups/20230302_120000",
"/backups/20230303_120000"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230303_120000".Equals(mostRecent));
}
[Test]
public void WithDifferentHours_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230301_110000",
"/backups/20230301_120000",
"/backups/20230301_130000"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230301_130000".Equals(mostRecent));
}
[Test]
public void WithDifferentMinutes_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230301_120100",
"/backups/20230301_120200",
"/backups/20230301_120300"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230301_120300".Equals(mostRecent));
}
[Test]
public void WithDifferentSeconds_ReturnsMostRecent()
{
// Arrange
var directories = new List<string>
{
"/backups/20230301_120000",
"/backups/20230301_120015",
"/backups/20230301_120030"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230301_120030".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case1()
{
// Arrange
var directories = new List<string>
{
"/backups/20210101_235959",
"/backups/20201231_235959",
"/backups/20220228_235959"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20220228_235959".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case2()
{
// Arrange
var directories = new List<string>
{
"/backups/20211001_000001",
"/backups/20211001_000002",
"/backups/20210930_235959"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20211001_000002".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case3()
{
// Arrange
var directories = new List<string>
{
"/backups/20211231_235959",
"/backups/20220101_000000",
"/backups/20211231_235958"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20220101_000000".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case4()
{
// Arrange
var directories = new List<string>
{
"/backups/20230530_123456",
"/backups/20230529_123456",
"/backups/20220530_123457"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230530_123456".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case5()
{
// Arrange
var directories = new List<string>
{
"/backups/20230102_010101",
"/backups/20230101_222222",
"/backups/20221231_235959"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20230102_010101".Equals(mostRecent));
}
[Test]
public void WithMixedDateTimes_ReturnsMostRecent_Case6()
{
// Arrange
var directories = new List<string>
{
"/backups/20240101_120000",
"/backups/20231231_235959",
"/backups/20231231_235958"
};
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That("20240101_120000".Equals(mostRecent));
}
[Test]
public void WithNoDirectories_ReturnsNull()
{
// Arrange
// ReSharper disable once CollectionNeverUpdated.Local
var directories = new List<string>();
// Act
var mostRecent =
global::Saradomin.Utilities.SingleplayerManagement.FindMostRecentBackupDirectory(
directories
);
// Assert
Assert.That(mostRecent, Is.EqualTo(null));
}
}

View file

@ -1,20 +0,0 @@
namespace UnitTests.Saradomin.ViewModel.Controls.SingleplayerViewModel;
[TestFixture]
public class IsServerTerminationLogTests
{
[Test]
[TestCase("[23:43:26]: [SystemTermination] Server successfully terminated!", true)]
[TestCase("[23:00:26]: [SystemTermination] Server successfully terminated!", true)]
[TestCase("[23:43:26]: lol said [SystemTermination] Server successfully terminated!", false)]
[TestCase("[23:00:26]: [SystemTermination] Server successfully terminated! weee exploit", false)]
[TestCase("Gotcha [23:00:26]: [SystemTermination] Server successfully terminated!", false)]
public void ServerTerminationLogTests(string log, bool expectedOutcome)
{
// Act
var result = global::Saradomin.ViewModel.Controls.SingleplayerViewModel.IsServerTerminationLog(log);
// Assert
Assert.That(result, Is.EqualTo(expectedOutcome), $"Failed for log: {log}");
}
}

View file

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>
<PackageReference Include="coverlet.collector" Version="3.1.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Saradomin\Saradomin.csproj" />
</ItemGroup>
</Project>

View file

@ -1 +0,0 @@
global using NUnit.Framework;