mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-19 14:55:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bca9f6d66b | ||
|
|
d6f71cb2eb |
@@ -5,10 +5,13 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.FocusViewComponents"
|
||||
xmlns:controls="clr-namespace:Daybreak.Controls"
|
||||
xmlns:converters="clr-namespace:Daybreak.Converters"
|
||||
x:Name="_this"
|
||||
mc:Ignorable="d"
|
||||
Loaded="Control_Loaded"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToGridLengthConverter x:Key="ResourceBarGridLengthConverter" VisibleValue="*" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
@@ -25,7 +28,6 @@
|
||||
Text="{Binding ElementName=_this,Path=ExperienceBarText, Mode=OneWay}"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Experience}"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
Height="113"
|
||||
FontSize="14"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonDown="ExperienceBar_MouseLeftButtonDown">
|
||||
@@ -63,13 +65,13 @@
|
||||
StrokeThickness="0.08"
|
||||
Stroke="{DynamicResource MahApps.Brushes.ThemeForeground}"></Polygon>
|
||||
</Viewbox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2">
|
||||
<StackPanel.OpacityMask>
|
||||
<Grid.OpacityMask>
|
||||
<DrawingBrush>
|
||||
<DrawingBrush.Drawing>
|
||||
<DrawingGroup>
|
||||
@@ -92,12 +94,18 @@
|
||||
</DrawingGroup>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</StackPanel.OpacityMask>
|
||||
</Grid.OpacityMask>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=User.CurrentKurzickPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.MaxKurzickPoints, Mode=OneWay}"
|
||||
Grid.Row="0"
|
||||
CurrentResourceValue="{Binding Path=User.User.CurrentKurzickPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.User.MaxKurzickPoints, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Kurzick}"
|
||||
@@ -110,9 +118,9 @@
|
||||
MouseLeftButtonDown="KurzickBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=User.CurrentLuxonPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.MaxLuxonPoints, Mode=OneWay}"
|
||||
Grid.Row="1"
|
||||
CurrentResourceValue="{Binding Path=User.User.CurrentLuxonPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.User.MaxLuxonPoints, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Luxon}"
|
||||
@@ -125,9 +133,9 @@
|
||||
MouseLeftButtonDown="LuxonBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=User.CurrentImperialPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.MaxImperialPoints, Mode=OneWay}"
|
||||
Grid.Row="2"
|
||||
CurrentResourceValue="{Binding Path=User.User.CurrentImperialPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.User.MaxImperialPoints, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Imperial}"
|
||||
@@ -140,9 +148,9 @@
|
||||
MouseLeftButtonDown="ImperialBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=LeftSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=User.CurrentBalthazarPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.MaxBalthazarPoints, Mode=OneWay}"
|
||||
Grid.Row="3"
|
||||
CurrentResourceValue="{Binding Path=User.User.CurrentBalthazarPoints, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=User.User.MaxBalthazarPoints, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Balthazar}"
|
||||
@@ -153,7 +161,7 @@
|
||||
BorderThickness="1"
|
||||
Text="{Binding ElementName=_this, Path=BalthazarBarText, Mode=OneWay}"
|
||||
MouseLeftButtonDown="BalthazarBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Viewbox
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
@@ -166,14 +174,14 @@
|
||||
Height="10.2"
|
||||
Stroke="{DynamicResource MahApps.Brushes.ThemeForeground}"></Polygon>
|
||||
</Viewbox>
|
||||
<StackPanel
|
||||
<Grid
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Column="3"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="2"
|
||||
Grid.RowSpan="2">
|
||||
<StackPanel.OpacityMask>
|
||||
<Grid.OpacityMask>
|
||||
<DrawingBrush>
|
||||
<DrawingBrush.Drawing>
|
||||
<DrawingGroup>
|
||||
@@ -196,11 +204,17 @@
|
||||
</DrawingGroup>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</StackPanel.OpacityMask>
|
||||
</Grid.OpacityMask>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{Binding ElementName=_this, Path=Vanquishing, Mode=OneWay, Converter={StaticResource ResourceBarGridLengthConverter}}"></RowDefinition>
|
||||
<RowDefinition Height="{Binding ElementName=_this, Path=TitleActive, Mode=OneWay, Converter={StaticResource ResourceBarGridLengthConverter}}"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=Session.FoesKilled, Mode=OneWay}"
|
||||
Grid.Row="0"
|
||||
CurrentResourceValue="{Binding Path=Session.Session.FoesKilled, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=TotalFoes, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
@@ -216,7 +230,7 @@
|
||||
MouseLeftButtonDown="VanquishingBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
Grid.Row="1"
|
||||
CurrentResourceValue="{Binding ElementName=_this, Path=PointsInCurrentRank, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding ElementName=_this, Path=PointsForNextRank, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
@@ -232,9 +246,9 @@
|
||||
Visibility="{Binding ElementName=_this, Path=TitleActive, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=MainPlayer.CurrentHealth, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=MainPlayer.MaxHealth, Mode=OneWay}"
|
||||
Grid.Row="2"
|
||||
CurrentResourceValue="{Binding Path=Player.PlayerInformation.CurrentHealth, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=Player.PlayerInformation.MaxHealth, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Health}"
|
||||
@@ -248,9 +262,9 @@
|
||||
MouseLeftButtonDown="HealthBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
<controls:HorizontalResourceBar
|
||||
Margin="0, 2, 0, 2"
|
||||
Height="{Binding ElementName=_this, Path=RightSideBarSize, Mode=OneWay}"
|
||||
CurrentResourceValue="{Binding Path=MainPlayer.CurrentEnergy, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=MainPlayer.MaxEnergy, Mode=OneWay}"
|
||||
Grid.Row="3"
|
||||
CurrentResourceValue="{Binding Path=Player.PlayerInformation.CurrentEnergy, Mode=OneWay}"
|
||||
MaxResourceValue="{Binding Path=Player.PlayerInformation.MaxEnergy, Mode=OneWay}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
BarColor="{DynamicResource Daybreak.Brushes.Energy}"
|
||||
@@ -262,7 +276,7 @@
|
||||
BorderBrush="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
BorderThickness="1"
|
||||
MouseLeftButtonDown="EnergyBar_MouseLeftButtonDown"></controls:HorizontalResourceBar>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Viewbox
|
||||
Grid.Column="3"
|
||||
Grid.ColumnSpan="2"
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace Daybreak.Controls.FocusViewComponents;
|
||||
/// </summary>
|
||||
public partial class PlayerResourcesComponent : UserControl
|
||||
{
|
||||
private const double BarsTotalSize = 116; // Size of the bars on one side of the screen.
|
||||
|
||||
private readonly IExperienceCalculator experienceCalculator;
|
||||
private readonly ILiveUpdateableOptions<FocusViewOptions> liveOptions;
|
||||
|
||||
@@ -58,11 +56,6 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
[GenerateDependencyProperty]
|
||||
private string vanquishingText = string.Empty;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private double leftSideBarSize;
|
||||
[GenerateDependencyProperty]
|
||||
private double rightSideBarSize;
|
||||
|
||||
public PlayerResourcesComponent()
|
||||
: this(
|
||||
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IExperienceCalculator>(),
|
||||
@@ -77,34 +70,17 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
this.experienceCalculator = experienceCalculator.ThrowIfNull();
|
||||
this.liveOptions = liveOptions.ThrowIfNull();
|
||||
this.InitializeComponent();
|
||||
|
||||
this.LeftSideBarSize = 25;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
if (e.Property == VanquishingProperty &&
|
||||
e.OldValue != e.NewValue)
|
||||
{
|
||||
this.UpdateRightSideBarsLayout();
|
||||
}
|
||||
else if (e.Property == TitleActiveProperty &&
|
||||
e.OldValue != e.NewValue)
|
||||
{
|
||||
this.UpdateRightSideBarsLayout();
|
||||
}
|
||||
else if (e.Property == DataContextProperty)
|
||||
if (e.Property == DataContextProperty)
|
||||
{
|
||||
this.UpdateGameData();
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.UpdateRightSideBarsLayout();
|
||||
}
|
||||
|
||||
private void ExperienceBar_MouseLeftButtonDown(object _, MouseButtonEventArgs e)
|
||||
{
|
||||
switch (this.liveOptions.Value.ExperienceDisplay)
|
||||
@@ -260,40 +236,23 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
this.UpdateEnergyText();
|
||||
}
|
||||
|
||||
private void UpdateRightSideBarsLayout()
|
||||
{
|
||||
var bars = 2;
|
||||
if (this.Vanquishing)
|
||||
{
|
||||
bars += 1;
|
||||
}
|
||||
|
||||
if (this.TitleActive)
|
||||
{
|
||||
bars += 1;
|
||||
}
|
||||
|
||||
var marginsTotalSize = 4 * bars;
|
||||
var finalBarSize = (BarsTotalSize - marginsTotalSize) / bars;
|
||||
this.RightSideBarSize = finalBarSize;
|
||||
}
|
||||
|
||||
private void UpdateGameData()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData ||
|
||||
gameData.MainPlayer is null ||
|
||||
gameData.Session is null)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.User?.User is null ||
|
||||
mainPlayerResourceContext.Session?.Session is null ||
|
||||
mainPlayerResourceContext.Player?.PlayerInformation is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.CurrentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(gameData.MainPlayer.Experience);
|
||||
this.NextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(gameData.MainPlayer.Experience);
|
||||
this.TotalFoes = (int)(gameData.Session.FoesKilled + gameData.Session.FoesToKill);
|
||||
this.Vanquishing = gameData.Session.FoesToKill + gameData.Session.FoesKilled > 0U;
|
||||
this.TitleActive = gameData.MainPlayer.TitleInformation is not null && gameData.MainPlayer.TitleInformation.IsValid;
|
||||
this.CurrentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
this.NextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
this.TotalFoes = (int)(mainPlayerResourceContext.Session.Session.FoesKilled + mainPlayerResourceContext.Session.Session.FoesToKill);
|
||||
this.Vanquishing = mainPlayerResourceContext.Session.Session.FoesToKill + mainPlayerResourceContext.Session.Session.FoesKilled > 0U;
|
||||
this.TitleActive = mainPlayerResourceContext.Player.PlayerInformation.TitleInformation is not null && mainPlayerResourceContext.Player.PlayerInformation.TitleInformation.IsValid;
|
||||
|
||||
if (gameData.MainPlayer.TitleInformation is TitleInformation titleInformation && titleInformation.IsValid)
|
||||
if (mainPlayerResourceContext.Player.PlayerInformation.TitleInformation is TitleInformation titleInformation && titleInformation.IsValid)
|
||||
{
|
||||
if (titleInformation.MaxTierNumber == titleInformation.TierNumber)
|
||||
{
|
||||
@@ -325,8 +284,8 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
|
||||
private void UpdateExperienceText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData ||
|
||||
gameData.MainPlayer is null)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.Player?.PlayerInformation is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -334,22 +293,22 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.ExperienceDisplay)
|
||||
{
|
||||
case Configuration.FocusView.ExperienceDisplay.CurrentLevelCurrentAndCurrentLevelMax:
|
||||
var currentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(gameData.MainPlayer.Experience);
|
||||
var nextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(gameData.MainPlayer.Experience);
|
||||
var currentExperienceInLevel = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
var nextLevelExperienceThreshold = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
this.ExperienceBarText = $"{(int)currentExperienceInLevel} / {(int)nextLevelExperienceThreshold} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.TotalCurretAndTotalMax:
|
||||
var currentTotalExperience = gameData.MainPlayer.Experience;
|
||||
var currentTotalExperience = mainPlayerResourceContext.Player.PlayerInformation.Experience;
|
||||
var requiredTotalExperience = this.experienceCalculator.GetTotalExperienceForNextLevel(currentTotalExperience);
|
||||
this.ExperienceBarText = $"{(int)currentTotalExperience} / {(int)requiredTotalExperience} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.RemainingUntilNextLevel:
|
||||
var remainingExperience = this.experienceCalculator.GetRemainingExperienceForNextLevel(gameData.MainPlayer.Experience);
|
||||
var remainingExperience = this.experienceCalculator.GetRemainingExperienceForNextLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
this.ExperienceBarText = $"Remaining {(int)remainingExperience} XP";
|
||||
break;
|
||||
case Configuration.FocusView.ExperienceDisplay.Percentage:
|
||||
var currentExperienceInLevel2 = this.experienceCalculator.GetExperienceForCurrentLevel(gameData.MainPlayer.Experience);
|
||||
var nextLevelExperienceThreshold2 = this.experienceCalculator.GetNextExperienceThreshold(gameData.MainPlayer.Experience);
|
||||
var currentExperienceInLevel2 = this.experienceCalculator.GetExperienceForCurrentLevel(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
var nextLevelExperienceThreshold2 = this.experienceCalculator.GetNextExperienceThreshold(mainPlayerResourceContext.Player.PlayerInformation.Experience);
|
||||
this.ExperienceBarText = $"{(int)((double)currentExperienceInLevel2 / (double)nextLevelExperienceThreshold2 * 100)}% XP";
|
||||
break;
|
||||
}
|
||||
@@ -357,7 +316,8 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
|
||||
private void UpdateLuxonText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.User?.User is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -365,20 +325,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.LuxonPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.LuxonBarText = $"{gameData.User!.CurrentLuxonPoints} / {gameData.User.MaxLuxonPoints} Luxon Points";
|
||||
this.LuxonBarText = $"{mainPlayerResourceContext.User.User.CurrentLuxonPoints} / {mainPlayerResourceContext.User.User.MaxLuxonPoints} Luxon Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.LuxonBarText = $"Remaining {gameData.User!.MaxLuxonPoints - gameData.User.CurrentLuxonPoints} Luxon Points";
|
||||
this.LuxonBarText = $"Remaining {mainPlayerResourceContext.User.User.MaxLuxonPoints - mainPlayerResourceContext.User.User.CurrentLuxonPoints} Luxon Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.LuxonBarText = $"{(int)((double)gameData.User!.CurrentLuxonPoints / (double)gameData.User.MaxLuxonPoints * 100)}% Luxon Points";
|
||||
this.LuxonBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentLuxonPoints / (double)mainPlayerResourceContext.User.User.MaxLuxonPoints * 100)}% Luxon Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKurzickText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.User?.User is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -386,20 +347,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.KurzickPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.KurzickBarText = $"{gameData.User!.CurrentKurzickPoints} / {gameData.User.MaxKurzickPoints} Kurzick Points";
|
||||
this.KurzickBarText = $"{mainPlayerResourceContext.User.User.CurrentKurzickPoints} / {mainPlayerResourceContext.User.User.MaxKurzickPoints} Kurzick Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.KurzickBarText = $"Remaining {gameData.User!.MaxKurzickPoints - gameData.User.CurrentKurzickPoints} Kurzick Points";
|
||||
this.KurzickBarText = $"Remaining {mainPlayerResourceContext.User.User!.MaxKurzickPoints - mainPlayerResourceContext.User.User.CurrentKurzickPoints} Kurzick Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.KurzickBarText = $"{(int)((double)gameData.User!.CurrentKurzickPoints / (double)gameData.User.MaxKurzickPoints * 100)}% Kurzick Points";
|
||||
this.KurzickBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentKurzickPoints / (double)mainPlayerResourceContext.User.User.MaxKurzickPoints * 100)}% Kurzick Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImperialText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.User?.User is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -407,20 +369,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.ImperialPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.ImperialBarText = $"{gameData.User!.CurrentImperialPoints} / {gameData.User.MaxImperialPoints} Imperial Points";
|
||||
this.ImperialBarText = $"{mainPlayerResourceContext.User.User!.CurrentImperialPoints} / {mainPlayerResourceContext.User.User.MaxImperialPoints} Imperial Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.ImperialBarText = $"Remaining {gameData.User!.MaxImperialPoints - gameData.User.CurrentImperialPoints} Imperial Points";
|
||||
this.ImperialBarText = $"Remaining {mainPlayerResourceContext.User.User!.MaxImperialPoints - mainPlayerResourceContext.User.User.CurrentImperialPoints} Imperial Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.ImperialBarText = $"{(int)((double)gameData.User!.CurrentImperialPoints / (double)gameData.User.MaxImperialPoints * 100)}% Imperial Points";
|
||||
this.ImperialBarText = $"{(int)((double)mainPlayerResourceContext.User.User!.CurrentImperialPoints / (double)mainPlayerResourceContext.User.User.MaxImperialPoints * 100)}% Imperial Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBalthazarText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.User?.User is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -428,21 +391,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.BalthazarPointsDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.BalthazarBarText = $"{gameData.User!.CurrentBalthazarPoints} / {gameData.User.MaxBalthazarPoints} Balthazar Points";
|
||||
this.BalthazarBarText = $"{mainPlayerResourceContext.User.User.CurrentBalthazarPoints} / {mainPlayerResourceContext.User.User.MaxBalthazarPoints} Balthazar Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.BalthazarBarText = $"Remaining {gameData.User!.MaxBalthazarPoints - gameData.User.CurrentBalthazarPoints} Balthazar Points";
|
||||
this.BalthazarBarText = $"Remaining {mainPlayerResourceContext.User.User.MaxBalthazarPoints - mainPlayerResourceContext.User.User.CurrentBalthazarPoints} Balthazar Points";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.BalthazarBarText = $"{(int)((double)gameData.User!.CurrentBalthazarPoints / (double)gameData.User.MaxBalthazarPoints * 100)}% Balthazar Points";
|
||||
this.BalthazarBarText = $"{(int)((double)mainPlayerResourceContext.User.User.CurrentBalthazarPoints / (double)mainPlayerResourceContext.User.User.MaxBalthazarPoints * 100)}% Balthazar Points";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVanquishingText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData ||
|
||||
gameData.Session is null)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.Session?.Session is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -450,21 +413,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.VanquishingDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.VanquishingText = $"{gameData.Session.FoesKilled} / {(int)this.TotalFoes} Foes Killed";
|
||||
this.VanquishingText = $"{mainPlayerResourceContext.Session.Session.FoesKilled} / {(int)this.TotalFoes} Foes Killed";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.VanquishingText = $"Remaining {gameData.Session.FoesToKill} Foes";
|
||||
this.VanquishingText = $"Remaining {mainPlayerResourceContext.Session.Session.FoesToKill} Foes";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.VanquishingText = $"{(int)((double)gameData.Session.FoesKilled / (double)this.TotalFoes * 100)}% Foes Killed";
|
||||
this.VanquishingText = $"{(int)((double)mainPlayerResourceContext.Session.Session.FoesKilled / (double)this.TotalFoes * 100)}% Foes Killed";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHealthText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData ||
|
||||
gameData.MainPlayer is null)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.Player?.PlayerInformation is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -472,21 +435,21 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.HealthDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.HealthBarText = $"{(int)gameData.MainPlayer.CurrentHealth} / {(int)gameData.MainPlayer.MaxHealth} Health";
|
||||
this.HealthBarText = $"{(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth} / {(int)mainPlayerResourceContext.Player.PlayerInformation.MaxHealth} Health";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.HealthBarText = $"Remaining {(int)gameData.MainPlayer.CurrentHealth} Health";
|
||||
this.HealthBarText = $"Remaining {(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth} Health";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.HealthBarText = $"{(int)(gameData.MainPlayer.CurrentHealth / gameData.MainPlayer.MaxHealth * 100)}% Health";
|
||||
this.HealthBarText = $"{(int)(mainPlayerResourceContext.Player.PlayerInformation.CurrentHealth / mainPlayerResourceContext.Player.PlayerInformation.MaxHealth * 100)}% Health";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEnergyText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData ||
|
||||
gameData.MainPlayer is null)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.Player?.PlayerInformation is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -494,31 +457,32 @@ public partial class PlayerResourcesComponent : UserControl
|
||||
switch (this.liveOptions.Value.EnergyDisplay)
|
||||
{
|
||||
case Configuration.FocusView.PointsDisplay.CurrentAndMax:
|
||||
this.EnergyBarText = $"{(int)gameData.MainPlayer.CurrentEnergy} / {(int)gameData.MainPlayer.MaxEnergy} Energy";
|
||||
this.EnergyBarText = $"{(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy} / {(int)mainPlayerResourceContext.Player.PlayerInformation.MaxEnergy} Energy";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Remaining:
|
||||
this.EnergyBarText = $"Remaining {(int)gameData.MainPlayer.CurrentEnergy} Energy";
|
||||
this.EnergyBarText = $"Remaining {(int)mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy} Energy";
|
||||
break;
|
||||
case Configuration.FocusView.PointsDisplay.Percentage:
|
||||
this.EnergyBarText = $"{(int)(gameData.MainPlayer.CurrentEnergy / gameData.MainPlayer.MaxEnergy * 100)}% Energy";
|
||||
this.EnergyBarText = $"{(int)(mainPlayerResourceContext.Player.PlayerInformation.CurrentEnergy / mainPlayerResourceContext.Player.PlayerInformation.MaxEnergy * 100)}% Energy";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTitleText()
|
||||
{
|
||||
if (this.DataContext is not GameData gameData)
|
||||
if (this.DataContext is not MainPlayerResourceContext mainPlayerResourceContext ||
|
||||
mainPlayerResourceContext.Player?.PlayerInformation is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameData.MainPlayer?.TitleInformation?.IsPercentage is true)
|
||||
if (mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.IsPercentage is true)
|
||||
{
|
||||
this.TitleText = $"{(double?)gameData.MainPlayer?.TitleInformation?.CurrentPoints / 10d}% Rank Progress";
|
||||
this.TitleText = $"{(double?)mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.CurrentPoints / 10d}% Rank Progress";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.TitleText = $"{gameData.MainPlayer?.TitleInformation?.CurrentPoints}/{gameData.MainPlayer?.TitleInformation?.PointsForNextRank} Rank Progress";
|
||||
this.TitleText = $"{mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.CurrentPoints}/{mainPlayerResourceContext.Player.PlayerInformation.TitleInformation?.PointsForNextRank} Rank Progress";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public partial class GuildwarsMinimap : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
this.TargetEntityId = this.GameData.Session?.CurrentTargetId ?? 0;
|
||||
this.TargetEntityId = this.GameData.CurrentTargetId ?? 0;
|
||||
this.TargetEntityModelId = (int?)this.GameData.LivingEntities?.FirstOrDefault(e => e.Id == this.TargetEntityId)?.ModelType ?? 0;
|
||||
var screenVirtualWidth = this.ActualWidth / this.Zoom;
|
||||
var screenVirtualHeight = this.ActualHeight / this.Zoom;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
|
||||
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
|
||||
<Version>0.9.8.78</Version>
|
||||
<Version>0.9.8.80</Version>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
|
||||
@@ -7,9 +7,8 @@ public sealed class GameData
|
||||
public bool Valid { get; init; }
|
||||
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; }
|
||||
public List<LivingEntity>? LivingEntities { get; init; }
|
||||
public List<MapIcon>? MapIcons { get; init; }
|
||||
public int? CurrentTargetId { get; init; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public enum InstanceType
|
||||
{
|
||||
Undefined,
|
||||
Outpost,
|
||||
Explorable,
|
||||
Loading
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public sealed class MainPlayerData
|
||||
{
|
||||
public MainPlayerInformation? PlayerInformation { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
|
||||
public sealed class SessionData
|
||||
{
|
||||
public SessionInformation? Session { get; init; }
|
||||
}
|
||||
@@ -5,6 +5,6 @@ public sealed class SessionInformation
|
||||
public uint FoesKilled { get; init; }
|
||||
public uint FoesToKill { get; init; }
|
||||
public Map? CurrentMap { get; init; }
|
||||
public int? CurrentTargetId { get; init; }
|
||||
public uint InstanceTimer { get; init; }
|
||||
public InstanceType InstanceType { get; init; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Daybreak.Models.Guildwars;
|
||||
public sealed class UserData
|
||||
{
|
||||
public UserInformation? User { get; init; }
|
||||
}
|
||||
@@ -5,47 +5,63 @@ namespace Daybreak.Models.Interop;
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct EntityContext
|
||||
{
|
||||
public const uint EntityContextBaseOffset = 0x0014;
|
||||
|
||||
[FieldOffset(0x0000)]
|
||||
[FieldOffset(0x0014)]
|
||||
public readonly uint Timer;
|
||||
|
||||
[FieldOffset(0x0018)]
|
||||
[FieldOffset(0x002C)]
|
||||
public readonly uint AgentId;
|
||||
|
||||
[FieldOffset(0x001C)]
|
||||
[FieldOffset(0x0030)]
|
||||
public readonly uint ZCoords;
|
||||
|
||||
[FieldOffset(0x0044)]
|
||||
[FieldOffset(0x0058)]
|
||||
public readonly uint NameProperties;
|
||||
|
||||
[FieldOffset(0x0060)]
|
||||
[FieldOffset(0x0074)]
|
||||
public readonly GamePosition Position;
|
||||
|
||||
[FieldOffset(0x0088)]
|
||||
[FieldOffset(0x009C)]
|
||||
public readonly EntityType EntityType;
|
||||
|
||||
[FieldOffset(0x00E0)]
|
||||
[FieldOffset(0x00F4)]
|
||||
public readonly ushort EntityModelType;
|
||||
|
||||
[FieldOffset(0x00E2)]
|
||||
[FieldOffset(0x00F6)]
|
||||
public readonly ushort AgentModelType;
|
||||
|
||||
[FieldOffset(0x00F6)]
|
||||
[FieldOffset(0x010A)]
|
||||
public readonly byte PrimaryProfessionId;
|
||||
|
||||
[FieldOffset(0x00F7)]
|
||||
[FieldOffset(0x010B)]
|
||||
public readonly byte SecondaryProfessionId;
|
||||
|
||||
[FieldOffset(0x00F8)]
|
||||
[FieldOffset(0x010C)]
|
||||
public readonly byte Level;
|
||||
|
||||
[FieldOffset(0x00F9)]
|
||||
[FieldOffset(0x010D)]
|
||||
public readonly PvpTeam TeamId;
|
||||
|
||||
[FieldOffset(0x0144)]
|
||||
[FieldOffset(0x0114)]
|
||||
public readonly float EnergyRegen; // Only works for main player
|
||||
|
||||
[FieldOffset(0x011C)]
|
||||
public readonly float CurrentEnergyPercentage; // Only works for main player
|
||||
|
||||
[FieldOffset(0x0120)]
|
||||
public readonly uint MaxEnergy; // Only works for main player
|
||||
|
||||
[FieldOffset(0x0128)]
|
||||
public readonly float HealthRegen; // Only works for main player
|
||||
|
||||
[FieldOffset(0x0130)]
|
||||
public readonly float CurrentHealthPercentage; // Only works for main player
|
||||
|
||||
[FieldOffset(0x0134)]
|
||||
public readonly uint MaxHealth; // Only works for main player
|
||||
|
||||
[FieldOffset(0x0158)]
|
||||
public readonly EntityState State;
|
||||
|
||||
[FieldOffset(0x019D)]
|
||||
[FieldOffset(0x01B1)]
|
||||
public readonly EntityAllegiance Allegiance;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Daybreak.Models.Guildwars;
|
||||
|
||||
namespace Daybreak.Models;
|
||||
public sealed class MainPlayerResourceContext
|
||||
{
|
||||
public UserData? User { get; init; }
|
||||
public SessionData? Session { get; init; }
|
||||
public MainPlayerData? Player { get; init; }
|
||||
}
|
||||
@@ -19,6 +19,9 @@ public sealed class GuildwarsMemoryCache : IGuildwarsMemoryCache
|
||||
private readonly CachedData<PathingData?> pathingDataCache = new();
|
||||
private readonly CachedData<PathingMetadata?> pathingMetadataCache = new();
|
||||
private readonly CachedData<WorldData?> worldDataCache = new();
|
||||
private readonly CachedData<SessionData?> sessionDataCache = new();
|
||||
private readonly CachedData<UserData?> userDataCache = new();
|
||||
private readonly CachedData<MainPlayerData?> mainPlayerDataCache = new();
|
||||
|
||||
public GuildwarsMemoryCache(
|
||||
IGuildwarsMemoryReader guildwarsMemoryReader,
|
||||
@@ -58,6 +61,21 @@ public sealed class GuildwarsMemoryCache : IGuildwarsMemoryCache
|
||||
return this.ReadDataInternal(this.worldDataCache, this.guildwarsMemoryReader.ReadWorldData, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<SessionData?> ReadSessionData(CancellationToken cancellationToken)
|
||||
{
|
||||
return this.ReadDataInternal(this.sessionDataCache, this.guildwarsMemoryReader.ReadSessionData, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<UserData?> ReadUserData(CancellationToken cancellationToken)
|
||||
{
|
||||
return this.ReadDataInternal(this.userDataCache, this.guildwarsMemoryReader.ReadUserData, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<MainPlayerData?> ReadMainPlayerData(CancellationToken cancellationToken)
|
||||
{
|
||||
return this.ReadDataInternal(this.mainPlayerDataCache, this.guildwarsMemoryReader.ReadMainPlayerData, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<T?> ReadDataInternal<T>(CachedData<T?> cachedData, Func<CancellationToken, Task<T?>> task, CancellationToken cancellationToken)
|
||||
{
|
||||
if (DateTime.Now - cachedData.SetTime <= TimeSpan.FromMilliseconds(this.liveOptions.Value.MemoryReaderFrequency))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Models.Builds;
|
||||
using Daybreak.Models.Builds;
|
||||
using Daybreak.Models.Guildwars;
|
||||
using Daybreak.Models.Interop;
|
||||
using Daybreak.Models.Metrics;
|
||||
@@ -15,7 +14,6 @@ using System.Diagnostics.Metrics;
|
||||
using System.Extensions;
|
||||
using System.Linq;
|
||||
using System.Logging;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -146,6 +144,36 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
return Task.Run(() => this.SafeReadGameMemory(this.ReadWorldDataInternal), cancellationToken);
|
||||
}
|
||||
|
||||
public Task<UserData?> ReadUserData(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.memoryScanner.Scanning is false)
|
||||
{
|
||||
return Task.FromResult<UserData?>(default);
|
||||
}
|
||||
|
||||
return Task.Run(() => this.SafeReadGameMemory(this.ReadUserDataInternal), cancellationToken);
|
||||
}
|
||||
|
||||
public Task<SessionData?> ReadSessionData(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.memoryScanner.Scanning is false)
|
||||
{
|
||||
return Task.FromResult<SessionData?>(default);
|
||||
}
|
||||
|
||||
return Task.Run(() => this.SafeReadGameMemory(this.ReadSessionDataInternal), cancellationToken);
|
||||
}
|
||||
|
||||
public Task<MainPlayerData?> ReadMainPlayerData(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.memoryScanner.Scanning is false)
|
||||
{
|
||||
return Task.FromResult<MainPlayerData?>(default);
|
||||
}
|
||||
|
||||
return Task.Run(() => this.SafeReadGameMemory(this.ReadMainPlayerDataInternal), cancellationToken);
|
||||
}
|
||||
|
||||
private async Task InitializeSafe(Process process, ScopedLogger<GuildwarsMemoryReader> scopedLogger)
|
||||
{
|
||||
if (this.memoryScanner.Process is null ||
|
||||
@@ -208,7 +236,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private LoginData? ReadLoginDataInternal()
|
||||
{
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
@@ -226,26 +254,16 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private GameData? ReadGameDataInternal()
|
||||
{
|
||||
/*
|
||||
* The following offsets were reverse-engineered using pointer scanning. All of the following ones seem to currently work.
|
||||
* If any breaks, try the other ones from the list below.
|
||||
* startAddress + 00629204 -> +0x18
|
||||
* startAddress + 006296B4 -> +0x18
|
||||
* startAddress + 00AA9CE0 -> +0xC -> +0xC
|
||||
*/
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
{
|
||||
return new GameData { Valid = false };
|
||||
return default;
|
||||
}
|
||||
|
||||
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
|
||||
var gameContext = this.memoryScanner.Read(globalContext.GameContext, GameContext.BaseOffset);
|
||||
// UserContext struct is offset by 0x074 due to the memory layout of the structure.
|
||||
var userContext = this.memoryScanner.Read(globalContext.UserContext, UserContext.BaseOffset);
|
||||
// InstanceContext struct is offset by 0x01AC due to memory layout of the structure.
|
||||
var instanceContext = this.memoryScanner.Read(globalContext.InstanceContext, InstanceContext.BaseOffset);
|
||||
|
||||
@@ -258,7 +276,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
gameContext.Titles.Size > 1000 ||
|
||||
gameContext.TitlesTiers.Size > 10000)
|
||||
{
|
||||
return new GameData { Valid = false };
|
||||
return default;
|
||||
}
|
||||
|
||||
var mapEntities = this.memoryScanner.ReadArray(gameContext.MapEntities);
|
||||
@@ -282,17 +300,16 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
}
|
||||
|
||||
var entityPointers = this.memoryScanner.ReadArray<uint>(entityPointersArray.Buffer, entityPointersArray.Size);
|
||||
var entities = entityPointers.Select(ptr => this.memoryScanner.Read<EntityContext>(ptr + EntityContext.EntityContextBaseOffset)).ToArray();
|
||||
var entities = entityPointers.Select(ptr => (ptr, this.memoryScanner.Read<EntityContext>(ptr))).ToArray();
|
||||
|
||||
return this.AggregateGameData(
|
||||
gameContext,
|
||||
instanceContext,
|
||||
mapEntities,
|
||||
entities,
|
||||
entities.Select(e => e.Item2).ToArray(),
|
||||
players,
|
||||
professions,
|
||||
quests,
|
||||
userContext,
|
||||
skills,
|
||||
partyAttributes,
|
||||
titles,
|
||||
@@ -305,7 +322,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private PathingData? ReadPathingDataInternal()
|
||||
{
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
var globalContext = this.GetGlobalContext();
|
||||
var mapContext = this.memoryScanner.Read(globalContext.MapContext);
|
||||
|
||||
var pathingMapContext = this.memoryScanner.ReadPtrChain<PathingMapContext>(mapContext.PathingMapContextPtr, 0x0, 0x0);
|
||||
@@ -363,7 +380,6 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
var adjacentTrapezoid = this.memoryScanner.Read<PathingTrapezoid>(adjacentAddress);
|
||||
trapezoidAdjacencyList.Add((int)adjacentTrapezoid.Id);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +398,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private PathingMetadata? ReadPathingMetaDataInternal()
|
||||
{
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
var globalContext = this.GetGlobalContext();
|
||||
var mapContext = this.memoryScanner.Read(globalContext.MapContext);
|
||||
|
||||
var pathingMapContext = this.memoryScanner.ReadPtrChain<PathingMapContext>(mapContext.PathingMapContextPtr, 0x0, 0x0);
|
||||
@@ -393,7 +409,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private InventoryData? ReadInventoryDataInternal()
|
||||
{
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
@@ -424,8 +440,7 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
|
||||
private WorldData? ReadWorldDataInternal()
|
||||
{
|
||||
var globalContext = this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
@@ -451,6 +466,139 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
};
|
||||
}
|
||||
|
||||
private UserData? ReadUserDataInternal()
|
||||
{
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
|
||||
var gameContext = this.memoryScanner.Read(globalContext.GameContext, GameContext.BaseOffset);
|
||||
// UserContext struct is offset by 0x074 due to the memory layout of the structure.
|
||||
var userContext = this.memoryScanner.Read(globalContext.UserContext, UserContext.BaseOffset);
|
||||
|
||||
var userInformation = new UserInformation
|
||||
{
|
||||
Email = userContext.PlayerEmailFirstChar + (userContext.PlayerEmailSecondChar + userContext.PlayerEmailRemaining),
|
||||
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
|
||||
};
|
||||
|
||||
return new UserData { User = userInformation };
|
||||
}
|
||||
|
||||
private SessionData? ReadSessionDataInternal()
|
||||
{
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
|
||||
var gameContext = this.memoryScanner.Read(globalContext.GameContext, GameContext.BaseOffset);
|
||||
// InstanceContext struct is offset by 0x01AC due to memory layout of the structure.
|
||||
var instanceContext = this.memoryScanner.Read(globalContext.InstanceContext, InstanceContext.BaseOffset);
|
||||
// UserContext struct is offset by 0x074 due to the memory layout of the structure.
|
||||
var userContext = this.memoryScanner.Read(globalContext.UserContext, UserContext.BaseOffset);
|
||||
var instanceInfo = this.memoryScanner.ReadPtrChain<InstanceInfoContext>(this.GetInstanceInfoPointer(), 0x0, 0x0);
|
||||
|
||||
_ = Map.TryParse((int)userContext.MapId, out var currentMap);
|
||||
var sessionInformation = new SessionInformation
|
||||
{
|
||||
FoesKilled = gameContext.FoesKilled,
|
||||
FoesToKill = gameContext.FoesToKill,
|
||||
CurrentMap = currentMap,
|
||||
InstanceTimer = instanceContext.Timer,
|
||||
InstanceType = instanceInfo.InstanceType switch
|
||||
{
|
||||
Daybreak.Models.Interop.InstanceType.Outpost => Daybreak.Models.Guildwars.InstanceType.Outpost,
|
||||
Daybreak.Models.Interop.InstanceType.Explorable => Daybreak.Models.Guildwars.InstanceType.Explorable,
|
||||
Daybreak.Models.Interop.InstanceType.Loading => Daybreak.Models.Guildwars.InstanceType.Loading,
|
||||
_ => Daybreak.Models.Guildwars.InstanceType.Undefined
|
||||
}
|
||||
};
|
||||
|
||||
return new SessionData { Session = sessionInformation };
|
||||
}
|
||||
|
||||
private MainPlayerData? ReadMainPlayerDataInternal()
|
||||
{
|
||||
var globalContext = this.GetGlobalContext();
|
||||
if (!globalContext.GameContext.IsValid() ||
|
||||
!globalContext.UserContext.IsValid() ||
|
||||
!globalContext.InstanceContext.IsValid())
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// GameContext struct is offset by 0x07C due to the memory layout of the structure.
|
||||
var gameContext = this.memoryScanner.Read(globalContext.GameContext, GameContext.BaseOffset);
|
||||
// InstanceContext struct is offset by 0x01AC due to memory layout of the structure.
|
||||
var instanceContext = this.memoryScanner.Read(globalContext.InstanceContext, InstanceContext.BaseOffset);
|
||||
|
||||
var playerEntityId = this.memoryScanner.ReadPtrChain<int>(this.GetPlayerIdPointer(), 0x0, 0x0);
|
||||
var mapEntity = this.memoryScanner.ReadItemAtIndex(gameContext.MapEntities, playerEntityId);
|
||||
var entityPointersArray = this.memoryScanner.ReadPtrChain<GenericGuildwarsArray>(this.GetEntityArrayPointer(), 0x0, 0x0);
|
||||
var entity = this.memoryScanner.ReadPtrChain<EntityContext>((uint)(entityPointersArray.Buffer + (sizeof(uint) * playerEntityId)), 0x0, 0x0);
|
||||
if (mapEntity.MaxHealth != entity.MaxHealth ||
|
||||
mapEntity.MaxEnergy != entity.MaxEnergy ||
|
||||
mapEntity.MaxHealth < 0 || mapEntity.MaxHealth > 10000 ||
|
||||
mapEntity.MaxEnergy < 0 || mapEntity.MaxEnergy > 10000 ||
|
||||
gameContext.Professions.Size == 0 ||
|
||||
gameContext.Players.Size == 0 ||
|
||||
gameContext.Skillbars.Size == 0 ||
|
||||
gameContext.PartyAttributes.Size == 0 ||
|
||||
gameContext.Titles.Size == 0 ||
|
||||
gameContext.TitlesTiers.Size == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var professions = this.memoryScanner.ReadArray(gameContext.Professions);
|
||||
var players = this.memoryScanner.ReadArray(gameContext.Players);
|
||||
var quests = this.memoryScanner.ReadArray(gameContext.QuestLog);
|
||||
var skills = this.memoryScanner.ReadArray(gameContext.Skillbars);
|
||||
var partyAttributes = this.memoryScanner.ReadArray(gameContext.PartyAttributes);
|
||||
var titles = this.memoryScanner.ReadArray(gameContext.Titles);
|
||||
var titleTiers = this.memoryScanner.ReadArray(gameContext.TitlesTiers);
|
||||
var mainPlayerInfo = this.GetMainPlayerInformation(
|
||||
gameContext,
|
||||
instanceContext,
|
||||
players.FirstOrDefault(p => p.AgentId == playerEntityId),
|
||||
mapEntity,
|
||||
professions.FirstOrDefault(p => p.AgentId == playerEntityId),
|
||||
quests,
|
||||
skills.FirstOrDefault(p => p.AgentId == playerEntityId),
|
||||
partyAttributes.FirstOrDefault(p => p.AgentId == playerEntityId),
|
||||
titles,
|
||||
titleTiers,
|
||||
entity);
|
||||
|
||||
return new MainPlayerData
|
||||
{
|
||||
PlayerInformation = mainPlayerInfo
|
||||
};
|
||||
}
|
||||
|
||||
private uint GetPlayerIdPointer()
|
||||
{
|
||||
if (this.playerIdPointer == 0)
|
||||
@@ -568,6 +716,18 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
};
|
||||
}
|
||||
|
||||
private GlobalContext GetGlobalContext()
|
||||
{
|
||||
/*
|
||||
* The following offsets were reverse-engineered using pointer scanning. All of the following ones seem to currently work.
|
||||
* If any breaks, try the other ones from the list below.
|
||||
* startAddress + 00629204 -> +0x18
|
||||
* startAddress + 006296B4 -> +0x18
|
||||
* startAddress + 00AA9CE0 -> +0xC -> +0xC
|
||||
*/
|
||||
return this.memoryScanner.ReadPtrChain<GlobalContext>(this.memoryScanner.ModuleStartAddress, finalPointerOffset: 0x0, 0x00629204, 0x18);
|
||||
}
|
||||
|
||||
private unsafe GameData AggregateGameData(
|
||||
GameContext gameContext,
|
||||
InstanceContext instanceContext,
|
||||
@@ -576,7 +736,6 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
PlayerContext[] players,
|
||||
ProfessionsContext[] professions,
|
||||
QuestContext[] quests,
|
||||
UserContext userContext,
|
||||
SkillbarContext[] skills,
|
||||
PartyAttributesContext[] partyAttributes,
|
||||
TitleContext[] titles,
|
||||
@@ -586,11 +745,16 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
int mainPlayerEntityId,
|
||||
int targetEntityId)
|
||||
{
|
||||
var name = userContext.PlayerName;
|
||||
_ = Map.TryParse((int)userContext.MapId, out var currentMap);
|
||||
var partyMembers = professions
|
||||
.Where(p => p.AgentId != mainPlayerEntityId)
|
||||
.Select(p => GetPlayerInformation((int)p.AgentId, instanceContext, mapEntities, professions, skills, partyAttributes, entities))
|
||||
.Select(p => GetPlayerInformation(
|
||||
(int)p.AgentId,
|
||||
instanceContext,
|
||||
mapEntities.Skip((int)p.AgentId).FirstOrDefault(),
|
||||
professions.FirstOrDefault(prof => prof.AgentId == p.AgentId),
|
||||
skills.FirstOrDefault(s => s.AgentId == p.AgentId),
|
||||
partyAttributes.OfType<PartyAttributesContext>().FirstOrDefault(attr => attr.AgentId == p.AgentId),
|
||||
entities.OfType<EntityContext>().FirstOrDefault(e => e.AgentId == p.AgentId)))
|
||||
.ToList();
|
||||
|
||||
var parsedMapIcons = GetMapIcons(mapIcons);
|
||||
@@ -599,18 +763,28 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
gameContext,
|
||||
instanceContext,
|
||||
players.FirstOrDefault(p => p.AgentId == mainPlayerEntityId),
|
||||
mapEntities,
|
||||
professions,
|
||||
mapEntities.Skip(mainPlayerEntityId).FirstOrDefault(),
|
||||
professions.FirstOrDefault(p => p.AgentId == mainPlayerEntityId),
|
||||
quests,
|
||||
skills,
|
||||
partyAttributes,
|
||||
skills.OfType<SkillbarContext>().FirstOrDefault(s => s.AgentId == mainPlayerEntityId),
|
||||
partyAttributes.OfType<PartyAttributesContext>().FirstOrDefault(p => p.AgentId == mainPlayerEntityId),
|
||||
titles,
|
||||
titleTiers,
|
||||
entities);
|
||||
entities.FirstOrDefault(e => e.AgentId == mainPlayerEntityId));
|
||||
|
||||
var worldPlayers = players
|
||||
.Where(p => p.AgentId != mainPlayerEntityId && p.AgentId != 0)
|
||||
.Select(p => this.GetWorldPlayerInformation(p, instanceContext, mapEntities, professions, skills, partyAttributes, titles, titleTiers, entities))
|
||||
.Select(p =>
|
||||
this.GetWorldPlayerInformation(
|
||||
p,
|
||||
instanceContext,
|
||||
mapEntities.Skip(p.AgentId).First(),
|
||||
professions.FirstOrDefault(prof => prof.AgentId == p.AgentId),
|
||||
skills.OfType<SkillbarContext>().FirstOrDefault(s => s.AgentId == p.AgentId),
|
||||
partyAttributes.OfType<PartyAttributesContext>().FirstOrDefault(attr => attr.AgentId == p.AgentId),
|
||||
titles,
|
||||
titleTiers,
|
||||
entities.FirstOrDefault(e => e.AgentId == p.AgentId)))
|
||||
.ToList();
|
||||
|
||||
var remainingEntities = entities
|
||||
@@ -619,44 +793,15 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
.ToArray();
|
||||
|
||||
var livingEntities = this.GetLivingEntities(remainingEntities, npcs);
|
||||
|
||||
var userInformation = new UserInformation
|
||||
{
|
||||
Email = userContext.PlayerEmailFirstChar + (userContext.PlayerEmailSecondChar + userContext.PlayerEmailRemaining),
|
||||
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,
|
||||
CurrentMap = currentMap,
|
||||
InstanceTimer = instanceContext.Timer,
|
||||
CurrentTargetId = targetEntityId
|
||||
};
|
||||
|
||||
return new GameData
|
||||
{
|
||||
Party = partyMembers,
|
||||
MainPlayer = mainPlayer,
|
||||
Session = sessionInformation,
|
||||
User = userInformation,
|
||||
WorldPlayers = worldPlayers,
|
||||
LivingEntities = livingEntities,
|
||||
MapIcons = parsedMapIcons,
|
||||
CurrentTargetId = targetEntityId,
|
||||
Valid = mapEntities.Length > 0 || players.Length > 0 || professions.Length > 0 || partyAttributes.Length > 0 || entities.Length > 0
|
||||
};
|
||||
}
|
||||
@@ -749,16 +894,16 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
GameContext gameContext,
|
||||
InstanceContext instanceContext,
|
||||
PlayerContext playerContext,
|
||||
MapEntityContext[] mapEntities,
|
||||
ProfessionsContext[] professions,
|
||||
MapEntityContext mapEntity,
|
||||
ProfessionsContext professions,
|
||||
QuestContext[] quests,
|
||||
SkillbarContext[] skillbars,
|
||||
PartyAttributesContext[] partyAttributes,
|
||||
SkillbarContext skillbar,
|
||||
PartyAttributesContext partyAttributes,
|
||||
TitleContext[] titles,
|
||||
TitleTierContext[] titleTiers,
|
||||
EntityContext[] entities)
|
||||
EntityContext entity)
|
||||
{
|
||||
var playerInformation = this.GetWorldPlayerInformation(playerContext, instanceContext, mapEntities, professions, skillbars, partyAttributes, titles, titleTiers, entities);
|
||||
var playerInformation = this.GetWorldPlayerInformation(playerContext, instanceContext, mapEntity, professions, skillbar, partyAttributes, titles, titleTiers, entity);
|
||||
_ = Quest.TryParse((int)gameContext.QuestId, out var quest);
|
||||
var questLog = quests
|
||||
.Select(q =>
|
||||
@@ -788,8 +933,8 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
SecondaryProfession = playerInformation.SecondaryProfession,
|
||||
UnlockedProfession = playerInformation.UnlockedProfession,
|
||||
HardModeUnlocked = gameContext.HardModeUnlocked == 1,
|
||||
CurrentEnergy = playerInformation.CurrentEnergy,
|
||||
CurrentHealth = playerInformation.CurrentHealth,
|
||||
CurrentEnergy = playerInformation.MaxEnergy * entity.CurrentEnergyPercentage,
|
||||
CurrentHealth = playerInformation.MaxHealth * entity.CurrentHealthPercentage,
|
||||
MaxEnergy = playerInformation.MaxEnergy,
|
||||
MaxHealth = playerInformation.MaxHealth,
|
||||
EnergyRegen = playerInformation.EnergyRegen,
|
||||
@@ -808,16 +953,16 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
private WorldPlayerInformation GetWorldPlayerInformation(
|
||||
PlayerContext playerContext,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] mapEntities,
|
||||
ProfessionsContext[] professions,
|
||||
SkillbarContext[] skillbars,
|
||||
PartyAttributesContext[] partyAttributes,
|
||||
MapEntityContext mapEntity,
|
||||
ProfessionsContext professions,
|
||||
SkillbarContext skillbar,
|
||||
PartyAttributesContext partyAttributes,
|
||||
TitleContext[] titles,
|
||||
TitleTierContext[] titleTiers,
|
||||
EntityContext[] entities)
|
||||
EntityContext entity)
|
||||
{
|
||||
var name = this.memoryScanner.Read(playerContext.NamePointer);
|
||||
var playerInformation = GetPlayerInformation(playerContext.AgentId, instanceContext, mapEntities, professions, skillbars, partyAttributes, entities);
|
||||
var playerInformation = GetPlayerInformation(playerContext.AgentId, instanceContext, mapEntity, professions, skillbar, partyAttributes, entity);
|
||||
var maybeCurrentTitleContext = (TitleContext?)null;
|
||||
var maybeCurrentTitle = (Title?)null;
|
||||
for(var i = 0; i < titles.Length; i++)
|
||||
@@ -878,14 +1023,12 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
private static PlayerInformation GetPlayerInformation(
|
||||
int playerId,
|
||||
InstanceContext instanceContext,
|
||||
MapEntityContext[] mapEntities,
|
||||
ProfessionsContext[] professions,
|
||||
SkillbarContext[] skillbars,
|
||||
PartyAttributesContext[] partyAttributes,
|
||||
EntityContext[] entities)
|
||||
MapEntityContext mapEntity,
|
||||
ProfessionsContext professionContext,
|
||||
SkillbarContext? skillbar,
|
||||
PartyAttributesContext? partyAttributes,
|
||||
EntityContext entity)
|
||||
{
|
||||
var mapEntityContext = mapEntities.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
|
||||
@@ -894,11 +1037,10 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
.Where(p => p is not null && p != Profession.None)
|
||||
.OrderBy(p => p.Id)
|
||||
.ToList();
|
||||
var maybeSkillbarContext = skillbars.Select(s => (SkillbarContext?) s).FirstOrDefault(s => s?.AgentId == playerId);
|
||||
var maybePartyAttributesContext = partyAttributes.Select(p => (PartyAttributesContext?) p).FirstOrDefault(p => p?.AgentId == playerId);
|
||||
Build? build = null;
|
||||
if (maybeSkillbarContext is SkillbarContext skillbarContext &&
|
||||
maybePartyAttributesContext is PartyAttributesContext attributesContext &&
|
||||
if (skillbar is SkillbarContext skillbarContext &&
|
||||
partyAttributes is PartyAttributesContext attributesContext &&
|
||||
partyAttributes.Value.Attributes is not null &&
|
||||
primaryProfession is not null &&
|
||||
secondaryProfession is not null)
|
||||
{
|
||||
@@ -949,9 +1091,8 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
};
|
||||
}
|
||||
|
||||
(var currentHp, var currentEnergy) = ApplyEnergyAndHealthRegen(instanceContext, mapEntityContext);
|
||||
EntityContext? entityContext = entities.FirstOrDefault(e => e.AgentId == playerId);
|
||||
if (!Npc.TryParse(entityContext?.EntityModelType ?? 0, out var npc))
|
||||
(var currentHp, var currentEnergy) = ApplyEnergyAndHealthRegen(instanceContext, mapEntity);
|
||||
if (!Npc.TryParse(entity.EntityModelType, out var npc))
|
||||
{
|
||||
npc = Npc.Unknown;
|
||||
}
|
||||
@@ -959,27 +1100,25 @@ public sealed class GuildwarsMemoryReader : IGuildwarsMemoryReader
|
||||
return new PlayerInformation
|
||||
{
|
||||
Id = playerId,
|
||||
Timer = entityContext!.Value.Timer,
|
||||
Level = entityContext.Value.Level,
|
||||
Timer = entity.Timer,
|
||||
Level = entity.Level,
|
||||
NpcDefinition = npc,
|
||||
ModelType = entityContext.Value.EntityModelType,
|
||||
ModelType = entity.EntityModelType,
|
||||
PrimaryProfession = primaryProfession,
|
||||
SecondaryProfession = secondaryProfession,
|
||||
UnlockedProfession = unlockedProfessions,
|
||||
CurrentHealth = currentHp,
|
||||
CurrentEnergy = currentEnergy,
|
||||
MaxHealth = mapEntityContext.MaxHealth,
|
||||
MaxEnergy = mapEntityContext.MaxEnergy,
|
||||
HealthRegen = mapEntityContext.HealthRegen,
|
||||
EnergyRegen = mapEntityContext.EnergyRegen,
|
||||
MaxHealth = mapEntity.MaxHealth,
|
||||
MaxEnergy = mapEntity.MaxEnergy,
|
||||
HealthRegen = mapEntity.HealthRegen,
|
||||
EnergyRegen = mapEntity.EnergyRegen,
|
||||
CurrentBuild = build,
|
||||
Position = entityContext is not null ?
|
||||
new Position
|
||||
{
|
||||
X = entityContext.Value.Position.X,
|
||||
Y = entityContext.Value.Position.Y
|
||||
} :
|
||||
default
|
||||
Position = new Position
|
||||
{
|
||||
X = entity.Position.X,
|
||||
Y = entity.Position.Y
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,4 +12,7 @@ public interface IGuildwarsMemoryCache
|
||||
Task<PathingMetadata?> ReadPathingMetaData(CancellationToken cancellationToken);
|
||||
Task<InventoryData?> ReadInventoryData(CancellationToken cancellationToken);
|
||||
Task<WorldData?> ReadWorldData(CancellationToken cancellationToken);
|
||||
Task<SessionData?> ReadSessionData(CancellationToken cancellationToken);
|
||||
Task<UserData?> ReadUserData(CancellationToken cancellationToken);
|
||||
Task<MainPlayerData?> ReadMainPlayerData(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -13,5 +13,8 @@ public interface IGuildwarsMemoryReader
|
||||
Task<PathingMetadata?> ReadPathingMetaData(CancellationToken cancellationToken);
|
||||
Task<InventoryData?> ReadInventoryData(CancellationToken cancellationToken);
|
||||
Task<WorldData?> ReadWorldData(CancellationToken cancellationToken);
|
||||
Task<UserData?> ReadUserData(CancellationToken cancellationToken);
|
||||
Task<SessionData?> ReadSessionData(CancellationToken cancellationToken);
|
||||
Task<MainPlayerData?> ReadMainPlayerData(CancellationToken cancellationToken);
|
||||
void Stop();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Daybreak.Models.Interop;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Services.Scanner;
|
||||
|
||||
@@ -18,6 +19,8 @@ public interface IMemoryScanner
|
||||
T[] ReadArray<T>(uint address, uint size);
|
||||
T[] ReadArray<T>(GuildwarsArray<T> guildwarsArray);
|
||||
T[] ReadArray<T>(GuildwarsPointerArray<T> guildwarsPointerArray);
|
||||
T ReadItemAtIndex<T>(uint address, int index);
|
||||
T ReadItemAtIndex<T>(GuildwarsArray<T> guildwarsArray, int index);
|
||||
byte[]? ReadBytes(uint address, uint size);
|
||||
string ReadWString(uint address, uint maxsize);
|
||||
T ReadPtrChain<T>(uint Base, uint finalPointerOffset = 0, params uint[] offsets);
|
||||
|
||||
@@ -179,6 +179,18 @@ public sealed class MemoryScanner : IMemoryScanner
|
||||
{
|
||||
return this.ReadArray<T>(guildwarsArray.Buffer.Address, guildwarsArray.Size);
|
||||
}
|
||||
|
||||
public T ReadItemAtIndex<T>(uint address, int index)
|
||||
{
|
||||
var itemSize = Marshal.SizeOf(typeof(T));
|
||||
var itemAtIndexAddress = address + (index * itemSize);
|
||||
return this.Read<T>((uint)itemAtIndexAddress);
|
||||
}
|
||||
|
||||
public T ReadItemAtIndex<T>(GuildwarsArray<T> guildwarsArray, int index)
|
||||
{
|
||||
return this.ReadItemAtIndex<T>(guildwarsArray.Buffer.Address, index);
|
||||
}
|
||||
|
||||
public T[] ReadArray<T>(GuildwarsPointerArray<T> guildwarsPointerArray)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<components:PlayerResourcesComponent Margin="5, 5, 5, 0"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData, Mode=OneWay}"
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=MainPlayerDataValid, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<Grid Margin="5, 0, 5, 0"
|
||||
Visibility="{Binding ElementName=_this, Path=MainPlayerDataValid, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
@@ -47,26 +47,26 @@
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<components:MainPlayerInformationComponent Grid.Row="0"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData.MainPlayer, Mode=OneWay}"
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext.Player.PlayerInformation, Mode=OneWay}"
|
||||
Margin="0, 0, 0, 10"
|
||||
NavigateToClicked="Component_NavigateToClicked"/>
|
||||
<components:TitleInformationComponent Grid.Row="1"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData.MainPlayer.TitleInformation, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=GameData.MainPlayer.TitleInformation.IsValid, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext.Player.PlayerInformation.TitleInformation, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=MainPlayerResourceContext.Player.PlayerInformation.TitleInformation.IsValid, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Margin="0, 0, 0, 10"
|
||||
NavigateToClicked="Component_NavigateToClicked"/>
|
||||
<components:CurrentMapComponent Grid.Row="2"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData.Session.CurrentMap, Mode=OneWay}"
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext.Session.Session.CurrentMap, Mode=OneWay}"
|
||||
Margin="0, 0, 0, 10"
|
||||
NavigateToClicked="Component_NavigateToClicked"/>
|
||||
<components:CurrentQuestComponent Grid.Row="3"
|
||||
Margin="0, 0, 0, 10"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData.MainPlayer.Quest, Mode=OneWay}"
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext.Player.PlayerInformation.Quest, Mode=OneWay}"
|
||||
NavigateToClicked="Component_NavigateToClicked"/>
|
||||
<components:QuestLogComponent Grid.Row="4"
|
||||
Grid.RowSpan="2"
|
||||
NavigateToClicked="Component_NavigateToClicked"
|
||||
DataContext="{Binding ElementName=_this, Path=GameData.MainPlayer.QuestLog, Mode=OneWay}"></components:QuestLogComponent>
|
||||
DataContext="{Binding ElementName=_this, Path=MainPlayerResourceContext.Player.PlayerInformation.QuestLog, Mode=OneWay}"></components:QuestLogComponent>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1"
|
||||
Margin="5, 0, 5, 0">
|
||||
|
||||
@@ -45,6 +45,9 @@ public partial class FocusView : UserControl
|
||||
[GenerateDependencyProperty]
|
||||
private PathingData pathingData;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private MainPlayerResourceContext mainPlayerResourceContext;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private bool loadingPathingData;
|
||||
|
||||
@@ -113,6 +116,7 @@ public partial class FocusView : UserControl
|
||||
if (pathingMeta?.TrapezoidCount == this.PathingData?.Trapezoids?.Count ||
|
||||
this.cancellationTokenSource?.IsCancellationRequested is not false)
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() => this.LoadingPathingData = false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,43 +136,6 @@ public partial class FocusView : UserControl
|
||||
});
|
||||
}
|
||||
|
||||
private async Task UpdateGameData()
|
||||
{
|
||||
if (this.applicationLauncher.IsGuildwarsRunning is false)
|
||||
{
|
||||
this.logger.LogInformation($"Executable is not running. Returning to {nameof(LauncherView)}");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
var maybeGameData = await this.guildwarsMemoryCache.ReadGameData(this.cancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(true);
|
||||
if (maybeGameData is not GameData gameData)
|
||||
{
|
||||
this.MainPlayerDataValid = false;
|
||||
this.Browser.Visibility = this.MainPlayerDataValid is true ? Visibility.Visible : Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameData.MainPlayer is null ||
|
||||
gameData.User is null ||
|
||||
gameData.Session is null ||
|
||||
gameData.Valid is false)
|
||||
{
|
||||
this.MainPlayerDataValid = false;
|
||||
this.Browser.Visibility = this.MainPlayerDataValid is true ? Visibility.Visible : Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
this.GameData = gameData;
|
||||
this.MainPlayerDataValid = true;
|
||||
this.Browser.Visibility = this.MainPlayerDataValid is true ?
|
||||
this.minimapMaximized ?
|
||||
Visibility.Hidden :
|
||||
Visibility.Visible :
|
||||
Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async void PeriodicallyReadPathingData(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
@@ -203,10 +170,18 @@ public partial class FocusView : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
var readGameDataTask = this.guildwarsMemoryCache.ReadGameData(cancellationToken);
|
||||
await Task.WhenAll(
|
||||
this.UpdateGameData(),
|
||||
readGameDataTask,
|
||||
Task.Delay(16, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var maybeGameData = await readGameDataTask;
|
||||
if (maybeGameData is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.GameData = maybeGameData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -246,6 +221,64 @@ public partial class FocusView : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private async void PeriodicallyReadMainPlayerContextData(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.applicationLauncher.IsGuildwarsRunning is false)
|
||||
{
|
||||
this.logger.LogInformation($"Executable is not running. Returning to {nameof(LauncherView)}");
|
||||
this.viewManager.ShowView<LauncherView>();
|
||||
this.cancellationTokenSource?.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
var readUserDataTask = this.guildwarsMemoryCache.ReadUserData(cancellationToken);
|
||||
var readSessionDataTask = this.guildwarsMemoryCache.ReadSessionData(cancellationToken);
|
||||
var readMainPlayerDataTask = this.guildwarsMemoryCache.ReadMainPlayerData(cancellationToken);
|
||||
await Task.WhenAll(
|
||||
readUserDataTask,
|
||||
readSessionDataTask,
|
||||
readMainPlayerDataTask,
|
||||
Task.Delay(16, cancellationToken)).ConfigureAwait(true);
|
||||
|
||||
var userData = await readUserDataTask;
|
||||
var sessionData = await readSessionDataTask;
|
||||
var mainPlayerData = await readMainPlayerDataTask;
|
||||
if (userData?.User is null ||
|
||||
sessionData?.Session is null ||
|
||||
mainPlayerData?.PlayerInformation is null ||
|
||||
sessionData.Session.InstanceType is InstanceType.Loading or InstanceType.Undefined)
|
||||
{
|
||||
this.MainPlayerDataValid = false;
|
||||
this.Browser.Visibility = Visibility.Collapsed;
|
||||
continue;
|
||||
}
|
||||
|
||||
this.MainPlayerResourceContext = new MainPlayerResourceContext
|
||||
{
|
||||
Player = mainPlayerData,
|
||||
User = userData,
|
||||
Session = sessionData
|
||||
};
|
||||
|
||||
this.MainPlayerDataValid = true;
|
||||
this.Browser.Visibility = this.minimapMaximized ? Visibility.Hidden : Visibility.Visible;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.logger.LogError(ex, "Encountered non-terminating exception. Silently continuing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FocusView_Loaded(object _, RoutedEventArgs e)
|
||||
{
|
||||
this.BrowserAddress = this.liveUpdateableOptions.Value.BrowserUrl;
|
||||
@@ -254,7 +287,7 @@ public partial class FocusView : UserControl
|
||||
this.cancellationTokenSource?.Dispose();
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = this.cancellationTokenSource.Token;
|
||||
this.PeriodicallyReadGameData(cancellationToken);
|
||||
this.PeriodicallyReadMainPlayerContextData(cancellationToken);
|
||||
if (this.InventoryVisible)
|
||||
{
|
||||
this.PeriodicallyReadInventoryData(cancellationToken);
|
||||
@@ -262,6 +295,7 @@ public partial class FocusView : UserControl
|
||||
|
||||
if (this.MinimapVisible)
|
||||
{
|
||||
this.PeriodicallyReadGameData(cancellationToken);
|
||||
this.PeriodicallyReadPathingData(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user