mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-19 14:55:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fa01353dd | ||
|
|
4f849d880b | ||
|
|
4b60c96aad | ||
|
|
a4dc0d65cf | ||
|
|
85e017423f |
@@ -1,4 +1,5 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Configuration.FocusView;
|
||||
using Daybreak.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -41,4 +42,6 @@ public sealed class ApplicationConfiguration
|
||||
public string? ProtectedGraphAccessToken { get; set; }
|
||||
[JsonProperty("ProtectedGraphRefreshToken")]
|
||||
public string? ProtectedGraphRefreshToken { get; set; }
|
||||
[JsonProperty("FocusViewOptions")]
|
||||
public FocusViewOptions FocusViewOptions { get; set; } = new FocusViewOptions();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Daybreak.Configuration.FocusView;
|
||||
|
||||
public enum ExperienceDisplay
|
||||
{
|
||||
TotalCurretAndTotalMax,
|
||||
CurrentLevelCurrentAndCurrentLevelMax,
|
||||
RemainingUntilNextLevel,
|
||||
Percentage
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Daybreak.Configuration.FocusView;
|
||||
|
||||
public sealed class FocusViewOptions
|
||||
{
|
||||
[JsonProperty("ExperienceDisplay")]
|
||||
public ExperienceDisplay ExperienceDisplay { get; set; }
|
||||
[JsonProperty("KurzickPointsDisplay")]
|
||||
public PointsDisplay KurzickPointsDisplay { get; set; }
|
||||
[JsonProperty("LuxonPointsDisplay")]
|
||||
public PointsDisplay LuxonPointsDisplay { get; set; }
|
||||
[JsonProperty("BalthazarPointsDisplay")]
|
||||
public PointsDisplay BalthazarPointsDisplay { get; set; }
|
||||
[JsonProperty("ImperialPointsDisplay")]
|
||||
public PointsDisplay ImperialPointsDisplay { get; set; }
|
||||
[JsonProperty("VanquishingDisplay")]
|
||||
public PointsDisplay VanquishingDisplay { get; set; }
|
||||
[JsonProperty("HealthDisplay")]
|
||||
public PointsDisplay HealthDisplay { get; set; }
|
||||
[JsonProperty("EnergyDisplay")]
|
||||
public PointsDisplay EnergyDisplay { get; set; }
|
||||
[JsonProperty("BrowserUrl")]
|
||||
public string? BrowserUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Daybreak.Configuration.FocusView;
|
||||
|
||||
public enum PointsDisplay
|
||||
{
|
||||
CurrentAndMax,
|
||||
Remaining,
|
||||
Percentage
|
||||
}
|
||||
@@ -31,6 +31,7 @@ using Daybreak.Services.Navigation;
|
||||
using Daybreak.Services.Onboarding;
|
||||
using Daybreak.Services.Menu;
|
||||
using Daybreak.Services.Scanner;
|
||||
using Daybreak.Services.Experience;
|
||||
|
||||
namespace Daybreak.Configuration;
|
||||
|
||||
@@ -113,6 +114,7 @@ public static class ProjectConfiguration
|
||||
services.AddScoped<IOnboardingService, OnboardingService>();
|
||||
services.AddScoped<IGuildwarsMemoryReader, GuildwarsMemoryReader>();
|
||||
services.AddScoped<IMemoryScanner, MemoryScanner>();
|
||||
services.AddScoped<IExperienceCalculator, ExperienceCalculator>();
|
||||
}
|
||||
|
||||
public static void RegisterViews(IViewProducer viewProducer)
|
||||
@@ -120,6 +122,7 @@ public static class ProjectConfiguration
|
||||
viewProducer.ThrowIfNull();
|
||||
|
||||
viewProducer.RegisterPermanentView<LauncherView>();
|
||||
viewProducer.RegisterPermanentView<Views.FocusView>();
|
||||
viewProducer.RegisterView<SettingsView>();
|
||||
viewProducer.RegisterView<AskUpdateView>();
|
||||
viewProducer.RegisterView<UpdateView>();
|
||||
@@ -136,7 +139,6 @@ public static class ProjectConfiguration
|
||||
viewProducer.RegisterView<GraphAuthorizationView>();
|
||||
viewProducer.RegisterView<BuildsSynchronizationView>();
|
||||
viewProducer.RegisterView<OnboardingView>();
|
||||
viewProducer.RegisterView<FocusView>();
|
||||
}
|
||||
|
||||
public static void RegisterPostUpdateActions(IPostUpdateActionProducer postUpdateActionProducer)
|
||||
|
||||
@@ -20,13 +20,12 @@
|
||||
FontStretch="{Binding ElementName=_this, Path=FontStretch, Mode=OneWay}"
|
||||
FontWeight="{Binding ElementName=_this, Path=FontWeight, Mode=OneWay}"
|
||||
FontStyle="{Binding ElementName=_this, Path=FontStyle, Mode=OneWay}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"></TextBlock>
|
||||
VerticalAlignment="{Binding ElementName=_this, Path=TextVerticalAlignment, Mode=OneWay}"
|
||||
HorizontalAlignment="{Binding ElementName=_this, Path=TextHorizontalAlignment, Mode=OneWay}"></TextBlock>
|
||||
<Rectangle Fill="{Binding ElementName=_this, Path=Highlight, Mode=OneWay}"
|
||||
Opacity="{Binding ElementName=_this, Path=HighlightOpacity, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=HighlightVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></Rectangle>
|
||||
<Rectangle MouseEnter="Rectangle_MouseEnter" MouseLeave="Rectangle_MouseLeave"
|
||||
MouseLeftButtonDown="Rectangle_MouseLeftButtonDown" Fill="Transparent"
|
||||
Cursor="Hand"></Rectangle>
|
||||
MouseLeftButtonDown="Rectangle_MouseLeftButtonDown" Fill="Transparent"></Rectangle>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -26,6 +26,10 @@ public partial class OpaqueButton : UserControl
|
||||
public double highlightOpacity;
|
||||
[GenerateDependencyProperty]
|
||||
public bool highlightVisible;
|
||||
[GenerateDependencyProperty(InitialValue = VerticalAlignment.Center)]
|
||||
public VerticalAlignment textVerticalAlignment;
|
||||
[GenerateDependencyProperty(InitialValue = HorizontalAlignment.Center)]
|
||||
public HorizontalAlignment textHorizontalAlignment;
|
||||
|
||||
public OpaqueButton()
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<ContextMenu.Template>
|
||||
<ControlTemplate>
|
||||
<StackPanel Margin="10">
|
||||
<local:OpaqueButton Text="Load build template" Foreground="White" Background="#F0202020" BackgroundOpacity="0.4"
|
||||
<local:OpaqueButton Text="Load build template" Foreground="White" Background="#F0212121" BackgroundOpacity="0.4"
|
||||
Clicked="LoadBuildTemplateButton_Click" FontSize="16" Height="40" Width="200"></local:OpaqueButton>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
@@ -33,7 +33,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
<wv2:WebView2 x:Name="WebBrowser" Source="{Binding ElementName=_this, Path=Address, Mode=TwoWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></wv2:WebView2>
|
||||
<Grid Grid.Row="1" Background="#80808080" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
<Grid Grid.Row="1" Background="#F0212121" IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=ControlsEnabled, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
@@ -69,17 +69,19 @@
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"></TextBox>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<local:HomeButton Width="30" Height="30" Margin="5"
|
||||
Visibility="{Binding ElementName=_this, Path=HomeButtonVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="HomeButton_Clicked"></local:HomeButton>
|
||||
<local:StarGlyph Height="30" Width="30" Margin="5"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="StarGlyph_Clicked" x:Name="FavoriteButton"></local:StarGlyph>
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=HomeButtonVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="StarGlyph_Clicked" x:Name="FavoriteButton"></local:StarGlyph>
|
||||
<local:MaximizeButton Height="30" Width="30" Margin="5"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="MaximizeButton_Clicked"></local:MaximizeButton>
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Clicked="MaximizeButton_Clicked"></local:MaximizeButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.RowSpan="2" Background="Gray"
|
||||
|
||||
@@ -57,6 +57,11 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
private string favoriteAddress = string.Empty;
|
||||
[GenerateDependencyProperty(InitialValue = false)]
|
||||
private bool preventDispose;
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool homeButtonVisible = true;
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool favoriteButtonVisible = true;
|
||||
|
||||
public string Address
|
||||
{
|
||||
get => this.GetTypedValue<string>(AddressProperty);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="Daybreak.Controls.HorizontalResourceBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Viewbox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stretch="Fill">
|
||||
<Grid Height="1">
|
||||
<Rectangle
|
||||
Fill="{Binding ElementName=_this, Path=BarColor, Mode=OneWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="{Binding ElementName=_this, Path=FillAlignment, Mode=OneWay}"
|
||||
Width="{Binding ElementName=_this, Path=MaxResourceValue, Mode=TwoWay}"></Rectangle>
|
||||
<Rectangle
|
||||
Fill="#F0202020"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"></Rectangle>
|
||||
<Rectangle
|
||||
Fill="{Binding ElementName=_this, Path=BarColor, Mode=OneWay}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="{Binding ElementName=_this, Path=FillAlignment, Mode=OneWay}"
|
||||
Width="{Binding ElementName=_this, Path=CurrentResourceValue, Mode=TwoWay}"></Rectangle>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
<TextBlock
|
||||
FontSize="{Binding ElementName=_this, Path=FontSize, Mode=OneWay}"
|
||||
FontFamily="{Binding ElementName=_this, Path=FontFamily, Mode=OneWay}"
|
||||
Text="{Binding ElementName=_this, Path=Text, Mode=OneWay}"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"></TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Daybreak.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ResourceBar.xaml
|
||||
/// </summary>
|
||||
public partial class HorizontalResourceBar : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
private double currentResourceValue;
|
||||
[GenerateDependencyProperty]
|
||||
private double maxResourceValue;
|
||||
[GenerateDependencyProperty]
|
||||
private Brush barColor;
|
||||
[GenerateDependencyProperty]
|
||||
private string text;
|
||||
[GenerateDependencyProperty(InitialValue = HorizontalAlignment.Left)]
|
||||
private HorizontalAlignment fillAlignment;
|
||||
|
||||
public HorizontalResourceBar()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.MaxResourceValue = 1;
|
||||
this.CurrentResourceValue = 0;
|
||||
this.barColor = Brushes.Transparent;
|
||||
this.text = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="GameCompanionButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:GoldenArrowGlyph Foreground="White"
|
||||
@@ -22,6 +23,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="ManageBuildsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:FireballGlyph Foreground="White"
|
||||
@@ -32,6 +34,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="AccountSettingsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:AvatarGlyph Foreground="White"
|
||||
@@ -42,6 +45,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="GuildwarsSettingsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:FileGlyph Foreground="White"
|
||||
@@ -52,6 +56,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="LauncherSettingsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:CogGlyph Foreground="White"
|
||||
@@ -63,6 +68,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="ExperimentalSettingsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:ExperimentGlyph Foreground="White"
|
||||
@@ -73,6 +79,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="VersionManagementButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:StaticGlyph Foreground="White"
|
||||
@@ -83,6 +90,7 @@
|
||||
Foreground="White"
|
||||
HighlightColor="White"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="LogsButton_Clicked">
|
||||
<buttons:MenuButton.InnerContent>
|
||||
<local:LogsGlyph Foreground="White"
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
x:Name="_this"
|
||||
Unloaded="BuildTemplate_Unloaded"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="Transparent" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
@@ -30,20 +33,15 @@
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Primary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
|
||||
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
|
||||
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
|
||||
SelectedItem="{Binding ElementName=_this, Path=PrimaryProfession, Mode=TwoWay}" Height="30"
|
||||
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ListView>
|
||||
<local:OpaqueButton Grid.Column="2"
|
||||
Text="{Binding ElementName=_this, Path=PrimaryProfession.Name, Mode=OneWay}"
|
||||
Foreground="White"
|
||||
Highlight="White"
|
||||
HighlightOpacity="0.4"
|
||||
FontSize="16"
|
||||
TextHorizontalAlignment="Left"
|
||||
Margin="0, 0, 10, 0"
|
||||
Clicked="PrimaryProfessionButton_Clicked"></local:OpaqueButton>
|
||||
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
|
||||
Clicked="HelpButtonPrimary_Clicked" Cursor="Hand"></local:HelpButton>
|
||||
</Grid>
|
||||
@@ -54,20 +52,15 @@
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Secondary Profession: " FontSize="16" Foreground="White" Grid.Column="1"></TextBlock>
|
||||
<ListView FontSize="16" Foreground="White" Grid.Column="2" BorderThickness="0"
|
||||
Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
|
||||
SelectedItem="{Binding ElementName=_this, Path=SecondaryProfession, Mode=TwoWay}" Height="30"
|
||||
SelectionMode="Single" ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
PreviewMouseWheel="ListView_NavigateWithMouseWheel">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name, Mode=OneWay}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<behaviors:ScrollIntoView></behaviors:ScrollIntoView>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ListView>
|
||||
<local:OpaqueButton Grid.Column="2"
|
||||
Text="{Binding ElementName=_this, Path=SecondaryProfession.Name, Mode=OneWay}"
|
||||
Foreground="White"
|
||||
Highlight="White"
|
||||
HighlightOpacity="0.4"
|
||||
FontSize="16"
|
||||
TextHorizontalAlignment="Left"
|
||||
Margin="0, 0, 10, 0"
|
||||
Clicked="SecondaryProfessionButton_Clicked"></local:OpaqueButton>
|
||||
<local:HelpButton Grid.Column="0" Foreground="White" Width="20" Height="20" Margin="3"
|
||||
Clicked="HelpButtonSecondary_Clicked" Cursor="Hand"></local:HelpButton>
|
||||
</Grid>
|
||||
@@ -170,10 +163,11 @@
|
||||
<ListView x:Name="SkillsListView"
|
||||
Grid.Row="1"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Background="Transparent"
|
||||
ScrollViewer.CanContentScroll="False"
|
||||
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}"
|
||||
MouseDoubleClick="ListView_MouseDoubleClick">
|
||||
MouseDoubleClick="SkillListView_MouseDoubleClick">
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel>
|
||||
@@ -188,5 +182,28 @@
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" Grid.RowSpan="6">
|
||||
<ListView x:Name="ProfessionListView"
|
||||
Width="0"
|
||||
HorizontalAlignment="Right"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Background="Transparent"
|
||||
ScrollViewer.CanContentScroll="False"
|
||||
ItemsSource="{Binding ElementName=_this, Path=Professions, Mode=OneWay}"
|
||||
MouseDoubleClick="ProfessionListView_MouseDoubleClick">
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel>
|
||||
</VirtualizingStackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -30,11 +30,15 @@ public partial class BuildTemplate : UserControl
|
||||
private const string InfoNamePlaceholder = "[NAME]";
|
||||
private const string BaseAddress = $"https://wiki.guildwars.com/wiki/{InfoNamePlaceholder}";
|
||||
|
||||
private bool showingSkillList = false;
|
||||
private bool replacingSecondaryProfession;
|
||||
private bool replacingPrimaryProfession;
|
||||
private bool suppressBuildChanged = false;
|
||||
private bool loadedProperties = false;
|
||||
private IIconBrowser? iconBrowser;
|
||||
private BuildEntry? loadedBuild;
|
||||
private SkillTemplate? selectingSkillTemplate;
|
||||
private List<Skill>? skillListCache;
|
||||
private CancellationTokenSource? cancellationTokenSource = new();
|
||||
|
||||
public event EventHandler? BuildChanged;
|
||||
@@ -269,7 +273,7 @@ public partial class BuildTemplate : UserControl
|
||||
}
|
||||
|
||||
var filteredSkills = await this.FilterSkills(this.SkillSearchText).ToListAsync().ConfigureAwait(true);
|
||||
this.AvailableSkills.ClearAnd().AddRange(filteredSkills);
|
||||
this.PrepareSkillListCache(filteredSkills);
|
||||
}
|
||||
|
||||
private void LoadBuild()
|
||||
@@ -301,8 +305,9 @@ public partial class BuildTemplate : UserControl
|
||||
{
|
||||
if (this.SkillBrowser.BrowserSupported is true)
|
||||
{
|
||||
this.HideSkillListView();
|
||||
this.HideProfessionListView();
|
||||
this.SkillBrowser.Width = 400;
|
||||
this.SkillListContainer.Width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,13 +321,87 @@ public partial class BuildTemplate : UserControl
|
||||
|
||||
private void ShowSkillListView()
|
||||
{
|
||||
this.SkillBrowser.Width = 0;
|
||||
this.HideInfoBrowser();
|
||||
this.HideProfessionListView();
|
||||
this.SkillListContainer.Width = 400;
|
||||
this.SkillListContainer.Visibility = Visibility.Visible;
|
||||
this.showingSkillList = true;
|
||||
if (this.skillListCache?.Except(this.AvailableSkills).None() is true &&
|
||||
this.skillListCache.Count == this.AvailableSkills.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.AvailableSkills.ClearAnd().AddRange(this.skillListCache);
|
||||
}
|
||||
|
||||
private void HideSkillListView()
|
||||
{
|
||||
this.SkillListContainer.Visibility = Visibility.Hidden;
|
||||
this.SkillListContainer.Width = 0;
|
||||
this.showingSkillList = false;
|
||||
}
|
||||
|
||||
private void ShowProfessionListView()
|
||||
{
|
||||
this.HideInfoBrowser();
|
||||
this.HideSkillListView();
|
||||
this.ProfessionListView.Width = 400;
|
||||
this.ProfessionListView.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideProfessionListView()
|
||||
{
|
||||
this.ProfessionListView.Visibility = Visibility.Hidden;
|
||||
this.ProfessionListView.Width = 0;
|
||||
}
|
||||
|
||||
private void PrepareSkillListCache(List<Skill> skills)
|
||||
{
|
||||
/*
|
||||
* To improve application performance, only load the skill list when it is showing.
|
||||
* Otherwise, defer the loading to when the skill list will show.
|
||||
*/
|
||||
|
||||
this.skillListCache = skills;
|
||||
if (this.showingSkillList)
|
||||
{
|
||||
this.ShowSkillListView();
|
||||
}
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<Skill> FilterSkills(string searchTerm)
|
||||
{
|
||||
// Replace symbols to ease search
|
||||
searchTerm = searchTerm?.Replace("\"", "").Replace("!", "")!;
|
||||
|
||||
foreach (var skill in Skill.Skills)
|
||||
{
|
||||
if (skill == Skill.NoSkill)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (skill.Profession != this.PrimaryProfession &&
|
||||
skill.Profession != this.SecondaryProfession &&
|
||||
skill.Profession != Profession.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchTerm.IsNullOrWhiteSpace())
|
||||
{
|
||||
yield return skill;
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesName = await Task.Run(() => StringUtils.MatchesSearchString(skill.Name!.Replace("\"", "").Replace("!", ""), searchTerm!));
|
||||
if (matchesName)
|
||||
{
|
||||
yield return skill;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HelpButtonPrimary_Clicked(object sender, System.EventArgs e)
|
||||
@@ -392,7 +471,7 @@ public partial class BuildTemplate : UserControl
|
||||
sender.As<SkillTemplate>().DataContext = Skill.NoSkill;
|
||||
}
|
||||
|
||||
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
private void SkillListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (this.selectingSkillTemplate is null)
|
||||
{
|
||||
@@ -412,6 +491,21 @@ public partial class BuildTemplate : UserControl
|
||||
this.loadedBuild!.Build!.Skills[7] = this.Skill7;
|
||||
}
|
||||
|
||||
private void ProfessionListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
var selected = sender.As<ListView>().SelectedItem.As<Profession>();
|
||||
if (this.replacingPrimaryProfession)
|
||||
{
|
||||
this.PrimaryProfession = selected;
|
||||
}
|
||||
else if (this.replacingSecondaryProfession)
|
||||
{
|
||||
this.SecondaryProfession = selected;
|
||||
}
|
||||
|
||||
this.HideProfessionListView();
|
||||
}
|
||||
|
||||
private void ListView_NavigateWithMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
@@ -440,37 +534,17 @@ public partial class BuildTemplate : UserControl
|
||||
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<Skill> FilterSkills(string searchTerm)
|
||||
private void SecondaryProfessionButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
// Replace symbols to ease search
|
||||
searchTerm = searchTerm?.Replace("\"", "").Replace("!", "")!;
|
||||
|
||||
foreach (var skill in Skill.Skills)
|
||||
{
|
||||
if (skill == Skill.NoSkill)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
this.replacingPrimaryProfession = false;
|
||||
this.replacingSecondaryProfession = true;
|
||||
this.ShowProfessionListView();
|
||||
}
|
||||
|
||||
if (skill.Profession != this.PrimaryProfession &&
|
||||
skill.Profession != this.SecondaryProfession &&
|
||||
skill.Profession != Profession.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchTerm.IsNullOrWhiteSpace())
|
||||
{
|
||||
yield return skill;
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchesName = await Task.Run(() => StringUtils.MatchesSearchString(skill.Name!.Replace("\"", "").Replace("!", ""), searchTerm!));
|
||||
if (matchesName)
|
||||
{
|
||||
yield return skill;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
private void PrimaryProfessionButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.replacingPrimaryProfession = true;
|
||||
this.replacingSecondaryProfession = false;
|
||||
this.ShowProfessionListView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Templates.QuestLogTemplate"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Templates"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<StackPanel x:Name="ItemStackPanel">
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,170 @@
|
||||
using Daybreak.Models.Guildwars;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Daybreak.Controls.Templates;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for QuestLogTemplate.xaml
|
||||
/// </summary>
|
||||
public partial class QuestLogTemplate : UserControl
|
||||
{
|
||||
private const string UncategorizedQuestsString = "Uncategorized Quests";
|
||||
private List<IGrouping<Map?, QuestMetadata>>? questLogCache;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private List<QuestMetadata> quests;
|
||||
|
||||
public event EventHandler<Map?>? MapClicked;
|
||||
public event EventHandler<Quest?>? QuestClicked;
|
||||
|
||||
public QuestLogTemplate()
|
||||
{
|
||||
this.quests = new List<QuestMetadata>();
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
|
||||
if (e.Property == QuestsProperty)
|
||||
{
|
||||
this.DrawQuestLogLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawQuestLogLayout()
|
||||
{
|
||||
var questLog = this.Quests?.GroupBy(q => q.From).OrderBy(g => g.Key?.Name).ToList();
|
||||
if (!this.DetectQuestLogChange(questLog))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.questLogCache = questLog;
|
||||
if (this.questLogCache is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.ItemStackPanel.Children.Clear();
|
||||
|
||||
foreach(var grouping in this.questLogCache)
|
||||
{
|
||||
var location = grouping.Key;
|
||||
var locationTextBlock = new OpaqueButton
|
||||
{
|
||||
Text = grouping.Key?.Name ?? UncategorizedQuestsString,
|
||||
FontSize = 18,
|
||||
Cursor = grouping.Key is null ? Cursors.Arrow : Cursors.Hand,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
TextHorizontalAlignment = HorizontalAlignment.Left,
|
||||
Highlight = Brushes.White,
|
||||
HighlightOpacity = grouping.Key is null ? 0 : 0.6
|
||||
};
|
||||
|
||||
locationTextBlock.MouseLeftButtonDown += (_, _) => this.OnMapClicked(location);
|
||||
this.ItemStackPanel.Children.Add(locationTextBlock);
|
||||
var rectangle = new Rectangle
|
||||
{
|
||||
Height = 1,
|
||||
};
|
||||
|
||||
var rectangleForegroundBinding = new Binding("Foreground")
|
||||
{
|
||||
Source = this
|
||||
};
|
||||
|
||||
rectangle.SetBinding(Rectangle.FillProperty, rectangleForegroundBinding);
|
||||
this.ItemStackPanel.Children.Add(rectangle);
|
||||
foreach (var questMetadata in grouping)
|
||||
{
|
||||
var quest = questMetadata.Quest;
|
||||
var questTextBlock = new OpaqueButton
|
||||
{
|
||||
Text = quest?.Name,
|
||||
FontSize = 20,
|
||||
Cursor = Cursors.Hand,
|
||||
Highlight = Brushes.White,
|
||||
HighlightOpacity = 0.6,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
TextHorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
questTextBlock.MouseLeftButtonDown += (_, _) => this.OnQuestClicked(quest);
|
||||
this.ItemStackPanel.Children.Add(questTextBlock);
|
||||
}
|
||||
|
||||
this.ItemStackPanel.Children.Add(new Rectangle
|
||||
{
|
||||
Fill = Brushes.Transparent,
|
||||
Height = 10,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool DetectQuestLogChange(List<IGrouping<Map?, QuestMetadata>>? newQuestLog)
|
||||
{
|
||||
if (this.questLogCache is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newQuestLog is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.questLogCache.Count != newQuestLog.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.questLogCache.Count; i++)
|
||||
{
|
||||
var grouping1 = this.questLogCache[i];
|
||||
var grouping2 = newQuestLog[i];
|
||||
if (grouping1.Key?.Id != grouping2.Key?.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var cachedQuests = grouping1.ToList();
|
||||
var newQuests = grouping2.ToList();
|
||||
if (cachedQuests.Count != newQuests.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var j = 0; j < cachedQuests.Count; j++)
|
||||
{
|
||||
if (cachedQuests[j].Quest?.Id != newQuests[j].Quest?.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnMapClicked(Map? map)
|
||||
{
|
||||
this.MapClicked?.Invoke(this, map);
|
||||
}
|
||||
|
||||
private void OnQuestClicked(Quest? quest)
|
||||
{
|
||||
this.QuestClicked?.Invoke(this, quest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="Daybreak.Controls.VerticalResourceBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Viewbox VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stretch="Fill">
|
||||
<Grid Width="1">
|
||||
<Rectangle
|
||||
Fill="{Binding ElementName=_this, Path=BarColor, Mode=OneWay}"
|
||||
VerticalAlignment="{Binding ElementName=_this, Path=FillAlignment, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Height="{Binding ElementName=_this, Path=MaxResourceValue, Mode=TwoWay}"></Rectangle>
|
||||
<Rectangle
|
||||
Fill="#F0212121"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"></Rectangle>
|
||||
<Rectangle
|
||||
Fill="{Binding ElementName=_this, Path=BarColor, Mode=OneWay}"
|
||||
VerticalAlignment="{Binding ElementName=_this, Path=FillAlignment, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Height="{Binding ElementName=_this, Path=CurrentResourceValue, Mode=TwoWay}"></Rectangle>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
<TextBlock
|
||||
FontSize="{Binding ElementName=_this, Path=FontSize, Mode=OneWay}"
|
||||
FontFamily="{Binding ElementName=_this, Path=FontFamily, Mode=OneWay}"
|
||||
Text="{Binding ElementName=_this, Path=Text, Mode=OneWay}"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"></TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Daybreak.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for ResourceBar.xaml
|
||||
/// </summary>
|
||||
public partial class VerticalResourceBar : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
private double currentResourceValue;
|
||||
[GenerateDependencyProperty]
|
||||
private double maxResourceValue;
|
||||
[GenerateDependencyProperty]
|
||||
private Brush barColor;
|
||||
[GenerateDependencyProperty]
|
||||
private string text;
|
||||
[GenerateDependencyProperty(InitialValue = VerticalAlignment.Bottom)]
|
||||
private VerticalAlignment fillAlignment;
|
||||
|
||||
public VerticalResourceBar()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.MaxResourceValue = 1;
|
||||
this.CurrentResourceValue = 0;
|
||||
this.barColor = Brushes.Transparent;
|
||||
this.text = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
|
||||
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
|
||||
<Version>0.9.8</Version>
|
||||
<Version>0.9.8.5</Version>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
@@ -72,6 +72,9 @@
|
||||
<Compile Update="Controls\Buttons\CancelButton.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Controls\VerticalResourceBar.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\IconDownloadView.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid>
|
||||
<Grid Cursor="Hand">
|
||||
<Rectangle x:Name="OverlayRect" Fill="{TemplateBinding Background}" Opacity="0"></Rectangle>
|
||||
<controls:ThreeDotsGlyph Margin="13"></controls:ThreeDotsGlyph>
|
||||
</Grid>
|
||||
@@ -33,7 +33,7 @@
|
||||
<Condition Property="IsMouseOver" Value="True"></Condition>
|
||||
</MultiTrigger.Conditions>
|
||||
<MultiTrigger.Setters>
|
||||
<Setter Property="Opacity" TargetName="OverlayRect" Value="0.5"></Setter>
|
||||
<Setter Property="Opacity" TargetName="OverlayRect" Value="1"></Setter>
|
||||
</MultiTrigger.Setters>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
@@ -118,7 +118,7 @@
|
||||
Grid.RowSpan="2"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#A0202020">
|
||||
Background="#F0212121">
|
||||
<controls:MenuList Margin="0, 30, 0, 0"/>
|
||||
</Grid>
|
||||
<ToggleButton x:Name="OpeningSettingsButton"
|
||||
@@ -126,6 +126,7 @@
|
||||
Width="50"
|
||||
Height="30"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#F0212121"
|
||||
Margin="0, 0, 0, 0"
|
||||
Grid.ColumnSpan="2"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
@@ -138,6 +139,7 @@
|
||||
Margin="0, 0, 0, 0"
|
||||
Grid.ColumnSpan="2"
|
||||
Foreground="White"
|
||||
Background="#F09E9E9E"
|
||||
Visibility="{Binding ElementName=_this, Path=IsShowingDropdown, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Click="SettingsButton_Clicked"></ToggleButton>
|
||||
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Grid.ColumnSpan="2" Active="True"></wcl:Border>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct GameContext
|
||||
{
|
||||
[FieldOffset(0x002C)]
|
||||
public readonly IntPtr WorldContext;
|
||||
|
||||
[FieldOffset(0x0044)]
|
||||
public readonly IntPtr CharContext;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Daybreak.Models.Guildwars;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Daybreak.Models;
|
||||
|
||||
public sealed class GameData
|
||||
{
|
||||
public MainPlayerInformation? MainPlayer { get; init; }
|
||||
public List<PlayerInformation>? Party { get; init; }
|
||||
public UserInformation? User { get; init; }
|
||||
public SessionInformation? Session { get; init; }
|
||||
public List<WorldPlayerInformation>? WorldPlayers { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public sealed class MainPlayerInformation : WorldPlayerInformation
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public bool HardModeUnlocked { get; init; }
|
||||
public uint Experience { get; init; }
|
||||
public uint Level { get; init; }
|
||||
public uint Morale { get; init; }
|
||||
public Quest? Quest { get; init; }
|
||||
public List<QuestMetadata>? QuestLog { get; init; }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public class PlayerInformation
|
||||
{
|
||||
public Profession? PrimaryProfession { get; init; }
|
||||
public Profession? SecondaryProfession { get; init; }
|
||||
public List<Profession>? UnlockedProfession { get; init; }
|
||||
public float CurrentHealth { get; init; }
|
||||
public float MaxHealth { get; init; }
|
||||
public float CurrentEnergy { get; init; }
|
||||
public float MaxEnergy { get; init; }
|
||||
public float HealthRegen { get; init; }
|
||||
public float EnergyRegen { get; init; }
|
||||
}
|
||||
@@ -1161,8 +1161,12 @@ public sealed class Quest
|
||||
public static readonly Quest OperationCrushSpirits = new() { Id = 1179, Name = "Operation Crush Spirits", WikiUrl = "https://wiki.guildwars.com/wiki/Operation:_Crush_Spirits" };
|
||||
public static readonly Quest FightinginaWinterWonderland = new() { Id = 1180, Name = "Fighting in a Winter Wonderland", WikiUrl = "https://wiki.guildwars.com/wiki/Fighting_in_a_Winter_Wonderland" };
|
||||
public static readonly Quest VanguardBountyBlazefiendGriefblade = new() { Id = 1182, Name = "Vanguard Bounty Blazefiend Griefblade", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Bounty:_Blazefiend_Griefblade" };
|
||||
public static readonly Quest VanguardBountyCountessNadya = new() { Id = 1183, Name = "Vanguard Bounty Countess Nadya", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Bounty:_Countess_Nadya" };
|
||||
public static readonly Quest VanguardBountyUtiniWupwup = new() { Id = 1184, Name = "Vanguard Bounty Utini Wupwup", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Bounty:_Utini_Wupwup" };
|
||||
public static readonly Quest VanguardRescueFarmerHamnet = new() { Id = 1185, Name = "Vanguard Rescue Farmer Hamnet", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Rescue:_Farmer_Hamnet" };
|
||||
public static readonly Quest VanguardRescueFootmanTate = new() { Id = 1186, Name = "Vanguard Rescue Footman Tate", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Rescue:_Footman_Tate" };
|
||||
public static readonly Quest VanguardRescueSavetheAscalonianNoble = new() { Id = 1187, Name = "Vanguard Rescue Save the Ascalonian Noble", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Rescue:_Save_the_Ascalonian_Noble" };
|
||||
public static readonly Quest VanguardAnnihilationCharr = new() { Id = 1188, Name = "Vanguard Annihilation Charr", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Annihilation:_Charr" };
|
||||
public static readonly Quest VanguardAnnihilationBandits = new() { Id = 1189, Name = "Vanguard Annihilation Bandits", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Annihilation:_Bandits" };
|
||||
public static readonly Quest VanguardAnnihilationUndead = new() { Id = 1190, Name = "Vanguard Annihilation Undead", WikiUrl = "https://wiki.guildwars.com/wiki/Vanguard_Annihilation:_Undead" };
|
||||
public static readonly Quest AnvilRockZaishenVanquish = new() { Id = 1191, Name = "Anvil Rock (Zaishen vanquish)", WikiUrl = "https://wiki.guildwars.com/wiki/Anvil_Rock_(Zaishen_vanquish)" };
|
||||
@@ -2558,10 +2562,14 @@ public sealed class Quest
|
||||
OperationCrushSpirits,
|
||||
FightinginaWinterWonderland,
|
||||
VanguardBountyBlazefiendGriefblade,
|
||||
VanguardBountyCountessNadya,
|
||||
VanguardBountyUtiniWupwup,
|
||||
VanguardRescueSavetheAscalonianNoble,
|
||||
VanguardRescueFarmerHamnet,
|
||||
VanguardRescueFootmanTate,
|
||||
VanguardAnnihilationBandits,
|
||||
VanguardAnnihilationUndead,
|
||||
VanguardAnnihilationCharr,
|
||||
AnvilRockZaishenVanquish,
|
||||
ArborstoneZaishenVanquish,
|
||||
WitmansFollyZaishenVanquish,
|
||||
@@ -2841,4 +2849,8 @@ public sealed class Quest
|
||||
|
||||
return quest;
|
||||
}
|
||||
|
||||
private Quest()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public sealed class QuestMetadata
|
||||
{
|
||||
public Quest? Quest { get; init; }
|
||||
public Map? From { get; init; }
|
||||
public Map? To { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public sealed class SessionInformation
|
||||
{
|
||||
public uint FoesKilled { get; init; }
|
||||
public uint FoesToKill { get; init; }
|
||||
}
|
||||
@@ -1,22 +1,14 @@
|
||||
using Daybreak.Models.Guildwars;
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
namespace Daybreak.Models;
|
||||
|
||||
public sealed class GameData
|
||||
public sealed class UserInformation
|
||||
{
|
||||
public string? Email { get; init; }
|
||||
public string? CharacterName { get; init; }
|
||||
public Quest? Quest { get; init; }
|
||||
public bool HardModeUnlocked { get; init; }
|
||||
public uint Experience { get; init; }
|
||||
public uint CurrentKurzickPoints { get; init; }
|
||||
public uint TotalKurzickPoints { get; init; }
|
||||
public uint CurrentLuxonPoints { get; init; }
|
||||
public uint TotalLuxonPoints { get; init; }
|
||||
public uint CurrentImperialPoints { get; init; }
|
||||
public uint TotalImperialPoints { get; init; }
|
||||
public uint Level { get; init; }
|
||||
public uint Morale { get; init; }
|
||||
public uint CurrentBalthazarPoints { get; init; }
|
||||
public uint TotalBalthazarPoints { get; init; }
|
||||
public uint CurrentSkillPoints { get; init; }
|
||||
@@ -25,6 +17,4 @@ public sealed class GameData
|
||||
public uint MaxLuxonPoints { get; init; }
|
||||
public uint MaxImperialPoints { get; init; }
|
||||
public uint MaxBalthazarPoints { get; init; }
|
||||
public uint FoesKilled { get; init; }
|
||||
public uint FoesToKill { get; init; }
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct WorldContext
|
||||
{
|
||||
public const int BaseOffset = 0x0528;
|
||||
|
||||
[FieldOffset(0x0000)]
|
||||
public readonly uint QuestId;
|
||||
|
||||
[FieldOffset(0x015C)]
|
||||
public readonly uint HardModeUnlocked;
|
||||
|
||||
[FieldOffset(0x0218)]
|
||||
public readonly uint Experience;
|
||||
|
||||
[FieldOffset(0x0220)]
|
||||
public readonly uint CurrentKurzick;
|
||||
|
||||
[FieldOffset(0x0228)]
|
||||
public readonly uint TotalKurzick;
|
||||
|
||||
[FieldOffset(0x0230)]
|
||||
public readonly uint CurrentLuxon;
|
||||
|
||||
[FieldOffset(0x0238)]
|
||||
public readonly uint TotalLuxon;
|
||||
|
||||
[FieldOffset(0x0240)]
|
||||
public readonly uint CurrentImperial;
|
||||
|
||||
[FieldOffset(0x0248)]
|
||||
public readonly uint TotalImperial;
|
||||
|
||||
[FieldOffset(0x0260)]
|
||||
public readonly uint Level;
|
||||
|
||||
[FieldOffset(0x0268)]
|
||||
public readonly uint Morale;
|
||||
|
||||
[FieldOffset(0x0270)]
|
||||
public readonly uint CurrentBalthazar;
|
||||
|
||||
[FieldOffset(0x0278)]
|
||||
public readonly uint TotalBalthazar;
|
||||
|
||||
[FieldOffset(0x0280)]
|
||||
public readonly uint CurrentSkillPoints;
|
||||
|
||||
[FieldOffset(0x0288)]
|
||||
public readonly uint TotalSkillPoints;
|
||||
|
||||
[FieldOffset(0x0290)]
|
||||
public readonly uint MaxKurzick;
|
||||
|
||||
[FieldOffset(0x0294)]
|
||||
public readonly uint MaxLuxon;
|
||||
|
||||
[FieldOffset(0x0298)]
|
||||
public readonly uint MaxBalthazar;
|
||||
|
||||
[FieldOffset(0x029C)]
|
||||
public readonly uint MaxImperial;
|
||||
|
||||
[FieldOffset(0x0324)]
|
||||
public readonly uint FoesKilled;
|
||||
|
||||
[FieldOffset(0x0328)]
|
||||
public readonly uint FoesToKill;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public class WorldPlayerInformation : PlayerInformation
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct EntityContext
|
||||
{
|
||||
[FieldOffset(0x2C)]
|
||||
public readonly uint EntityId;
|
||||
|
||||
[FieldOffset(0x9C)]
|
||||
public readonly EntityType Type;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
public enum EntityType
|
||||
{
|
||||
Living = 0xDB,
|
||||
Gadget = 0x200,
|
||||
Item = 0x400
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct GameContext
|
||||
{
|
||||
public const int BaseOffset = 0x007C;
|
||||
|
||||
[FieldOffset(0x0000)]
|
||||
///Array of type <see cref="MapEntityContext"/>.
|
||||
public readonly GuildwarsArray MapEntities;
|
||||
|
||||
[FieldOffset(0x04AC)]
|
||||
public readonly uint QuestId;
|
||||
|
||||
[FieldOffset(0x04B0)]
|
||||
public readonly GuildwarsArray QuestLog;
|
||||
|
||||
[FieldOffset(0x0608)]
|
||||
public readonly uint HardModeUnlocked;
|
||||
|
||||
[FieldOffset(0x640)]
|
||||
///Array of type <see cref="ProfessionsContext"/>
|
||||
public readonly GuildwarsArray Professions;
|
||||
|
||||
[FieldOffset(0x06C4)]
|
||||
public readonly uint Experience;
|
||||
|
||||
[FieldOffset(0x06CC)]
|
||||
public readonly uint CurrentKurzick;
|
||||
|
||||
[FieldOffset(0x06D4)]
|
||||
public readonly uint TotalKurzick;
|
||||
|
||||
[FieldOffset(0x06DC)]
|
||||
public readonly uint CurrentLuxon;
|
||||
|
||||
[FieldOffset(0x06E4)]
|
||||
public readonly uint TotalLuxon;
|
||||
|
||||
[FieldOffset(0x06EC)]
|
||||
public readonly uint CurrentImperial;
|
||||
|
||||
[FieldOffset(0x06F4)]
|
||||
public readonly uint TotalImperial;
|
||||
|
||||
[FieldOffset(0x070C)]
|
||||
public readonly uint Level;
|
||||
|
||||
[FieldOffset(0x0714)]
|
||||
public readonly uint Morale;
|
||||
|
||||
[FieldOffset(0x071C)]
|
||||
public readonly uint CurrentBalthazar;
|
||||
|
||||
[FieldOffset(0x0724)]
|
||||
public readonly uint TotalBalthazar;
|
||||
|
||||
[FieldOffset(0x072C)]
|
||||
public readonly uint CurrentSkillPoints;
|
||||
|
||||
[FieldOffset(0x0734)]
|
||||
public readonly uint TotalSkillPoints;
|
||||
|
||||
[FieldOffset(0x073C)]
|
||||
public readonly uint MaxKurzick;
|
||||
|
||||
[FieldOffset(0x0740)]
|
||||
public readonly uint MaxLuxon;
|
||||
|
||||
[FieldOffset(0x0744)]
|
||||
public readonly uint MaxBalthazar;
|
||||
|
||||
[FieldOffset(0x0748)]
|
||||
public readonly uint MaxImperial;
|
||||
|
||||
[FieldOffset(0x790)]
|
||||
public readonly GuildwarsArray Players;
|
||||
|
||||
[FieldOffset(0x07D0)]
|
||||
public readonly uint FoesKilled;
|
||||
|
||||
[FieldOffset(0x07D4)]
|
||||
public readonly uint FoesToKill;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct GlobalContext
|
||||
{
|
||||
[FieldOffset(0x0008)]
|
||||
public readonly IntPtr InstanceContext;
|
||||
|
||||
[FieldOffset(0x002C)]
|
||||
public readonly IntPtr GameContext;
|
||||
|
||||
[FieldOffset(0x0044)]
|
||||
public readonly IntPtr UserContext;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct GuildwarsArray
|
||||
{
|
||||
public readonly IntPtr Buffer;
|
||||
public readonly uint Capacity;
|
||||
public readonly uint Size;
|
||||
public readonly uint Param;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
public readonly struct InstanceContext
|
||||
{
|
||||
public const int BaseOffset = 0x01AC;
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds since the instance was joined.
|
||||
/// </summary>
|
||||
public readonly uint Timer;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct MapEntityContext
|
||||
{
|
||||
[FieldOffset(0x00)]
|
||||
public readonly float CurrentEnergy;
|
||||
[FieldOffset(0x04)]
|
||||
public readonly float MaxEnergy;
|
||||
/// <summary>
|
||||
/// Amount of regen in one second.
|
||||
/// </summary>
|
||||
[FieldOffset(0x08)]
|
||||
public readonly float EnergyRegen;
|
||||
/// <summary>
|
||||
/// Milliseconds since the last information update from the server.
|
||||
/// </summary>
|
||||
[FieldOffset(0x0C)]
|
||||
public readonly int SkillTimestamp;
|
||||
[FieldOffset(0x20)]
|
||||
public readonly float CurrentHealth;
|
||||
[FieldOffset(0x24)]
|
||||
public readonly float MaxHealth;
|
||||
/// <summary>
|
||||
/// Amount of regen in one second.
|
||||
/// </summary>
|
||||
[FieldOffset(0x28)]
|
||||
public readonly float HealthRegen;
|
||||
/// <summary>
|
||||
/// Flags containing the current effects on the entity.
|
||||
/// 0x0001 Bleeding
|
||||
/// 0x0002 Conditioned
|
||||
/// 0x000A == 0xA Crippled
|
||||
/// 0x0010 Dead
|
||||
/// 0x0020 Deep Wound
|
||||
/// 0x0040 Poisoned
|
||||
/// 0x0080 Enchanted
|
||||
/// 0x0400 Degen Hexed
|
||||
/// 0x0800 Hexed
|
||||
/// 0x8000 Holding Item
|
||||
/// </summary>
|
||||
[FieldOffset(0x30)]
|
||||
public readonly uint Effects;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct PlayerContext
|
||||
{
|
||||
[FieldOffset(0x0000)]
|
||||
public readonly int AgentId;
|
||||
|
||||
[FieldOffset(0x0014)]
|
||||
public readonly uint PartyFlags;
|
||||
|
||||
[FieldOffset(0x0018)]
|
||||
public readonly uint PrimaryProfession;
|
||||
|
||||
[FieldOffset(0x001C)]
|
||||
public readonly uint SecondaryProfession;
|
||||
|
||||
[FieldOffset(0x0028)]
|
||||
public readonly IntPtr NamePointer;
|
||||
|
||||
[FieldOffset(0x002C)]
|
||||
public readonly uint PartyLeaderPlayerNumber;
|
||||
|
||||
[FieldOffset(0x0034)]
|
||||
public readonly uint PlayerNumber;
|
||||
|
||||
[FieldOffset(0x0038)]
|
||||
public readonly uint PartySize;
|
||||
|
||||
[FieldOffset(0x003C)]
|
||||
//Ignore this field. Added so that the struct will have the proper size and be marshaled properly into the array.
|
||||
private readonly GuildwarsArray HH3C;
|
||||
|
||||
public bool Pvp => (this.PartyFlags & 0x800) != 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct ProfessionsContext
|
||||
{
|
||||
public readonly uint AgentId;
|
||||
|
||||
public readonly uint CurrentPrimary;
|
||||
|
||||
public readonly uint CurrentSecondary;
|
||||
|
||||
public readonly uint UnlockedProfessionsFlags;
|
||||
|
||||
//Ignore this field. Added so that the struct will have the proper size and be marshaled properly into the array.
|
||||
private readonly uint H0010;
|
||||
|
||||
public bool ProfessionUnlocked(int professionId)
|
||||
{
|
||||
return (this.UnlockedProfessionsFlags & 1U << professionId) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct QuestContext
|
||||
{
|
||||
[FieldOffset(0x0000)]
|
||||
public readonly uint QuestId;
|
||||
|
||||
[FieldOffset(0x0014)]
|
||||
public readonly uint MapFrom;
|
||||
|
||||
[FieldOffset(0x0028)]
|
||||
public readonly uint MapTo;
|
||||
|
||||
[FieldOffset(0x0030)]
|
||||
//Ignore this field. Added so that the struct will have the proper size and be marshaled properly into the array.
|
||||
private readonly uint HH30;
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
namespace Daybreak.Models.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct CharContext
|
||||
public readonly struct UserContext
|
||||
{
|
||||
public const int BaseOffset = 0x0074;
|
||||
|
||||
[FieldOffset(0x0000)]
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x28)]
|
||||
public readonly byte[] PlayerNameBytes;
|
||||
|
||||
|
||||
[FieldOffset(0x01B0)]
|
||||
public readonly int Language;
|
||||
|
||||
|
||||
[FieldOffset(0x0230)]
|
||||
public readonly uint PlayerNumber;
|
||||
|
||||
[FieldOffset(0x0344)]
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x80)]
|
||||
public readonly byte[] PlayerEmailBytes;
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Daybreak.Services.Experience;
|
||||
|
||||
/// <summary>
|
||||
/// Based on the explanation from here https://wiki.guildwars.com/wiki/Experience.
|
||||
/// After 182600, the experience threshold is capped.
|
||||
/// </summary>
|
||||
public sealed class ExperienceCalculator : IExperienceCalculator
|
||||
{
|
||||
private const uint ExperienceCalculationThreshold = 182600;
|
||||
private const uint MaxExperienceRequirement = 15000;
|
||||
private static readonly List<uint> ExperienceThreshold = new()
|
||||
{
|
||||
0,
|
||||
2000,
|
||||
4600,
|
||||
7800,
|
||||
11600,
|
||||
16000,
|
||||
21000,
|
||||
26600,
|
||||
32800,
|
||||
39600,
|
||||
47000,
|
||||
55000,
|
||||
63600,
|
||||
72800,
|
||||
82600,
|
||||
93000,
|
||||
104000,
|
||||
115600,
|
||||
127800,
|
||||
140600,
|
||||
154000,
|
||||
168000,
|
||||
182600
|
||||
};
|
||||
|
||||
public uint GetExperienceForCurrentLevel(uint currentTotalExperience)
|
||||
{
|
||||
if (currentTotalExperience < ExperienceCalculationThreshold)
|
||||
{
|
||||
var previousLevelExperience = 0U;
|
||||
foreach (var threshold in ExperienceThreshold)
|
||||
{
|
||||
if (threshold >= currentTotalExperience)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
previousLevelExperience = threshold;
|
||||
}
|
||||
|
||||
return currentTotalExperience - previousLevelExperience;
|
||||
}
|
||||
else
|
||||
{
|
||||
var experienceOverThreshold = currentTotalExperience - ExperienceCalculationThreshold;
|
||||
var currentExperience = (experienceOverThreshold % MaxExperienceRequirement);
|
||||
return currentExperience;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetRemainingExperienceForNextLevel(uint currentTotalExperience)
|
||||
{
|
||||
if (currentTotalExperience < ExperienceCalculationThreshold)
|
||||
{
|
||||
var totalXpForNextLevel = 0U;
|
||||
foreach(var threshold in ExperienceThreshold)
|
||||
{
|
||||
if (currentTotalExperience < threshold)
|
||||
{
|
||||
totalXpForNextLevel = threshold;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return totalXpForNextLevel - currentTotalExperience;
|
||||
}
|
||||
else
|
||||
{
|
||||
var experienceOverThreshold = currentTotalExperience - ExperienceCalculationThreshold;
|
||||
var remaining = MaxExperienceRequirement - (experienceOverThreshold % MaxExperienceRequirement);
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetTotalExperienceForNextLevel(uint currentTotalExperience)
|
||||
{
|
||||
return currentTotalExperience + this.GetRemainingExperienceForNextLevel(currentTotalExperience);
|
||||
}
|
||||
|
||||
public uint GetNextExperienceThreshold(uint currentTotalExperience)
|
||||
{
|
||||
if (currentTotalExperience < ExperienceCalculationThreshold)
|
||||
{
|
||||
var totalXpForNextLevel = 0U;
|
||||
var totalXpForPreviousLevel = 0U;
|
||||
foreach (var threshold in ExperienceThreshold)
|
||||
{
|
||||
if (currentTotalExperience < threshold)
|
||||
{
|
||||
totalXpForNextLevel = threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
totalXpForPreviousLevel = threshold;
|
||||
}
|
||||
|
||||
return totalXpForNextLevel - totalXpForPreviousLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return MaxExperienceRequirement;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Daybreak.Services.Experience;
|
||||
|
||||
public interface IExperienceCalculator
|
||||
{
|
||||
uint GetExperienceForCurrentLevel(uint currentTotalExperience);
|
||||
uint GetTotalExperienceForNextLevel(uint currentTotalExperience);
|
||||
uint GetRemainingExperienceForNextLevel(uint currentTotalExperience);
|
||||
uint GetNextExperienceThreshold(uint currentTotalExperience);
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Models.Guildwars;
|
||||
using Daybreak.Models.Interop;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Linq;
|
||||
using System.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.Scanner;
|
||||
|
||||
public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
|
||||
{
|
||||
private const int RetryInitializationCount = 15;
|
||||
|
||||
private readonly IMemoryScanner memoryScanner;
|
||||
private readonly ILogger<GuildwarsMemoryReader> logger;
|
||||
|
||||
private IntPtr playerIdPointer;
|
||||
private IntPtr entityArrayPointer;
|
||||
private volatile CancellationTokenSource? cancellationTokenSource;
|
||||
|
||||
public bool Running => this.cancellationTokenSource is not null && this.cancellationTokenSource.IsCancellationRequested is false;
|
||||
@@ -31,7 +40,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public void Initialize(Process process)
|
||||
public async void Initialize(Process process)
|
||||
{
|
||||
var scoppedLogger = this.logger.CreateScopedLogger(nameof(this.Initialize), default);
|
||||
if (process is null)
|
||||
@@ -40,27 +49,14 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
scoppedLogger.LogInformation($"Initializing {nameof(GuildwarsMemoryReader)}");
|
||||
if (this.memoryScanner.Process?.MainModule?.FileName != process?.MainModule?.FileName)
|
||||
try
|
||||
{
|
||||
if (this.memoryScanner.Scanning)
|
||||
{
|
||||
scoppedLogger.LogInformation("Scanner is already scanning a different process. Restart scanner and target the new process");
|
||||
this.memoryScanner.EndScanner();
|
||||
}
|
||||
|
||||
scoppedLogger.LogInformation("Initializing scanner");
|
||||
this.memoryScanner.BeginScanner(process!);
|
||||
await this.InitializeSafe(process, scoppedLogger);
|
||||
}
|
||||
|
||||
if (!this.memoryScanner.Scanning)
|
||||
catch(Exception e)
|
||||
{
|
||||
scoppedLogger.LogInformation("Initializing scanner");
|
||||
this.memoryScanner.BeginScanner(process!);
|
||||
scoppedLogger.LogError(e, "Encountered exception during initialization");
|
||||
}
|
||||
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
this.PeriodicallyReadGuildwarsMemory();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
@@ -70,11 +66,66 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private async Task InitializeSafe(Process process, ScopedLogger<GuildwarsMemoryReader> scopedLogger)
|
||||
{
|
||||
scopedLogger.LogInformation($"Initializing {nameof(GuildwarsMemoryReader)}");
|
||||
if (this.memoryScanner.Process is null ||
|
||||
this.memoryScanner.Process.HasExited ||
|
||||
this.memoryScanner.Process.MainModule?.FileName != process?.MainModule?.FileName)
|
||||
{
|
||||
if (this.memoryScanner.Scanning)
|
||||
{
|
||||
scopedLogger.LogInformation("Scanner is already scanning a different process. Restart scanner and target the new process");
|
||||
this.memoryScanner.EndScanner();
|
||||
}
|
||||
|
||||
await this.ResilientBeginScanner(scopedLogger, process!);
|
||||
}
|
||||
|
||||
if (!this.memoryScanner.Scanning)
|
||||
{
|
||||
await this.ResilientBeginScanner(scopedLogger, process!);
|
||||
}
|
||||
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
this.PeriodicallyReadGuildwarsMemory();
|
||||
}
|
||||
|
||||
private async Task ResilientBeginScanner(ScopedLogger<GuildwarsMemoryReader> scopedLogger, Process process)
|
||||
{
|
||||
for (var i = 0; i < RetryInitializationCount; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
scopedLogger.LogInformation("Initializing scanner");
|
||||
this.memoryScanner.BeginScanner(process!);
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
scopedLogger.LogError(e, "Error during initialization");
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PeriodicallyReadGuildwarsMemory()
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
this.cancellationTokenSource = cancellationTokenSource;
|
||||
TaskExtensions.RunPeriodicAsync(() => this.ReadGameMemory(cancellationTokenSource.Token), TimeSpan.Zero, TimeSpan.FromSeconds(1), cancellationTokenSource.Token);
|
||||
System.Extensions.TaskExtensions.RunPeriodicAsync(() => this.SafeReadGameMemory(cancellationTokenSource.Token), TimeSpan.Zero, TimeSpan.FromSeconds(1), cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void SafeReadGameMemory(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ReadGameMemory(cancellationToken);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
this.logger.LogError(e, "Exception encountered when reading game memory");
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadGameMemory(CancellationToken cancellationToken)
|
||||
@@ -93,43 +144,218 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
|
||||
* startAddress + 00AA9D58 -> +0xC -> +0xC
|
||||
* startAddress + 00AA9D58 -> +0x0 -> +0xC -> +0xC
|
||||
*/
|
||||
var gameContext = this.memoryScanner.ReadPtrChain<GameContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629244, 0xC, 0xC);
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629244, 0xC, 0xC);
|
||||
|
||||
// WorldContext struct is offset by 0x528 due to the memory layout of the structure.
|
||||
var worldContext = this.memoryScanner.Read<WorldContext>(gameContext.WorldContext + WorldContext.BaseOffset);
|
||||
// CharContext struct is offset by 0x074 due to the memory layout of the structure.
|
||||
var charContext = this.memoryScanner.Read<CharContext>(gameContext.CharContext + CharContext.BaseOffset);
|
||||
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
|
||||
var gameContext = this.memoryScanner.Read<GameContext>(globalContext.GameContext + GameContext.BaseOffset);
|
||||
// UserContext struct is offset by 0x074 due to the memory layout of the structure.
|
||||
var userContext = this.memoryScanner.Read<UserContext>(globalContext.UserContext + UserContext.BaseOffset);
|
||||
// InstanceContext struct is offset by 0x01AC due to memory layout of the structure.
|
||||
var instanceContext = this.memoryScanner.Read<InstanceContext>(globalContext.InstanceContext + InstanceContext.BaseOffset);
|
||||
|
||||
var email = ParseAndCleanWCharArray(charContext.PlayerEmailBytes);
|
||||
var name = ParseAndCleanWCharArray(charContext.PlayerNameBytes);
|
||||
_ = Quest.TryParse((int)worldContext.QuestId, out var quest);
|
||||
var mapEntities = this.memoryScanner.ReadArray<MapEntityContext>(gameContext.MapEntities);
|
||||
var professions = this.memoryScanner.ReadArray<ProfessionsContext>(gameContext.Professions);
|
||||
var players = this.memoryScanner.ReadArray<PlayerContext>(gameContext.Players);
|
||||
var quests = this.memoryScanner.ReadArray<QuestContext>(gameContext.QuestLog);
|
||||
var playerEntityId = this.memoryScanner.ReadPtrChain<int>(this.GetPlayerIdPointer(), 0x0, 0x0);
|
||||
|
||||
// The following lines would retrieve all entities, including item entities.
|
||||
//var entityArray = this.memoryScanner.ReadPtrChain<GuildwarsArray>(this.GetEntityArrayPointer(), 0x0, 0x0);
|
||||
//var entities = this.memoryScanner.ReadArray<EntityContext>(entityArray);
|
||||
|
||||
this.GameData = new GameData
|
||||
var email = ParseAndCleanWCharArray(userContext.PlayerEmailBytes);
|
||||
var name = ParseAndCleanWCharArray(userContext.PlayerNameBytes);
|
||||
|
||||
this.GameData = this.AggregateGameData(gameContext, instanceContext, mapEntities, players, professions, quests, email, name, playerEntityId);
|
||||
}
|
||||
|
||||
private IntPtr GetPlayerIdPointer()
|
||||
{
|
||||
if (this.playerIdPointer == IntPtr.Zero)
|
||||
{
|
||||
this.playerIdPointer = this.memoryScanner.ScanForPtr(new byte[] { 0x5D, 0xE9, 0x00, 0x00, 0x00, 0x00, 0x55, 0x8B, 0xEC, 0x53 }, "xx????xxxx") - 0xE;
|
||||
}
|
||||
|
||||
return this.playerIdPointer;
|
||||
}
|
||||
|
||||
private IntPtr GetEntityArrayPointer()
|
||||
{
|
||||
if (this.entityArrayPointer == IntPtr.Zero)
|
||||
{
|
||||
this.entityArrayPointer = this.memoryScanner.ScanForPtr(new byte[] { 0xFF, 0x50, 0x10, 0x47, 0x83, 0xC6, 0x04, 0x3B, 0xFB, 0x75, 0xE1 }, "xxxxxxxxxxx") + 0xD;
|
||||
}
|
||||
|
||||
return this.entityArrayPointer;
|
||||
}
|
||||
|
||||
private GameData AggregateGameData(
|
||||
GameContext gameContext,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] entities,
|
||||
PlayerContext[] players,
|
||||
ProfessionsContext[] professions,
|
||||
QuestContext[] quests,
|
||||
string email,
|
||||
string name,
|
||||
int mainPlayerEntityId)
|
||||
{
|
||||
var partyMembers = professions
|
||||
.Where(p => p.AgentId != mainPlayerEntityId)
|
||||
.Select(p => GetPlayerInformation((int)p.AgentId, instanceContext, entities, professions))
|
||||
.ToList();
|
||||
|
||||
var mainPlayer = GetMainPlayerInformation(mainPlayerEntityId, name, gameContext, instanceContext, entities, professions, quests);
|
||||
|
||||
var worldPlayers = players
|
||||
.Where(p => p.AgentId != mainPlayerEntityId)
|
||||
.Select(p => GetWorldPlayerInformation(p, this.memoryScanner.ReadWString(p.NamePointer, 0x40), instanceContext, entities, professions))
|
||||
.ToList();
|
||||
|
||||
var userInformation = new UserInformation
|
||||
{
|
||||
CharacterName = name,
|
||||
Email = email,
|
||||
Quest = quest,
|
||||
HardModeUnlocked = worldContext.HardModeUnlocked == 1,
|
||||
Level = worldContext.Level,
|
||||
Morale = worldContext.Morale,
|
||||
Experience = worldContext.Experience,
|
||||
CurrentBalthazarPoints = worldContext.CurrentBalthazar,
|
||||
CurrentImperialPoints = worldContext.CurrentImperial,
|
||||
CurrentKurzickPoints = worldContext.CurrentKurzick,
|
||||
CurrentLuxonPoints = worldContext.CurrentLuxon,
|
||||
TotalBalthazarPoints = worldContext.TotalBalthazar,
|
||||
TotalImperialPoints = worldContext.TotalImperial,
|
||||
TotalKurzickPoints = worldContext.TotalKurzick,
|
||||
TotalLuxonPoints = worldContext.TotalLuxon,
|
||||
MaxBalthazarPoints = worldContext.MaxBalthazar,
|
||||
MaxImperialPoints = worldContext.MaxImperial,
|
||||
MaxKurzickPoints = worldContext.MaxKurzick,
|
||||
MaxLuxonPoints = worldContext.MaxLuxon,
|
||||
CurrentSkillPoints = worldContext.CurrentSkillPoints,
|
||||
TotalSkillPoints = worldContext.TotalSkillPoints,
|
||||
FoesKilled = worldContext.FoesKilled,
|
||||
FoesToKill = worldContext.FoesToKill
|
||||
CurrentKurzickPoints = gameContext.CurrentKurzick,
|
||||
TotalKurzickPoints = gameContext.TotalKurzick,
|
||||
MaxKurzickPoints = gameContext.MaxKurzick,
|
||||
CurrentLuxonPoints = gameContext.CurrentLuxon,
|
||||
TotalLuxonPoints = gameContext.TotalLuxon,
|
||||
MaxLuxonPoints = gameContext.MaxLuxon,
|
||||
CurrentImperialPoints = gameContext.CurrentImperial,
|
||||
TotalImperialPoints = gameContext.TotalImperial,
|
||||
MaxImperialPoints = gameContext.MaxImperial,
|
||||
CurrentBalthazarPoints = gameContext.CurrentBalthazar,
|
||||
TotalBalthazarPoints = gameContext.TotalBalthazar,
|
||||
MaxBalthazarPoints = gameContext.MaxBalthazar,
|
||||
CurrentSkillPoints = gameContext.CurrentSkillPoints,
|
||||
TotalSkillPoints = gameContext.TotalSkillPoints
|
||||
};
|
||||
|
||||
var sessionInformation = new SessionInformation
|
||||
{
|
||||
FoesKilled = gameContext.FoesKilled,
|
||||
FoesToKill = gameContext.FoesToKill
|
||||
};
|
||||
|
||||
return new GameData
|
||||
{
|
||||
Party = partyMembers,
|
||||
MainPlayer = mainPlayer,
|
||||
Session = sessionInformation,
|
||||
User = userInformation,
|
||||
WorldPlayers = worldPlayers
|
||||
};
|
||||
}
|
||||
|
||||
private static MainPlayerInformation GetMainPlayerInformation(
|
||||
int mainPlayerId,
|
||||
string name,
|
||||
GameContext gameContext,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] entities,
|
||||
ProfessionsContext[] professions,
|
||||
QuestContext[] quests)
|
||||
{
|
||||
var playerInformation = GetPlayerInformation(mainPlayerId, instanceContext, entities, professions);
|
||||
_ = Quest.TryParse((int)gameContext.QuestId, out var quest);
|
||||
var questLog = quests
|
||||
.Select(q =>
|
||||
{
|
||||
_ = Quest.TryParse((int)q.QuestId, out var parsedQuest);
|
||||
_ = Map.TryParse((int)q.MapFrom, out var mapFrom);
|
||||
_ = Map.TryParse((int)q.MapTo, out var mapTo);
|
||||
return new QuestMetadata { Quest = parsedQuest, From = mapFrom, To = mapTo };
|
||||
})
|
||||
.Where(q => q?.Quest is not null)
|
||||
.ToList();
|
||||
|
||||
return new MainPlayerInformation
|
||||
{
|
||||
PrimaryProfession = playerInformation.PrimaryProfession,
|
||||
SecondaryProfession = playerInformation.SecondaryProfession,
|
||||
UnlockedProfession = playerInformation.UnlockedProfession,
|
||||
HardModeUnlocked = gameContext.HardModeUnlocked == 1,
|
||||
CurrentEnergy = playerInformation.CurrentEnergy,
|
||||
CurrentHealth = playerInformation.CurrentHealth,
|
||||
MaxEnergy = playerInformation.MaxEnergy,
|
||||
MaxHealth = playerInformation.MaxHealth,
|
||||
EnergyRegen = playerInformation.EnergyRegen,
|
||||
HealthRegen = playerInformation.HealthRegen,
|
||||
Quest = quest,
|
||||
QuestLog = questLog,
|
||||
Name = name,
|
||||
Experience = gameContext.Experience,
|
||||
Level = gameContext.Level,
|
||||
Morale = gameContext.Morale
|
||||
};
|
||||
}
|
||||
|
||||
private static WorldPlayerInformation GetWorldPlayerInformation(
|
||||
PlayerContext playerContext,
|
||||
string name,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] entities,
|
||||
ProfessionsContext[] professions)
|
||||
{
|
||||
var playerInformation = GetPlayerInformation(playerContext.AgentId, instanceContext, entities, professions);
|
||||
return new WorldPlayerInformation
|
||||
{
|
||||
PrimaryProfession = playerInformation.PrimaryProfession,
|
||||
SecondaryProfession = playerInformation.SecondaryProfession,
|
||||
UnlockedProfession = playerInformation.UnlockedProfession,
|
||||
CurrentEnergy = playerInformation.CurrentEnergy,
|
||||
CurrentHealth = playerInformation.CurrentHealth,
|
||||
MaxEnergy = playerInformation.MaxEnergy,
|
||||
MaxHealth = playerInformation.MaxHealth,
|
||||
EnergyRegen = playerInformation.EnergyRegen,
|
||||
HealthRegen = playerInformation.HealthRegen,
|
||||
Name = name,
|
||||
};
|
||||
}
|
||||
|
||||
private static PlayerInformation GetPlayerInformation(
|
||||
int playerId,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] entities,
|
||||
ProfessionsContext[] professions)
|
||||
{
|
||||
var entityContext = entities.Skip(playerId).FirstOrDefault();
|
||||
var professionContext = professions.Where(p => p.AgentId == playerId).FirstOrDefault();
|
||||
_ = Profession.TryParse((int)professionContext.CurrentPrimary, out var primaryProfession);
|
||||
_ = Profession.TryParse((int)professionContext.CurrentSecondary, out var secondaryProfession);
|
||||
var unlockedProfessions = Profession.Professions
|
||||
.Where(p => professionContext.ProfessionUnlocked(p.Id))
|
||||
.Append(primaryProfession)
|
||||
.Where(p => p is not null && p != Profession.None)
|
||||
.OrderBy(p => p.Id)
|
||||
.ToList();
|
||||
(var currentHp, var currentEnergy) = ApplyEnergyAndHealthRegen(instanceContext, entityContext);
|
||||
return new PlayerInformation
|
||||
{
|
||||
PrimaryProfession = primaryProfession,
|
||||
SecondaryProfession = secondaryProfession,
|
||||
UnlockedProfession = unlockedProfessions,
|
||||
CurrentHealth = currentHp,
|
||||
CurrentEnergy = currentEnergy,
|
||||
MaxHealth = entityContext.MaxHealth,
|
||||
MaxEnergy = entityContext.MaxEnergy,
|
||||
HealthRegen = entityContext.HealthRegen,
|
||||
EnergyRegen = entityContext.EnergyRegen
|
||||
};
|
||||
}
|
||||
|
||||
private static (float CurrentHp, float CurrentEnergy) ApplyEnergyAndHealthRegen(InstanceContext instanceContext, MapEntityContext entityContext)
|
||||
{
|
||||
var lastKnownHp = entityContext.CurrentHealth;
|
||||
var lastKnownEnergy = entityContext.CurrentEnergy;
|
||||
var hpRegen = entityContext.HealthRegen;
|
||||
var energyRegen = entityContext.EnergyRegen;
|
||||
var millisSinceLastKnownInformation = instanceContext.Timer - (uint)entityContext.SkillTimestamp;
|
||||
var currentHp = lastKnownHp + (hpRegen * ((float)millisSinceLastKnownInformation / 1000));
|
||||
var currentEnergy = lastKnownEnergy + (energyRegen * ((float)millisSinceLastKnownInformation / 1000));
|
||||
return (
|
||||
currentHp > entityContext.MaxHealth ? entityContext.MaxHealth : currentHp,
|
||||
currentEnergy > entityContext.MaxEnergy ? entityContext.MaxEnergy : currentEnergy);
|
||||
}
|
||||
|
||||
private static string ParseAndCleanWCharArray(byte[] bytes)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Daybreak.Models.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Daybreak.Services.Scanner;
|
||||
@@ -14,8 +16,10 @@ public interface IMemoryScanner
|
||||
void BeginScanner(Process process);
|
||||
void EndScanner();
|
||||
T Read<T>(IntPtr address);
|
||||
T[] ReadArray<T>(IntPtr address, int size);
|
||||
T[] ReadArray<T>(GuildwarsArray guildwarsArray);
|
||||
byte[]? ReadBytes(IntPtr address, int size);
|
||||
string ReadWString(IntPtr address, int maxsize);
|
||||
T ReadPtrChain<T>(IntPtr Base, int finalPointerOffset = 0, params int[] offsets);
|
||||
IntPtr ScanForPtr(byte[] pattern, bool readptr = false);
|
||||
IntPtr ScanForPtr(byte[] pattern, string? mask = default, bool readptr = false);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Daybreak.Utils;
|
||||
using Daybreak.Models.Interop;
|
||||
using Daybreak.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -96,6 +99,7 @@ public sealed class MemoryScanner : IMemoryScanner
|
||||
this.Memory = default;
|
||||
this.Size = default;
|
||||
this.ModuleStartAddress = default;
|
||||
this.Scanning = false;
|
||||
Monitor.Exit(LockObject);
|
||||
}
|
||||
|
||||
@@ -118,6 +122,36 @@ public sealed class MemoryScanner : IMemoryScanner
|
||||
return ret;
|
||||
}
|
||||
|
||||
public T[] ReadArray<T>(IntPtr address, int size)
|
||||
{
|
||||
this.ValidateReadScanner();
|
||||
var itemSize = Marshal.SizeOf(typeof(T));
|
||||
var buffer = Marshal.AllocHGlobal(size * itemSize);
|
||||
|
||||
NativeMethods.ReadProcessMemory(this.Process!.Handle,
|
||||
address,
|
||||
buffer,
|
||||
size * itemSize,
|
||||
out _
|
||||
);
|
||||
|
||||
var retArray = new T[size];
|
||||
var arrayPointer = buffer;
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
retArray[i] = (T)Marshal.PtrToStructure(arrayPointer, typeof(T))!;
|
||||
arrayPointer += itemSize;
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
return retArray;
|
||||
}
|
||||
|
||||
public T[] ReadArray<T>(GuildwarsArray guildwarsArray)
|
||||
{
|
||||
return this.ReadArray<T>(guildwarsArray.Buffer, (int)guildwarsArray.Size);
|
||||
}
|
||||
|
||||
public byte[]? ReadBytes(IntPtr address, int size)
|
||||
{
|
||||
this.ValidateReadScanner();
|
||||
@@ -153,7 +187,7 @@ public sealed class MemoryScanner : IMemoryScanner
|
||||
return this.Read<T>(Base + finalPointerOffset);
|
||||
}
|
||||
|
||||
public IntPtr ScanForPtr(byte[] pattern, bool readptr = false)
|
||||
public IntPtr ScanForPtr(byte[] pattern, string? mask = default, bool readptr = false)
|
||||
{
|
||||
this.ValidateReadScanner();
|
||||
if (pattern?.Length == 0)
|
||||
@@ -171,6 +205,13 @@ public sealed class MemoryScanner : IMemoryScanner
|
||||
var matched = true;
|
||||
for (var patternIndex = 0; patternIndex < pattern.Length; ++patternIndex)
|
||||
{
|
||||
if (mask is not null &&
|
||||
mask.Length > patternIndex &&
|
||||
mask[patternIndex] == '?')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var memoryValue = this.Memory[scan + patternIndex];
|
||||
var signatureValue = pattern[patternIndex];
|
||||
if (memoryValue != signatureValue)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<TextBlock HorizontalAlignment="Center" Text="Accounts settings" FontSize="22" Foreground="White"></TextBlock>
|
||||
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5, 5, 45, 5"
|
||||
Clicked="AddButton_Clicked" VerticalAlignment="Top"></controls:AddButton>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
@@ -30,5 +33,9 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<controls:CircularLoadingWidget Grid.RowSpan="3"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Visibility="{Binding ElementName=_this, Path=Loading, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></controls:CircularLoadingWidget>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Extensions;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
|
||||
namespace Daybreak.Views;
|
||||
|
||||
@@ -21,6 +22,9 @@ public partial class BuildsListView : UserControl
|
||||
|
||||
private IEnumerable<BuildEntry>? buildEntries;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool loading;
|
||||
|
||||
public ObservableCollection<BuildEntry> BuildEntries { get; } = new ObservableCollection<BuildEntry>();
|
||||
|
||||
public BuildsListView(
|
||||
@@ -35,8 +39,10 @@ public partial class BuildsListView : UserControl
|
||||
|
||||
private async void LoadBuilds()
|
||||
{
|
||||
this.Loading = true;
|
||||
this.buildEntries = await this.buildTemplateManager.GetBuilds().ToListAsync();
|
||||
this.BuildEntries.ClearAnd().AddRange(this.buildEntries.OrderBy(b => b.Name));
|
||||
this.Loading = false;
|
||||
}
|
||||
|
||||
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<BooleanToVisibilityConverter x:Key="BaseBooleanToVisibilityConverter"></BooleanToVisibilityConverter>
|
||||
</UserControl.Resources>
|
||||
<Grid
|
||||
Background="#A0202020">
|
||||
Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
@@ -174,7 +174,7 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.RowSpan="3"
|
||||
Background="#A0202020"
|
||||
Background="#F0212121"
|
||||
Visibility="{Binding ElementName=_this, Path=ShowLoading, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<controls:CircularLoadingWidget MaxWidth="200"
|
||||
MaxHeight="200"/>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<TextBlock HorizontalAlignment="Center" Text="Executables settings" FontSize="22" Foreground="White"></TextBlock>
|
||||
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5, 5, 45, 5"
|
||||
Clicked="AddButton_Clicked" VerticalAlignment="Top"></controls:AddButton>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
|
||||
+277
-95
@@ -5,116 +5,298 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Views"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
xmlns:templates="clr-namespace:Daybreak.Controls.Templates"
|
||||
mc:Ignorable="d"
|
||||
Foreground="White"
|
||||
Loaded="FocusView_Loaded"
|
||||
Unloaded="FocusView_Unloaded"
|
||||
x:Name="_this"
|
||||
PreviewMouseLeftButtonDown="FocusView_PreviewMouseLeftButtonDown"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="0.01*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="0.01*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Fill="Transparent"
|
||||
PreviewMouseLeftButtonDown="FocusView_PreviewMouseLeftButtonDown"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsHitTestVisible="False"></Rectangle>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Grid.Column="0">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Background="#A0202020">
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Account: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.Email, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Character: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CharacterName, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Level: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.Level, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Skill Points: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CurrentSkillPoints, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Experience: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.Experience, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Current Quest: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.Quest.Name, Mode=OneWay}"
|
||||
FontSize="22" Foreground="Blue"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonDown="Quest_MouseLeftButtonDown"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Kurzick Points: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CurrentKurzickPoints, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Luxon Points: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CurrentLuxonPoints, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Balthazar Points: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CurrentBalthazarPoints, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5">
|
||||
<TextBlock Text="Imperial Points: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.CurrentImperialPoints, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5"
|
||||
Visibility="{Binding ElementName=_this, Path=GameData.HardModeUnlocked, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Text="Foes Killed: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.FoesKilled, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel
|
||||
Margin="5"
|
||||
Visibility="{Binding ElementName=_this, Path=GameData.HardModeUnlocked, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Text="Foes To Kill: " FontSize="22"></TextBlock>
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=GameData.FoesToKill, Mode=OneWay}"
|
||||
FontSize="22"></TextBlock>
|
||||
</WrapPanel>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="0*"></RowDefinition>
|
||||
<RowDefinition Height="1*"></RowDefinition>
|
||||
<RowDefinition Height="118"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
Margin="0, 2, 0, 0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="0.1*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="0.3*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="0.1*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:VerticalResourceBar
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentExperienceInLevel, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=NextLevelExperienceThreshold, Mode=OneWay}"
|
||||
Text="{Binding ElementName=_this,Path=ExperienceBarText, Mode=OneWay}"
|
||||
BarColor="#6076FF03"
|
||||
Foreground="White"
|
||||
Height="113"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonDown="ExperienceBar_MouseLeftButtonDown">
|
||||
<controls:VerticalResourceBar.OpacityMask>
|
||||
<DrawingBrush>
|
||||
<DrawingBrush.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="#FFFFFFFF">
|
||||
<GeometryDrawing.Geometry>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigure IsClosed="True" StartPoint="5, 1">
|
||||
<PathSegmentCollection>
|
||||
<LineSegment Point="1, 5"></LineSegment>
|
||||
<LineSegment Point="5, 9"></LineSegment>
|
||||
<LineSegment Point="15, 9"></LineSegment>
|
||||
<LineSegment Point="19, 5"></LineSegment>
|
||||
<LineSegment Point="15, 1"></LineSegment>
|
||||
</PathSegmentCollection>
|
||||
</PathFigure>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</GeometryDrawing.Geometry>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</controls:VerticalResourceBar.OpacityMask>
|
||||
</controls:VerticalResourceBar>
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2">
|
||||
<StackPanel.OpacityMask>
|
||||
<DrawingBrush>
|
||||
<DrawingBrush.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="#FFFFFFFF">
|
||||
<GeometryDrawing.Geometry>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigure IsClosed="True" StartPoint="1, 1">
|
||||
<PathSegmentCollection>
|
||||
<LineSegment Point="1, 9"></LineSegment>
|
||||
<LineSegment Point="21, 9"></LineSegment>
|
||||
<LineSegment Point="19, 5"></LineSegment>
|
||||
<LineSegment Point="21, 1"></LineSegment>
|
||||
</PathSegmentCollection>
|
||||
</PathFigure>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</GeometryDrawing.Geometry>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</StackPanel.OpacityMask>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentKurzick, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalKurzick, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#603D5AFE"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=KurzickBarText, Mode=OneWay}"
|
||||
MouseLeftButtonDown="KurzickBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentLuxon, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalLuxon, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#60FF9100"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=LuxonBarText, Mode=OneWay}"
|
||||
MouseLeftButtonDown="LuxonBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentImperial, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalImperial, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#60651FFF"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=ImperialBarText, Mode=OneWay}"
|
||||
MouseLeftButtonDown="ImperialBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentBalthazar, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalBalthazar, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#60FFEA00"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=BalthazarBarText, Mode=OneWay}"
|
||||
MouseLeftButtonDown="BalthazarBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="3"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2">
|
||||
<StackPanel.OpacityMask>
|
||||
<DrawingBrush>
|
||||
<DrawingBrush.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="#FFFFFFFF">
|
||||
<GeometryDrawing.Geometry>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigure IsClosed="True" StartPoint="1, 1">
|
||||
<PathSegmentCollection>
|
||||
<LineSegment Point="3, 5"></LineSegment>
|
||||
<LineSegment Point="1, 9"></LineSegment>
|
||||
<LineSegment Point="21, 9"></LineSegment>
|
||||
<LineSegment Point="21, 1"></LineSegment>
|
||||
</PathSegmentCollection>
|
||||
</PathFigure>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</GeometryDrawing.Geometry>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</StackPanel.OpacityMask>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=FoesKilled, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalFoes, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#60DD2C00"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=VanquishingText, Mode=OneWay}"
|
||||
FillAlignment="Right"
|
||||
Visibility="{Binding ElementName=_this, Path=Vanquishing, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
MouseLeftButtonDown="VanquishingBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.MainPlayer.CurrentHealth, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=GameData.MainPlayer.MaxHealth, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#60D50000"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=HealthBarText, Mode=OneWay}"
|
||||
FillAlignment="Right"
|
||||
MouseLeftButtonDown="HealthBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.MainPlayer.CurrentEnergy, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=GameData.MainPlayer.MaxEnergy, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="#6018FFFF"
|
||||
Foreground="White"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
Text="{Binding ElementName=_this, Path=EnergyBarText, Mode=OneWay}"
|
||||
FillAlignment="Right"
|
||||
MouseLeftButtonDown="EnergyBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="10"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Background="#F0212121"
|
||||
Grid.Row="0"
|
||||
Margin="0, 0, 5, 0">
|
||||
<TextBlock
|
||||
FontSize="18"
|
||||
Margin="10"
|
||||
Text="Current Quest"></TextBlock>
|
||||
<Rectangle
|
||||
Height="1"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Rectangle>
|
||||
<controls:OpaqueButton
|
||||
Text="{Binding ElementName=_this, Path=GameData.MainPlayer.Quest.Name, Mode=OneWay}"
|
||||
Highlight="White"
|
||||
HighlightOpacity="0.6"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextHorizontalAlignment="Left"
|
||||
Cursor="Hand"
|
||||
FontSize="20"
|
||||
MouseLeftButtonDown="CurrentQuest_MouseLeftButtonDown"></controls:OpaqueButton>
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Background="#F0212121"
|
||||
Grid.Row="2"
|
||||
FontSize="18"
|
||||
Margin="0, 0, 5, 0"
|
||||
Padding="10"
|
||||
Text="Quest Log"></TextBlock>
|
||||
<ScrollViewer
|
||||
Background="#F0212121"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Grid.Row="3"
|
||||
Margin="0, 0, 5, 0">
|
||||
<templates:QuestLogTemplate
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Quests="{Binding ElementName=_this,Path=GameData.MainPlayer.QuestLog, Mode=OneWay}"
|
||||
MapClicked="QuestLogTemplate_MapClicked"
|
||||
QuestClicked="QuestLogTemplate_QuestClicked">
|
||||
</templates:QuestLogTemplate>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
<controls:ChromiumBrowserWrapper
|
||||
x:Name="Browser"
|
||||
Grid.Column="1"
|
||||
ControlsEnabled="False"
|
||||
Width="0"
|
||||
AddressBarReadonly="True"
|
||||
Grid.Column="2"
|
||||
Grid.Row="1"
|
||||
Margin="5, 0, 0, 0"
|
||||
ControlsEnabled="True"
|
||||
AddressBarReadonly="False"
|
||||
CanNavigate="True"
|
||||
Margin="0, 0, 0, 60"></controls:ChromiumBrowserWrapper>
|
||||
Address="{Binding ElementName=_this, Path=BrowserAddress, Mode=TwoWay}"
|
||||
MaximizeClicked="Browser_MaximizeClicked"
|
||||
PreventDispose="True"></controls:ChromiumBrowserWrapper>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Configuration;
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Models.Guildwars;
|
||||
using Daybreak.Services.ApplicationLauncher;
|
||||
using Daybreak.Services.Experience;
|
||||
using Daybreak.Services.Navigation;
|
||||
using Daybreak.Services.Scanner;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Core.Extensions;
|
||||
using System.Drawing.Printing;
|
||||
using System.Extensions;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
|
||||
@@ -17,33 +23,122 @@ namespace Daybreak.Views;
|
||||
/// </summary>
|
||||
public partial class FocusView : UserControl
|
||||
{
|
||||
private const double BarsTotalSize = 116; // Size of the bars on one side of the screen.
|
||||
|
||||
private readonly IApplicationLauncher applicationLauncher;
|
||||
private readonly IGuildwarsMemoryReader guildwarsMemoryReader;
|
||||
private readonly IExperienceCalculator experienceCalculator;
|
||||
private readonly IViewManager viewManager;
|
||||
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
|
||||
private readonly ILogger<FocusView> logger;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private GameData gameData;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private double currentExperienceInLevel;
|
||||
[GenerateDependencyProperty]
|
||||
private double nextLevelExperienceThreshold;
|
||||
[GenerateDependencyProperty]
|
||||
private string experienceBarText = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private double currentKurzick;
|
||||
[GenerateDependencyProperty]
|
||||
private double totalKurzick;
|
||||
[GenerateDependencyProperty]
|
||||
private double currentLuxon;
|
||||
[GenerateDependencyProperty]
|
||||
private double totalLuxon;
|
||||
[GenerateDependencyProperty]
|
||||
private double currentImperial;
|
||||
[GenerateDependencyProperty]
|
||||
private double totalImperial;
|
||||
[GenerateDependencyProperty]
|
||||
private double currentBalthazar;
|
||||
[GenerateDependencyProperty]
|
||||
private double totalBalthazar;
|
||||
[GenerateDependencyProperty]
|
||||
private string luxonBarText = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string kurzickBarText = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string imperialBarText = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string balthazarBarText = string.Empty;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private double currentHealth;
|
||||
[GenerateDependencyProperty]
|
||||
private double maxHealth;
|
||||
[GenerateDependencyProperty]
|
||||
private double currentEnergy;
|
||||
[GenerateDependencyProperty]
|
||||
private double maxEnergy;
|
||||
[GenerateDependencyProperty]
|
||||
private string healthBarText = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string energyBarText = string.Empty;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool vanquishing;
|
||||
[GenerateDependencyProperty]
|
||||
private double foesKilled;
|
||||
[GenerateDependencyProperty]
|
||||
private double totalFoes;
|
||||
[GenerateDependencyProperty]
|
||||
private string vanquishingText = string.Empty;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private double leftSideBarSize;
|
||||
[GenerateDependencyProperty]
|
||||
private double rightSideBarSize;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private string browserAddress = string.Empty;
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private bool browserMaximized = false;
|
||||
|
||||
public FocusView(
|
||||
IApplicationLauncher applicationLauncher,
|
||||
IGuildwarsMemoryReader guildwarsMemoryReader,
|
||||
IExperienceCalculator experienceCalculator,
|
||||
IViewManager viewManager,
|
||||
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions,
|
||||
ILogger<FocusView> logger)
|
||||
{
|
||||
this.applicationLauncher = applicationLauncher.ThrowIfNull();
|
||||
this.guildwarsMemoryReader = guildwarsMemoryReader.ThrowIfNull();
|
||||
this.experienceCalculator = experienceCalculator.ThrowIfNull();
|
||||
this.viewManager = viewManager.ThrowIfNull();
|
||||
this.liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull();
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.gameData = new GameData();
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
this.LeftSideBarSize = 25;
|
||||
this.InitializeBrowser();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == BrowserAddressProperty &&
|
||||
this.Browser.BrowserEnabled &&
|
||||
this.cancellationTokenSource is not null) //CancellationToken is null only when the view is unloaded. Using this to not overwrite the previous browserurl with the uninitialized value of the address.
|
||||
{
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.BrowserUrl = this.BrowserAddress;
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
}
|
||||
else if (e.Property == VanquishingProperty &&
|
||||
e.OldValue != e.NewValue)
|
||||
{
|
||||
this.UpdateRightSideBarsLayout();
|
||||
}
|
||||
|
||||
base.OnPropertyChanged(e);
|
||||
}
|
||||
|
||||
private async void InitializeBrowser()
|
||||
{
|
||||
await this.Browser.InitializeDefaultBrowser();
|
||||
@@ -57,60 +152,414 @@ public partial class FocusView : UserControl
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
}
|
||||
|
||||
if (this.guildwarsMemoryReader.Running is false)
|
||||
{
|
||||
this.guildwarsMemoryReader.Initialize(this.applicationLauncher.RunningGuildwarsProcess!);
|
||||
}
|
||||
|
||||
this.Dispatcher.Invoke(() =>
|
||||
{
|
||||
this.GameData = this.guildwarsMemoryReader.GameData;
|
||||
if (this.GameData?.MainPlayer is null ||
|
||||
this.GameData?.User is null ||
|
||||
this.GameData?.Session is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.CurrentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(this.GameData.MainPlayer!.Experience);
|
||||
this.NextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(this.GameData.MainPlayer!.Experience);
|
||||
this.CurrentLuxon = (double)this.GameData.User.CurrentLuxonPoints;
|
||||
this.CurrentKurzick = (double)this.GameData.User.CurrentKurzickPoints;
|
||||
this.CurrentImperial = (double)this.GameData.User.CurrentImperialPoints;
|
||||
this.CurrentBalthazar = (double)this.GameData.User.CurrentBalthazarPoints;
|
||||
this.TotalLuxon = (double)this.GameData.User.MaxLuxonPoints;
|
||||
this.TotalKurzick = (double)this.GameData.User.MaxKurzickPoints;
|
||||
this.TotalImperial = (double)this.GameData.User.MaxImperialPoints;
|
||||
this.TotalBalthazar = (double)this.GameData.User.MaxBalthazarPoints;
|
||||
this.FoesKilled = (double)this.GameData.Session.FoesKilled;
|
||||
this.TotalFoes = (double)this.GameData.Session.FoesKilled + (double)this.GameData.Session.FoesToKill;
|
||||
this.Vanquishing = this.GameData.Session.FoesToKill + this.GameData.Session.FoesKilled > 0U;
|
||||
this.CurrentEnergy = (double)this.GameData.MainPlayer.CurrentEnergy;
|
||||
this.MaxEnergy = (double)this.GameData.MainPlayer.MaxEnergy;
|
||||
this.CurrentHealth = (double)this.GameData.MainPlayer.CurrentHealth;
|
||||
this.MaxHealth = (double)this.GameData.MainPlayer.MaxHealth;
|
||||
this.UpdateExperienceText();
|
||||
this.UpdateLuxonText();
|
||||
this.UpdateKurzickText();
|
||||
this.UpdateImperialText();
|
||||
this.UpdateBalthazarText();
|
||||
this.UpdateVanquishingText();
|
||||
this.UpdateHealthText();
|
||||
this.UpdateEnergyText();
|
||||
},
|
||||
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
private void ShowInfoBrowser()
|
||||
private void UpdateRightSideBarsLayout()
|
||||
{
|
||||
if (this.Browser.BrowserSupported is true)
|
||||
// If vanquishing, there's 3 bars, otherwise there's only 2 bars
|
||||
var bars = this.Vanquishing ? 3 : 2;
|
||||
var marginsTotalSize = 4 * bars;
|
||||
var finalBarSize = (BarsTotalSize - marginsTotalSize) / bars;
|
||||
this.RightSideBarSize = finalBarSize;
|
||||
}
|
||||
|
||||
private void UpdateExperienceText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay)
|
||||
{
|
||||
this.Browser.Width = 400;
|
||||
case Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax:
|
||||
var currentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(this.GameData.MainPlayer!.Experience);
|
||||
var nextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(this.GameData.MainPlayer!.Experience);
|
||||
this.ExperienceBarText = $"{currentExperienceInLevel} / {nextLevelExperienceThreshold} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax:
|
||||
var currentTotalExperience = this.GameData.MainPlayer!.Experience;
|
||||
var requiredTotalExperience = this.experienceCalculator.GetTotalExperienceForNextLevel(currentTotalExperience);
|
||||
this.ExperienceBarText = $"{currentTotalExperience} / {requiredTotalExperience} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel:
|
||||
var remainingExperience = this.experienceCalculator.GetRemainingExperienceForNextLevel(this.GameData.MainPlayer!.Experience);
|
||||
this.ExperienceBarText = $"Remaining {remainingExperience} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.Percentage:
|
||||
var currentExperienceInLevel2 = this.experienceCalculator.GetExperienceForCurrentLevel(this.GameData.MainPlayer!.Experience);
|
||||
var nextLevelExperienceThreshold2 = this.experienceCalculator.GetNextExperienceThreshold(this.GameData.MainPlayer!.Experience);
|
||||
this.ExperienceBarText = $"{(int)((double)currentExperienceInLevel2 / (double)nextLevelExperienceThreshold2 * 100)}% XP";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HideInfoBrowser()
|
||||
private void UpdateLuxonText()
|
||||
{
|
||||
if (this.Browser.BrowserSupported is true)
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.LuxonPointsDisplay)
|
||||
{
|
||||
this.Browser.Width = 0;
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.LuxonBarText = $"{this.CurrentLuxon} / {this.TotalLuxon} Luxon Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.LuxonBarText = $"Remaining {this.TotalLuxon - this.CurrentLuxon} Luxon Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.LuxonBarText = $"{(int)(this.CurrentLuxon / this.TotalLuxon * 100)}% Luxon Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void FocusView_Loaded(object sender, System.Windows.RoutedEventArgs e)
|
||||
private void UpdateKurzickText()
|
||||
{
|
||||
if (!this.guildwarsMemoryReader.Running)
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay)
|
||||
{
|
||||
this.guildwarsMemoryReader.Initialize(this.applicationLauncher.RunningGuildwarsProcess!);
|
||||
}
|
||||
|
||||
if (this.guildwarsMemoryReader.TargetProcess?.MainModule?.FileName != this.applicationLauncher.RunningGuildwarsProcess?.MainModule?.FileName)
|
||||
{
|
||||
this.guildwarsMemoryReader.Initialize(this.applicationLauncher.RunningGuildwarsProcess!);
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.KurzickBarText = $"{this.CurrentKurzick} / {this.TotalKurzick} Kurzick Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.KurzickBarText = $"Remaining {this.TotalKurzick - this.CurrentKurzick} Kurzick Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.KurzickBarText = $"{(int)(this.CurrentKurzick / this.TotalKurzick * 100)}% Kurzick Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImperialText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.ImperialBarText = $"{this.CurrentImperial} / {this.TotalImperial} Imperial Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.ImperialBarText = $"Remaining {this.TotalImperial - this.CurrentImperial} Imperial Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.ImperialBarText = $"{(int)(this.CurrentImperial / this.TotalImperial * 100)}% Imperial Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBalthazarText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.BalthazarBarText = $"{this.CurrentBalthazar} / {this.TotalBalthazar} Balthazar Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.BalthazarBarText = $"Remaining {this.TotalBalthazar - this.CurrentBalthazar} Balthazar Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.BalthazarBarText = $"{(int)(this.CurrentBalthazar / this.TotalBalthazar * 100)}% Balthazar Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVanquishingText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.VanquishingDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.VanquishingText = $"{this.FoesKilled} / {this.TotalFoes} Foes Killed";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.VanquishingText = $"Remaining {this.TotalFoes - this.FoesKilled} Foes";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.VanquishingText = $"{(int)(this.FoesKilled / this.TotalFoes * 100)}% Foes Killed";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHealthText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.HealthDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.HealthBarText = $"{this.CurrentHealth} / {this.MaxHealth} Health";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.HealthBarText = $"Remaining {this.CurrentHealth} Health";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.HealthBarText = $"{(int)(this.CurrentHealth / this.MaxHealth * 100)}% Health";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEnergyText()
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.EnergyBarText = $"{this.CurrentEnergy} / {this.MaxEnergy} Energy";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.EnergyBarText = $"Remaining {this.CurrentEnergy} Energy";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.EnergyBarText = $"{(int)(this.CurrentEnergy / this.MaxEnergy * 100)}% Energy";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void FocusView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.UpdateRightSideBarsLayout();
|
||||
this.BrowserAddress = this.liveUpdateableOptions.Value.FocusViewOptions.BrowserUrl;
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
TaskExtensions.RunPeriodicAsync(this.UpdateGameData, TimeSpan.Zero, TimeSpan.FromSeconds(1), this.cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void FocusView_Unloaded(object sender, System.Windows.RoutedEventArgs e)
|
||||
private void FocusView_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
this.cancellationTokenSource = null;
|
||||
this.guildwarsMemoryReader?.Stop();
|
||||
}
|
||||
|
||||
private void Quest_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
private void ExperienceBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
this.ShowInfoBrowser();
|
||||
this.Browser.Address = this.GameData?.Quest?.WikiUrl?.ToString()!;
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay)
|
||||
{
|
||||
case Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax;
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel;
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ExperienceDisplay = Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateExperienceText();
|
||||
}
|
||||
|
||||
private void FocusView_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
private void LuxonBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
this.HideInfoBrowser();
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.LuxonPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.LuxonPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateLuxonText();
|
||||
}
|
||||
|
||||
private void KurzickBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateKurzickText();
|
||||
}
|
||||
|
||||
private void ImperialBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateImperialText();
|
||||
}
|
||||
|
||||
private void BalthazarBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateBalthazarText();
|
||||
}
|
||||
|
||||
private void VanquishingBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.VanquishingDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.VanquishingDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.VanquishingDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.VanquishingDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateVanquishingText();
|
||||
}
|
||||
|
||||
private void HealthBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.HealthDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.HealthDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.HealthDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.HealthDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateHealthText();
|
||||
}
|
||||
|
||||
private void EnergyBar_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.PointsDisplay.Remaining;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.PointsDisplay.Percentage;
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.PointsDisplay.CurrentAndMax;
|
||||
break;
|
||||
}
|
||||
|
||||
this.liveUpdateableOptions.UpdateOption();
|
||||
this.UpdateEnergyText();
|
||||
}
|
||||
|
||||
private void CurrentQuest_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (this.GameData.MainPlayer?.Quest?.WikiUrl is string url)
|
||||
{
|
||||
this.BrowserAddress = url;
|
||||
}
|
||||
}
|
||||
|
||||
private void Browser_MaximizeClicked(object sender, EventArgs e)
|
||||
{
|
||||
this.browserMaximized = !this.browserMaximized;
|
||||
if (this.browserMaximized)
|
||||
{
|
||||
Grid.SetRow(this.Browser, 0);
|
||||
Grid.SetColumn(this.Browser, 0);
|
||||
Grid.SetRowSpan(this.Browser, int.MaxValue);
|
||||
Grid.SetColumnSpan(this.Browser, int.MaxValue);
|
||||
this.Browser.Margin = new Thickness(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grid.SetRow(this.Browser, 1);
|
||||
Grid.SetColumn(this.Browser, 2);
|
||||
Grid.SetRowSpan(this.Browser, 1);
|
||||
Grid.SetColumnSpan(this.Browser, 1);
|
||||
this.Browser.Margin = new Thickness(10, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void QuestLogTemplate_MapClicked(object _, Map e)
|
||||
{
|
||||
if (e is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.BrowserAddress = e.WikiUrl;
|
||||
}
|
||||
|
||||
private void QuestLogTemplate_QuestClicked(object _, Quest e)
|
||||
{
|
||||
if (e is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.BrowserAddress = e.WikiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid
|
||||
Background="#A0202020">
|
||||
Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
|
||||
@@ -24,20 +24,23 @@
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:OpaqueButton Text="Launch game" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
|
||||
<controls:OpaqueButton Text="Launch game" Highlight="White" TransparentBackground="#F0212121" HighlightOpacity="0.3" BackgroundOpacity="1"
|
||||
Foreground="White" FontSize="36" Width="250" Height="60" VerticalAlignment="Bottom"
|
||||
IsEnabled="{Binding ElementName=_this, Path=LaunchButtonEnabled, Mode=OneWay}"
|
||||
Clicked="LaunchButton_Clicked" Grid.Row="0" Grid.ColumnSpan="3"
|
||||
Cursor="Hand"
|
||||
Visibility="{Binding ElementName=_this, Path=ButtonsVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"></controls:OpaqueButton>
|
||||
<controls:OpaqueButton Text="Launch toolbox" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
|
||||
<controls:OpaqueButton Text="Launch toolbox" Highlight="White" TransparentBackground="#F0212121" HighlightOpacity="0.3" BackgroundOpacity="1"
|
||||
Foreground="White" FontSize="24" Width="200" Height="40" Margin="0, 10, 0, 10"
|
||||
IsEnabled="{Binding ElementName=_this, Path=LaunchToolboxButtonEnabled, Mode=OneWay}"
|
||||
Clicked="LaunchToolboxButton_Clicked" Grid.Row="1" Grid.ColumnSpan="2"
|
||||
Cursor="Hand"
|
||||
Visibility="{Binding ElementName=_this, Path=ButtonsVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"></controls:OpaqueButton>
|
||||
<controls:OpaqueButton Text="Launch texmod" Highlight="White" TransparentBackground="Black" HighlightOpacity="0.3" BackgroundOpacity="0.2"
|
||||
<controls:OpaqueButton Text="Launch texmod" Highlight="White" TransparentBackground="#F0212121" HighlightOpacity="0.3" BackgroundOpacity="1"
|
||||
Foreground="White" FontSize="24" Width="200" Height="40" Margin="0, 10, 0, 10"
|
||||
IsEnabled="{Binding ElementName=_this, Path=LaunchTexmodButtonEnabled, Mode=OneWay}"
|
||||
Clicked="LaunchTexmodButton_Clicked" Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="1"
|
||||
Cursor="Hand"
|
||||
Visibility="{Binding ElementName=_this, Path=ButtonsVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"></controls:OpaqueButton>
|
||||
<Grid Grid.Column="2" Margin="10" Visibility="{Binding ElementName=_this, Path=BrowsersEnabled, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}">
|
||||
<controls:ChromiumBrowserWrapper x:Name="RightWebBrowser"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Grid Background="#A0202020">
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Background="#A0202020">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
|
||||
</UserControl.Resources>
|
||||
<Grid Background="#F0212121">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
@@ -40,5 +43,9 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<controls:CircularLoadingWidget Grid.RowSpan="4"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Visibility="{Binding ElementName=_this, Path=Loading, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"></controls:CircularLoadingWidget>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -25,6 +25,9 @@ public partial class VersionManagementView : UserControl
|
||||
[GenerateDependencyProperty]
|
||||
private Version currentVersion;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool loading;
|
||||
|
||||
public ObservableCollection<Version> Versions { get; } = new();
|
||||
|
||||
public VersionManagementView(
|
||||
@@ -41,7 +44,9 @@ public partial class VersionManagementView : UserControl
|
||||
|
||||
private async void LoadVersionList()
|
||||
{
|
||||
this.Loading = true;
|
||||
this.Versions.ClearAnd().AddRange((await this.applicationUpdater.GetVersions()).Reverse());
|
||||
this.Loading = false;
|
||||
}
|
||||
|
||||
private void CurrentVersion_Clicked(object sender, EventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user