Compare commits

...
2 Commits
Author SHA1 Message Date
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
39 changed files with 2841 additions and 259 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,8 @@
namespace Daybreak.Configuration.FocusView;
public enum EnergyDisplay
{
TotalCurretAndTotalMax,
CurrentLevelCurrentAndCurrentLevelMax,
RemainingUntilNextLevel
}
@@ -0,0 +1,19 @@
using Newtonsoft.Json;
namespace Daybreak.Configuration.FocusView;
public sealed class FocusViewOptions
{
[JsonProperty("EnergyDisplay")]
public EnergyDisplay EnergyDisplay { 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("BrowserUrl")]
public string? BrowserUrl { get; set; } = string.Empty;
}
@@ -0,0 +1,7 @@
namespace Daybreak.Configuration.FocusView;
public enum PointsDisplay
{
CurrentAndMax,
Remaining
}
@@ -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)
@@ -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);
+37
View File
@@ -0,0 +1,37 @@
<UserControl x:Class="Daybreak.Controls.ResourceBar"
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="Left"
Width="{Binding ElementName=_this, Path=MaxResourceValue, Mode=TwoWay}"></Rectangle>
<Rectangle
Fill="#A0202020"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"></Rectangle>
<Rectangle
Fill="{Binding ElementName=_this, Path=BarColor, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Left"
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>
+30
View File
@@ -0,0 +1,30 @@
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 ResourceBar : UserControl
{
[GenerateDependencyProperty]
private double currentResourceValue;
[GenerateDependencyProperty]
private double maxResourceValue;
[GenerateDependencyProperty]
private Brush barColor;
[GenerateDependencyProperty]
private string text;
public ResourceBar()
{
this.InitializeComponent();
this.MaxResourceValue = 1;
this.CurrentResourceValue = 0;
this.barColor = Brushes.Transparent;
this.text = string.Empty;
}
}
@@ -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,159 @@
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.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 TextBlock
{
Text = grouping.Key?.Name ?? UncategorizedQuestsString,
FontSize = 18,
Margin = new Thickness(5),
Cursor = grouping.Key is null ? Cursors.Arrow : Cursors.Hand,
HorizontalAlignment = HorizontalAlignment.Left
};
locationTextBlock.MouseLeftButtonDown += (_, _) => this.OnMapClicked(location);
this.ItemStackPanel.Children.Add(locationTextBlock);
foreach(var questMetadata in grouping)
{
var quest = questMetadata.Quest;
var questTextBlock = new TextBlock
{
Text = quest?.Name,
FontSize = 20,
Cursor = Cursors.Hand,
HorizontalAlignment = HorizontalAlignment.Left
};
questTextBlock.MouseLeftButtonDown += (_, _) => this.OnQuestClicked(quest);
this.ItemStackPanel.Children.Add(questTextBlock);
}
var rectangle = new Rectangle
{
Height = 1,
Margin = new Thickness(0, 0, 0, 10)
};
var rectangleForegroundBinding = new Binding("Foreground")
{
Source = this
};
rectangle.SetBinding(Rectangle.FillProperty, rectangleForegroundBinding);
this.ItemStackPanel.Children.Add(rectangle);
}
}
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);
}
}
+1 -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.2</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
</PropertyGroup>
-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;
}
+12
View File
@@ -0,0 +1,12 @@
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; }
}
@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Daybreak.Models.Guildwars;
public sealed class MainPlayerInformation : PlayerInformation
{
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;
}
+39
View File
@@ -0,0 +1,39 @@
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;
// From here, all fields are only valid for living entities.
[FieldOffset(0xC4)]
public readonly uint OwnerId;
[FieldOffset(0x114)]
public readonly float EnergyRegen;
[FieldOffset(0x11C)]
public readonly float Energy;
[FieldOffset(0x120)]
public readonly uint MaxEnergy;
[FieldOffset(0x128)]
public readonly float HealthRegen;
[FieldOffset(0x130)]
public readonly float Health;
[FieldOffset(0x134)]
public readonly uint MaxHealth;
[FieldOffset(0x1BC)]
public readonly ushort H01BC;
}
+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;
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace Daybreak.Models.Interop;
[StructLayout(LayoutKind.Explicit)]
public readonly struct GlobalContext
{
[FieldOffset(0x002C)]
public readonly IntPtr CharacterContext;
[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,37 @@
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;
[FieldOffset(0x08)]
public readonly float EnergyRegen;
[FieldOffset(0x0C)]
public readonly int SkillTimestamp;
[FieldOffset(0x20)]
public readonly float CurrentHealth;
[FieldOffset(0x24)]
public readonly float MaxHealth;
[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,115 @@
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 (currentTotalExperience >= threshold)
{
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;
foreach (var threshold in ExperienceThreshold)
{
if (currentTotalExperience < threshold)
{
totalXpForNextLevel = threshold;
break;
}
}
return totalXpForNextLevel;
}
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)
@@ -48,15 +57,13 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
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.ResilientBeginScanner(scoppedLogger, process!);
}
if (!this.memoryScanner.Scanning)
{
scoppedLogger.LogInformation("Initializing scanner");
this.memoryScanner.BeginScanner(process!);
await this.ResilientBeginScanner(scoppedLogger, process!);
}
this.cancellationTokenSource?.Cancel();
@@ -70,11 +77,29 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader, IDisposable
this.cancellationTokenSource?.Cancel();
}
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.ReadGameMemory(cancellationTokenSource.Token), TimeSpan.Zero, TimeSpan.FromSeconds(1), cancellationTokenSource.Token);
}
private void ReadGameMemory(CancellationToken cancellationToken)
@@ -93,42 +118,166 @@ 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.CharacterContext + 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);
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);
var email = ParseAndCleanWCharArray(charContext.PlayerEmailBytes);
var name = ParseAndCleanWCharArray(charContext.PlayerNameBytes);
_ = Quest.TryParse((int)worldContext.QuestId, out var quest);
var email = ParseAndCleanWCharArray(userContext.PlayerEmailBytes);
var name = ParseAndCleanWCharArray(userContext.PlayerNameBytes);
this.GameData = new GameData
this.GameData = AggregateGameData(gameContext, mapEntities, 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 static GameData AggregateGameData(
GameContext gameContext,
MapEntityContext[] entities,
ProfessionsContext[] professions,
QuestContext[] quests,
string email,
string name,
int mainPlayerEntityId)
{
var partyMembers = professions
.Where(p => p.AgentId != mainPlayerEntityId)
.Select(p => GetPlayerInformation((int)p.AgentId, entities, professions))
.ToList();
var mainPlayer = GetMainPlayerInformation(mainPlayerEntityId, name, gameContext, entities, professions, quests);
var userInformation = new UserInformation
{
CharacterName = name,
Email = email,
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
};
}
private static MainPlayerInformation GetMainPlayerInformation(
int mainPlayerId,
string name,
GameContext gameContext,
MapEntityContext[] entities,
ProfessionsContext[] professions,
QuestContext[] quests)
{
var playerInformation = GetPlayerInformation(mainPlayerId, 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,
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
QuestLog = questLog,
Name = name,
Experience = gameContext.Experience,
Level = gameContext.Level,
Morale = gameContext.Morale
};
}
private static PlayerInformation GetPlayerInformation(
int playerId,
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 != Profession.None)
.OrderBy(p => p.Id)
.ToList();
return new PlayerInformation
{
PrimaryProfession = primaryProfession,
SecondaryProfession = secondaryProfession,
UnlockedProfession = unlockedProfessions,
CurrentHealth = entityContext.CurrentHealth,
CurrentEnergy = entityContext.CurrentEnergy,
MaxHealth = entityContext.MaxHealth,
MaxEnergy = entityContext.MaxEnergy,
HealthRegen = entityContext.HealthRegen,
EnergyRegen = entityContext.EnergyRegen
};
}
+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);
}
+42 -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;
@@ -118,6 +121,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 +186,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 +204,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)
+130 -93
View File
@@ -5,116 +5,153 @@
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"
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.1*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="0.1*"></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
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition Height="0.6*"></RowDefinition>
<RowDefinition Height="0.2*"></RowDefinition>
<RowDefinition Height="0.1*"></RowDefinition>
</Grid.RowDefinitions>
<controls:ResourceBar
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="2"
Height="30"
CurrentResourceValue="{Binding ElementName=_this, Path=CurrentExperienceInLevel, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=NextLevelExperienceThreshold, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
BarColor="#a026ff00"
Foreground="White"
FontSize="22"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=ExperienceBarText, Mode=OneWay}"
MouseLeftButtonDown="ExperienceBar_MouseLeftButtonDown"></controls:ResourceBar>
<controls:ResourceBar
Grid.Column="1"
Grid.Row="2"
Margin="0, 0, 20, 80"
Height="30"
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.User.CurrentKurzickPoints, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=GameData.User.TotalKurzickPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="#a00b00ff"
Foreground="White"
FontSize="22"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=KurzickBarText, Mode=OneWay}"
MouseLeftButtonDown="KurzickBar_MouseLeftButtonDown"></controls:ResourceBar>
<controls:ResourceBar
Grid.Column="1"
Grid.Row="2"
Margin="0, 0, 20, 40"
Height="30"
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.User.CurrentLuxonPoints, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=GameData.User.TotalLuxonPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="#a0ff3400"
Foreground="White"
FontSize="22"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=LuxonBarText, Mode=OneWay}"
MouseLeftButtonDown="LuxonBar_MouseLeftButtonDown"></controls:ResourceBar>
<controls:ResourceBar
Grid.Column="2"
Grid.Row="2"
Margin="20, 0, 0, 80"
Height="30"
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.User.CurrentImperialPoints, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=GameData.User.TotalImperialPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="#a0a800ff"
Foreground="White"
FontSize="22"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=ImperialBarText, Mode=OneWay}"
MouseLeftButtonDown="ImperialBar_MouseLeftButtonDown"></controls:ResourceBar>
<controls:ResourceBar
Grid.Column="2"
Grid.Row="2"
Margin="20, 0, 0, 40"
Height="30"
CurrentResourceValue="{Binding ElementName=_this, Path=GameData.User.CurrentBalthazarPoints, Mode=OneWay}"
MaxResourceValue="{Binding ElementName=_this, Path=GameData.User.TotalBalthazarPoints, Mode=OneWay}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
BarColor="#a0ffb200"
Foreground="White"
FontSize="22"
Cursor="Hand"
Text="{Binding ElementName=_this, Path=BalthazarBarText, Mode=OneWay}"
MouseLeftButtonDown="BalthazarBar_MouseLeftButtonDown"></controls:ResourceBar>
<Grid
Grid.Row="1"
Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0, 0, 10, 0">
<TextBlock
FontSize="18"
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
Text="Current Quest"></TextBlock>
<TextBlock
Text="{Binding ElementName=_this, Path=GameData.MainPlayer.Quest.Name, Mode=OneWay}"
Cursor="Hand"
FontSize="20"
MouseLeftButtonDown="CurrentQuest_MouseLeftButtonDown"></TextBlock>
<Rectangle
Height="1"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Rectangle>
<TextBlock
FontSize="18"
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>
Text="Quest Log"></TextBlock>
</StackPanel>
</ScrollViewer>
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Grid.Row="1">
<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="10, 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>
+250 -14
View File
@@ -1,12 +1,17 @@
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.Extensions;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -19,23 +24,49 @@ public partial class FocusView : UserControl
{
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 string luxonBarText = string.Empty;
[GenerateDependencyProperty]
private string kurzickBarText = string.Empty;
[GenerateDependencyProperty]
private string imperialBarText = string.Empty;
[GenerateDependencyProperty]
private string balthazarBarText = string.Empty;
[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();
@@ -44,6 +75,19 @@ public partial class FocusView : UserControl
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();
}
base.OnPropertyChanged(e);
}
private async void InitializeBrowser()
{
await this.Browser.InitializeDefaultBrowser();
@@ -60,28 +104,99 @@ public partial class FocusView : UserControl
this.Dispatcher.Invoke(() =>
{
this.GameData = this.guildwarsMemoryReader.GameData;
if (this.GameData?.MainPlayer is null)
{
return;
}
this.CurrentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(this.GameData.MainPlayer!.Experience);
this.NextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(this.GameData.MainPlayer!.Experience);
this.UpdateExperienceText();
this.UpdateLuxonText();
this.UpdateKurzickText();
this.UpdateImperialText();
this.UpdateBalthazarText();
},
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}
private void ShowInfoBrowser()
private void UpdateExperienceText()
{
if (this.Browser.BrowserSupported is true)
switch (this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay)
{
this.Browser.Width = 400;
case Configuration.FocusView.EnergyDisplay.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.EnergyDisplay.TotalCurretAndTotalMax:
var currentTotalExperience = this.GameData.MainPlayer!.Experience;
var requiredTotalExperience = this.experienceCalculator.GetTotalExperienceForNextLevel(currentTotalExperience);
this.ExperienceBarText = $"{currentTotalExperience} / {requiredTotalExperience} XP";
break;
case Configuration.FocusView.EnergyDisplay.RemainingUntilNextLevel:
var remainingExperience = this.experienceCalculator.GetRemainingExperienceForNextLevel(this.GameData.MainPlayer!.Experience);
this.ExperienceBarText = $"Remaining {remainingExperience} 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.GameData.User!.CurrentLuxonPoints} / {this.GameData.User!.MaxLuxonPoints} Luxon Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.LuxonBarText = $"Remaining {this.GameData.User!.MaxLuxonPoints - this.GameData.User!.CurrentLuxonPoints} Luxon Points";
break;
}
}
private void FocusView_Loaded(object sender, System.Windows.RoutedEventArgs e)
private void UpdateKurzickText()
{
switch (this.liveUpdateableOptions.Value.FocusViewOptions.KurzickPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.KurzickBarText = $"{this.GameData.User!.CurrentKurzickPoints} / {this.GameData.User!.MaxKurzickPoints} Kurzick Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.KurzickBarText = $"Remaining {this.GameData.User!.MaxKurzickPoints - this.GameData.User!.CurrentKurzickPoints} Kurzick Points";
break;
}
}
private void UpdateImperialText()
{
switch (this.liveUpdateableOptions.Value.FocusViewOptions.ImperialPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.ImperialBarText = $"{this.GameData.User!.CurrentImperialPoints} / {this.GameData.User!.MaxImperialPoints} Imperial Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.ImperialBarText = $"Remaining {this.GameData.User!.MaxImperialPoints - this.GameData.User!.CurrentImperialPoints} Imperial Points";
break;
}
}
private void UpdateBalthazarText()
{
switch (this.liveUpdateableOptions.Value.FocusViewOptions.BalthazarPointsDisplay)
{
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
this.BalthazarBarText = $"{this.GameData.User!.CurrentBalthazarPoints} / {this.GameData.User!.MaxBalthazarPoints} Balthazar Points";
break;
case Configuration.FocusView.PointsDisplay.Remaining:
this.BalthazarBarText = $"Remaining {this.GameData.User!.MaxBalthazarPoints - this.GameData.User!.CurrentBalthazarPoints} Balthazar Points";
break;
}
}
private void FocusView_Loaded(object sender, RoutedEventArgs e)
{
this.BrowserAddress = this.liveUpdateableOptions.Value.FocusViewOptions.BrowserUrl;
if (!this.guildwarsMemoryReader.Running)
{
this.guildwarsMemoryReader.Initialize(this.applicationLauncher.RunningGuildwarsProcess!);
@@ -97,20 +212,141 @@ public partial class FocusView : UserControl
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;
}
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.EnergyDisplay)
{
case Configuration.FocusView.EnergyDisplay.CurrentLevelCurrentAndCurrentLevelMax:
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.EnergyDisplay.TotalCurretAndTotalMax;
break;
case Configuration.FocusView.EnergyDisplay.TotalCurretAndTotalMax:
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.EnergyDisplay.RemainingUntilNextLevel;
break;
case Configuration.FocusView.EnergyDisplay.RemainingUntilNextLevel:
this.liveUpdateableOptions.Value.FocusViewOptions.EnergyDisplay = Configuration.FocusView.EnergyDisplay.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.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.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.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.CurrentAndMax;
break;
}
this.liveUpdateableOptions.UpdateOption();
this.UpdateBalthazarText();
}
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;
}
}