Compare commits

...
5 Commits
Author SHA1 Message Date
amacocianandGitHub 7fa01353dd UX Bugfixes and improvements (#151)
Fix memory reading issues when restarting the game
Fixed current energy and health retrieval and calculation
Added health and energy bars in focus view
Fixed focus view size calculation
Improved buildtemplate performance
Improved buildtemplate navigation
Added loading widgets to menus that are loading asynchronously
2023-03-02 16:49:00 +00:00
amacocianandGitHub 4f849d880b Ux improvements (#148)
Rearrange quest log layout
Fix some quest names
Fix some buttons ux
Mitigate issues when reading guildwars memory
2023-03-02 01:58:07 +00:00
amacocianandGitHub 4b60c96aad Closes #146 #145 (#147) 2023-03-02 00:39:52 +00:00
amacocianandGitHub a4dc0d65cf Implement Focus View (#144)
* Implement Focus View
Read extra information from guildwars

* Prever focusview browser from being disposed
2023-03-01 18:42:53 +00:00
amacocianandGitHub 85e017423f #142 - Fix Vanguard Quests Missing (#143)
Closes #142
2023-02-28 18:24:37 +00:00
63 changed files with 3622 additions and 369 deletions
@@ -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)
+3 -4
View File
@@ -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()
{
+10 -8
View File
@@ -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;
}
}
+8
View File
@@ -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"
+47 -30
View File
@@ -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>
+108 -34
View File
@@ -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;
}
}
+4 -1
View File
@@ -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>
+5 -3
View File
@@ -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>
-14
View File
@@ -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;
}
+13
View File
@@ -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; }
}
+12
View File
@@ -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; }
}
-72
View File
@@ -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; }
}
+14
View File
@@ -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;
}
+8
View File
@@ -0,0 +1,8 @@
namespace Daybreak.Models.Interop;
public enum EntityType
{
Living = 0xDB,
Gadget = 0x200,
Item = 0x400
}
+87
View File
@@ -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;
}
+17
View File
@@ -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;
}
+13
View File
@@ -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;
}
+38
View File
@@ -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;
}
}
+20
View File
@@ -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)
+6 -2
View File
@@ -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);
}
+43 -2
View File
@@ -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)
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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 -1
View File
@@ -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>
+6
View File
@@ -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"/>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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>
+470 -21
View File
@@ -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;
}
}
+1 -1
View File
@@ -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>
+6 -3
View File
@@ -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"
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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 -1
View File
@@ -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)