1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-10-16 06:00:45 +00:00

Added dynamic lod with auto fps

This commit is contained in:
hawkeye 2024-03-14 18:50:32 -04:00
parent b0a6079800
commit c68ec210d1
28 changed files with 1066 additions and 1166 deletions

View file

@ -11,9 +11,9 @@
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.DomainModel</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>

View file

@ -0,0 +1,38 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.DynamicLod
{
public class DynamicLodSimData : ObservableObject
{
public int Fps { get; set; }
public int Tlod { get; set; }
public int Olod { get; set; }
public double Agl { get; set; }
public string CloudQuality { get; set; } = "N/A";
public double AltAboveGround { get; set; }
public double AltAboveGroundMinusCg { get; set; }
public double GroundVelocity { get; set; }
public bool PlaneOnGround { get; set; } = true;
public void Clear()
{
Fps = 0;
Tlod = 0;
Olod = 0;
Agl = 0;
CloudQuality = "N/A";
AltAboveGround = 0;
AltAboveGroundMinusCg = 0;
GroundVelocity = 0;
PlaneOnGround = true;
}
}
}

View file

@ -1,11 +0,0 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.DynamicLod
{
public class LodConfig : ObservableObject
{
public int Agl { get; set; }
public int Lod { get; set; }
}
}

View file

@ -1,72 +0,0 @@
using System.Collections.Generic;
using System.Collections.Specialized;
namespace MSFSPopoutPanelManager.DomainModel.DynamicLod
{
public class ObservableLodConfigLinkedList : LinkedList<LodConfig>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, LodConfig item)
{
if (CollectionChanged == null)
return;
switch (action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Reset:
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
break;
}
}
public new void AddFirst(LodConfig item)
{
base.AddFirst(item);
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
}
public new void AddLast(LodConfig item)
{
base.AddLast(item);
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
}
public new void AddBefore(LinkedListNode<LodConfig> node, LinkedListNode<LodConfig> newNode)
{
base.AddBefore(node, newNode);
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, newNode.Value);
}
public new void AddAfter(LinkedListNode<LodConfig> node, LinkedListNode<LodConfig> newNode)
{
base.AddAfter(node, newNode);
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, newNode.Value);
}
public new void Remove(LodConfig item)
{
base.Remove(item);
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
}
public new void RemoveFirst()
{
if (First == null)
return;
base.RemoveFirst();
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, First.Value);
}
public new void RemoveLast()
{
if (Last == null)
return;
base.RemoveLast();
OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, Last.Value);
}
}
}

View file

@ -1,8 +1,4 @@
using MSFSPopoutPanelManager.DomainModel.DynamicLod;
using MSFSPopoutPanelManager.Shared;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.Serialization;
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
@ -11,31 +7,13 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting
public DynamicLodSetting()
{
InitializeChildPropertyChangeBinding();
TlodConfigs.CollectionChanged += (_, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.NewItems?[0] is LodConfig item)
item.PropertyChanged += (_, _) => TlodConfigs.OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, item);
}
};
OlodConfigs.CollectionChanged += (_, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.NewItems?[0] is LodConfig item)
item.PropertyChanged += (_, _) => OlodConfigs.OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, item);
}
};
}
public bool IsEnabled { get; set; } = false;
public ObservableLodConfigLinkedList TlodConfigs { get; set; } = new();
public bool PauseWhenMsfsLoseFocus { get; set; } = true;
public ObservableLodConfigLinkedList OlodConfigs { get; set; } = new();
public bool PauseOutsideCockpitView { get; set; } = true;
public bool ResetEnabled { get; set; } = false;
@ -43,29 +21,28 @@ namespace MSFSPopoutPanelManager.DomainModel.Setting
public int ResetOlod { get; set; } = 100;
public void AddDefaultTLodConfigs()
{
TlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 0, Lod = 100 }));
TlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 5000, Lod = 200 }));
TlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 10000, Lod = 300 }));
TlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 20000, Lod = 400 }));
}
public int TargetedFps { get; set; } = 60;
public void AddDefaultOLodConfigs()
{
OlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 0, Lod = 200 }));
OlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 1000, Lod = 150 }));
OlodConfigs.AddLast(new LinkedListNode<LodConfig>(new LodConfig { Agl = 5000, Lod = 100 }));
}
public int FpsTolerance { get; set; } = 5;
[OnDeserialized]
private void OnDeserialization(StreamingContext context)
{
foreach (var item in TlodConfigs)
item.PropertyChanged += (_, _) => TlodConfigs.OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, item);
public bool TlodMinOnGround { get; set; } = true;
foreach (var item in OlodConfigs)
item.PropertyChanged += (_, _) => OlodConfigs.OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, item);
}
public int AltTlodBase { get; set; } = 1000;
public int TlodMin { get; set; } = 50;
public int TlodMax { get; set; } = 400;
public int CloudRecoveryTlod { get; set; } = 100;
public bool DecreaseCloudQuality { get; set; } = true;
public int OlodTop { get; set; } = 20;
public int OlodBase { get; set; } = 200;
public int AltOlodBase { get; set; } = 1000;
public int AltOlodTop { get; set; } = 10000;
}
}

View file

@ -53,10 +53,11 @@ namespace MSFSPopoutPanelManager.MainApp
services.AddSingleton(s => new AppOrchestrator(SharedStorage, s.GetRequiredService<PanelConfigurationOrchestrator>(), s.GetRequiredService<FlightSimOrchestrator>(), s.GetRequiredService<HelpOrchestrator>(), s.GetRequiredService<KeyboardOrchestrator>()));
services.AddSingleton(_ => new ProfileOrchestrator(SharedStorage));
services.AddSingleton(_ => new DynamicLodOrchestrator(SharedStorage));
services.AddSingleton(s => new PanelSourceOrchestrator(SharedStorage, s.GetRequiredService<FlightSimOrchestrator>()));
services.AddSingleton(s => new PanelPopOutOrchestrator(SharedStorage, s.GetRequiredService<FlightSimOrchestrator>(), s.GetRequiredService<PanelSourceOrchestrator>(), s.GetRequiredService<PanelConfigurationOrchestrator>(), s.GetRequiredService<KeyboardOrchestrator>()));
services.AddSingleton(s => new PanelConfigurationOrchestrator(SharedStorage, s.GetRequiredService<FlightSimOrchestrator>(), s.GetRequiredService<KeyboardOrchestrator>()));
services.AddSingleton(_ => new FlightSimOrchestrator(SharedStorage));
services.AddSingleton(s => new FlightSimOrchestrator(SharedStorage, s.GetRequiredService<DynamicLodOrchestrator>()));
services.AddSingleton(_ => new KeyboardOrchestrator(SharedStorage));
services.AddSingleton(_ => new HelpOrchestrator());

View file

@ -2,13 +2,13 @@
x:Class="MSFSPopoutPanelManager.MainApp.AppUserControl.Dialog.AddProfileDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="clr-namespace:MSFSPopoutPanelManager.MainApp.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
xmlns:converter="clr-namespace:MSFSPopoutPanelManager.MainApp.Converter"
Width="400"
Height="210"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"

View file

@ -3,26 +3,33 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mainApp="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:viewModel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
xmlns:appUserControl="clr-namespace:MSFSPopoutPanelManager.MainApp.AppUserControl"
d:DesignHeight="800"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">22</system:Double>
<Style
x:Key="TextBlockHeading"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style
x:Key="TextBlockLabel"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontSize" Value="12" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="650" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="Margin" Value="0,7,0,0" />
<Setter Property="LineHeight" Value="20" />
</Style>
<Style
@ -33,522 +40,238 @@
</Style>
</UserControl.Resources>
<Grid d:DataContext="{d:DesignInstance viewModel:ApplicationViewModel}">
<WrapPanel Margin="30,10,0,0">
<!-- Terrain level of detail -->
<WrapPanel Margin="20,0,0,0" Orientation="Vertical">
<TextBlock
Margin="0,0,0,5"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold">
Terrain Level of Detail (TLOD)
</TextBlock>
<DataGrid
Name="TlodGrid"
Width="272"
Height="248"
<WrapPanel Orientation="Vertical">
<WrapPanel Margin="120,10,0,0">
<GroupBox
Margin="0"
HorizontalAlignment="Center"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
GridLinesVisibility="None"
HeadersVisibility="Column"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs}"
SelectionUnit="FullRow"
VerticalGridLinesBrush="#B9B9B9">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
FontSize="14"
Text="{Binding}"
TextAlignment="Center"
TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="30" />
<Setter Property="Background" Value="#FF576573" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="White" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="100" Header="AGL">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14">
<TextBox.Text>
<Binding
Mode="TwoWay"
Path="Agl"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:AglValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="LOD">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14">
<TextBox.Text>
<Binding
Mode="TwoWay"
Path="Lod"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="70" Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0"
HorizontalAlignment="Center"
Click="TLodDelete_Click"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Delete TLOD configuration">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="Delete" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<WrapPanel Margin="0,5,0,0">
<DataGrid
Name="AddTlodGrid"
Width="272"
Height="50"
Margin="0"
HorizontalAlignment="Center"
AutoGenerateColumns="False"
BorderThickness="0"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
GridLinesVisibility="None"
HeadersVisibility="None"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding AddTlodConfigs}"
SelectionUnit="FullRow"
VerticalGridLinesBrush="#B9B9B9"
VerticalScrollBarVisibility="Disabled">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="White" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="100" Header="AGL">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:HintAssist.Hint="New AGL"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14"
SourceUpdated="AddTlod_SourceUpdated">
<TextBox.Text>
<Binding
Mode="TwoWay"
NotifyOnSourceUpdated="True"
Path="Agl"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:AglValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="LOD">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:HintAssist.Hint="New LOD"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14"
SourceUpdated="AddTlod_SourceUpdated">
<TextBox.Text>
<Binding
Mode="TwoWay"
NotifyOnSourceUpdated="True"
Path="Lod"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="70" Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
Kind="Add" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</WrapPanel>
Padding="5,2,5,2"
Header="Sim Values"
Style="{StaticResource MaterialDesignGroupBox}">
<GroupBox.HeaderTemplate>
<HierarchicalDataTemplate>
<Label
Height="12"
Margin="10,1,1,1"
Padding="0"
FontSize="12">
Sim Values
</Label>
</HierarchicalDataTemplate>
</GroupBox.HeaderTemplate>
<WrapPanel Margin="3">
<TextBox
Width="75"
Margin="20,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="FPS"
IsHitTestVisible="False"
IsReadOnly="True"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding FlightSimData.DynamicLodSimData.Fps, StringFormat='{}{0:0}'}" />
<TextBox
Width="75"
Margin="20,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="TLOD"
IsHitTestVisible="False"
IsReadOnly="True"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding FlightSimData.DynamicLodSimData.Tlod}" />
<TextBox
Width="75"
Margin="20,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD"
IsHitTestVisible="False"
IsReadOnly="True"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding FlightSimData.DynamicLodSimData.Olod}" />
<TextBox
Width="75"
Margin="20,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="AGL"
IsHitTestVisible="False"
IsReadOnly="True"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding FlightSimData.DynamicLodSimData.Agl, StringFormat='{}{0:0}'}" />
<TextBox
Width="75"
Margin="20,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="Clouds"
IsHitTestVisible="False"
IsReadOnly="True"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding FlightSimData.DynamicLodSimData.CloudQuality}" />
</WrapPanel>
</GroupBox>
</WrapPanel>
<!-- Object level of detail -->
<WrapPanel Margin="50,0,0,0" Orientation="Vertical">
<TextBlock
Margin="0,0,0,5"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold">
Object Level of Detail (OLOD)
</TextBlock>
<DataGrid
Name="OlodGrid"
Width="272"
Height="248"
Margin="0"
HorizontalAlignment="Center"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
GridLinesVisibility="None"
HeadersVisibility="Column"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs}"
SelectionUnit="FullRow"
VerticalGridLinesBrush="#B9B9B9">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock
FontSize="14"
Text="{Binding}"
TextAlignment="Center"
TextWrapping="Wrap" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="30" />
<Setter Property="Background" Value="#FF576573" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="White" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="100" Header="AGL">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14">
<TextBox.Text>
<Binding
Mode="TwoWay"
Path="Agl"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:AglValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="LOD">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14">
<TextBox.Text>
<Binding
Mode="TwoWay"
Path="Lod"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="70" Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0"
HorizontalAlignment="Center"
Click="OLodDelete_Click"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Delete OLOD configuration">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="Delete" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<WrapPanel Margin="0,5,0,0">
<DataGrid
Name="AddOlodGrid"
Width="272"
Height="50"
Margin="0"
HorizontalAlignment="Center"
AutoGenerateColumns="False"
BorderThickness="0"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
GridLinesVisibility="None"
HeadersVisibility="None"
HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding AddOlodConfigs}"
SelectionUnit="FullRow"
VerticalGridLinesBrush="#B9B9B9"
VerticalScrollBarVisibility="Disabled">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="#FFB9B9B9" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="White" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="100" Header="AGL">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:HintAssist.Hint="New AGL"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14"
SourceUpdated="AddOlod_SourceUpdated">
<TextBox.Text>
<Binding
Mode="TwoWay"
NotifyOnSourceUpdated="True"
Path="Agl"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:AglValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="100" Header="LOD">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox
Width="100"
materialDesign:HintAssist.Hint="New LOD"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="0"
FontSize="14"
SourceUpdated="AddOlod_SourceUpdated">
<TextBox.Text>
<Binding
Mode="TwoWay"
NotifyOnSourceUpdated="True"
Path="Lod"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="70" Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
Kind="Add" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,0,0" Orientation="Horizontal">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.ResetEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
Enable reset of TLOD and OLOD to the following values when flight session ends.
</TextBlock>
<WrapPanel IsEnabled="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.ResetEnabled}" Orientation="Horizontal">
<WrapPanel Margin="47,10,0,0" Orientation="Vertical">
<WrapPanel Margin="0,0,0,0">
<TextBlock Style="{StaticResource TextBlockHeading}">General Options</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<TextBox
Width="100"
Height="40"
Margin="45,5,0,0"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
materialDesign:HintAssist.Hint="Reset TLOD"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="1"
FontSize="14"
Style="{StaticResource MaterialDesignFloatingHintTextBox}">
<TextBox.Text>
<Binding Mode="TwoWay" Path="AppSettingData.ApplicationSetting.DynamicLodSetting.ResetTlod">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Margin="0,0,0,0"
Padding="5,5,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Foreground="White"
materialDesign:HintAssist.Hint="Targeted FPS"
Background="#607D8B"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.TargetedFps, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="FPS Tolerance"
materialDesign:TextFieldAssist.SuffixText="%"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.FpsTolerance, Mode=TwoWay}" />
<WrapPanel Margin="40,10,0,0">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.PauseWhenMsfsLoseFocus, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Pause when MSFS loses focus</TextBlock>
</WrapPanel>
<WrapPanel Margin="20,10,0,0">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.PauseOutsideCockpitView, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Pause when not in cockpit view</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,15,0,0">
<TextBlock Style="{StaticResource TextBlockHeading}">TLOD Options</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<TextBox
Width="75"
Margin="0,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="TLOD Minimum"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.TlodMin, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="TLOD Maximum"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.TlodMax, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="TLOD Base Alt"
materialDesign:TextFieldAssist.SuffixText="ft"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.AltTlodBase, Mode=TwoWay}" />
<WrapPanel Margin="40,10,0,0">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.TlodMinOnGround, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">TLOD minimum on ground/TLOD Base Altitude</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,15,0,0">
<TextBlock Style="{StaticResource TextBlockHeading}">OLOD Options</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<TextBox
Width="75"
Margin="0,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD at Base Alt"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.OlodBase, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD at Top Alt"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.OlodTop, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD Base Alt"
materialDesign:TextFieldAssist.SuffixText="ft"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.AltOlodBase, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD Top Alt"
materialDesign:TextFieldAssist.SuffixText="ft"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.AltOlodTop, Mode=TwoWay}" />
</WrapPanel>
<WrapPanel Margin="0,15,0,0">
<TextBlock Style="{StaticResource TextBlockHeading}">Cloud Options</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<TextBox
Width="100"
Height="40"
Margin="20,5,0,0"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
materialDesign:HintAssist.Hint="Reset OLOD"
materialDesign:ValidationAssist.HorizontalAlignment="Center"
BorderThickness="1"
FontSize="14"
Style="{StaticResource MaterialDesignFloatingHintTextBox}">
<TextBox.Text>
<Binding Mode="TwoWay" Path="AppSettingData.ApplicationSetting.DynamicLodSetting.ResetOlod">
<Binding.ValidationRules>
<appUserControl:LodValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Margin="0,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="Cloud Adjust at TLOD"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.CloudRecoveryTlod, Mode=TwoWay}" />
<WrapPanel Margin="20,10,0,0">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.DecreaseCloudQuality, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Decrease cloud quality by one level to archive desired FPS</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,15,0,0">
<TextBlock Style="{StaticResource TextBlockHeading}">Reset TLOD and OLOD on Exit</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel Margin="0,0,0,0">
<WrapPanel Margin="0,7,0,0">
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.ResetEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Reset on Exit</TextBlock>
</WrapPanel>
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="TLOD"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.ResetTlod, Mode=TwoWay}" />
<TextBox
Width="75"
Margin="40,0,0,0"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="OLOD"
PreviewTextInput="TxtBox_NumbersOnly"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.ResetOlod, Mode=TwoWay}" />
</WrapPanel>
</WrapPanel>
</WrapPanel>
</WrapPanel>

View file

@ -1,22 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using MSFSPopoutPanelManager.DomainModel.DynamicLod;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.MainApp.AppUserControl
{
public partial class DynamicLodPreference
{
private ObservableLodConfigLinkedList _tlodConfigs;
private ObservableLodConfigLinkedList _olodConfigs;
public DynamicLodPreference()
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
@ -24,146 +16,21 @@ namespace MSFSPopoutPanelManager.MainApp.AppUserControl
InitializeComponent();
return;
}
AddTlodConfigs = new ObservableCollection<TempLodConfig>() { new () };
AddOlodConfigs = new ObservableCollection<TempLodConfig>() { new () };
Loaded += (_, _) =>
{
InitializeComponent();
var dataContext = DataContext as ApplicationViewModel;
_tlodConfigs = dataContext?.AppSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs;
_olodConfigs = dataContext?.AppSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs;
};
}
public ObservableCollection<TempLodConfig> AddTlodConfigs { get; set; }
public ObservableCollection<TempLodConfig> AddOlodConfigs { get; set; }
private void AddTlod_SourceUpdated(object sender, DataTransferEventArgs e)
private void TxtBox_NumbersOnly(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var lodConfig = textBox?.DataContext as TempLodConfig;
if (lodConfig?.Agl == null || lodConfig.Lod == null)
return;
if (UpdateTlodDuplicate(lodConfig))
{
RebindTLodGrid();
return;
}
var targetLodConfig = _tlodConfigs.LastOrDefault(x => lodConfig.Agl >= x.Agl);
var newLodConfig = new LodConfig { Agl = (int)lodConfig.Agl, Lod = (int)lodConfig.Lod };
if (targetLodConfig == null)
_tlodConfigs.AddFirst(newLodConfig);
else
_tlodConfigs.AddAfter(_tlodConfigs.Find(targetLodConfig), new LinkedListNode<LodConfig>(newLodConfig));
RebindTLodGrid();
}
private void TLodDelete_Click(object sender, RoutedEventArgs e)
{
var button = e.Source as Button;
if(button?.DataContext is not LodConfig lodConfig)
return;
_tlodConfigs.Remove(lodConfig);
RebindTLodGrid();
}
private void RebindTLodGrid()
{
this.TlodGrid.ItemsSource = null;
this.TlodGrid.ItemsSource = _tlodConfigs.ToList();
AddTlodConfigs.Clear();
AddTlodConfigs.Add(new TempLodConfig());
}
private bool UpdateTlodDuplicate(TempLodConfig lodConfig)
{
var tlodConfig = _tlodConfigs.FirstOrDefault(x => x.Agl == lodConfig.Agl);
if(tlodConfig == null)
return false;
tlodConfig.Lod = Convert.ToInt32(lodConfig.Lod);
return true;
}
private void AddOlod_SourceUpdated(object sender, DataTransferEventArgs e)
{
var textBox = sender as TextBox;
var lodConfig = textBox?.DataContext as TempLodConfig;
if (lodConfig?.Agl == null || lodConfig.Lod == null)
return;
if (UpdateOlodDuplicate(lodConfig))
{
RebindOLodGrid();
return;
}
var targetLodConfig = _olodConfigs.LastOrDefault(x => lodConfig.Agl >= x.Agl);
var newLodConfig = new LodConfig() { Agl = (int)lodConfig.Agl, Lod = (int)lodConfig.Lod };
if (targetLodConfig == null)
_olodConfigs.AddFirst(newLodConfig);
else
_olodConfigs.AddAfter(_olodConfigs.Find(targetLodConfig), new LinkedListNode<LodConfig>(newLodConfig));
RebindOLodGrid();
}
private void OLodDelete_Click(object sender, RoutedEventArgs e)
{
var button = e.Source as Button;
if (button?.DataContext is not LodConfig lodConfig)
return;
_olodConfigs.Remove(lodConfig);
RebindOLodGrid();
}
private void RebindOLodGrid()
{
this.OlodGrid.ItemsSource = null;
this.OlodGrid.ItemsSource = _olodConfigs.ToList();
AddOlodConfigs.Clear();
AddOlodConfigs.Add(new TempLodConfig());
}
private bool UpdateOlodDuplicate(TempLodConfig lodConfig)
{
var olodConfig = _olodConfigs.FirstOrDefault(x => x.Agl == lodConfig.Agl);
if (olodConfig == null)
return false;
olodConfig.Lod = Convert.ToInt32(lodConfig.Lod);
return true;
e.Handled = !(int.TryParse(e.Text, out _) || (e.Text.Trim() == "-"));
}
}
public class TempLodConfig
{
public int? Agl { get; set; }
public int? Lod { get; set; }
}
public class AglValidationRule : ValidationRule
{

View file

@ -633,21 +633,23 @@
<WrapPanel Visibility="{c:Binding LocalCompileOnly}">
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryDynamicLodSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Dynamically adjust Terrain and Object Level of Details (Not Supported)</TextBlock>
<WrapPanel>
<TextBlock
Padding="0,0,5,0"
Foreground="Red"
Style="{StaticResource TextBlockHeading}">
(Unsupported Feature)
</TextBlock>
<TextBlock Style="{StaticResource TextBlockHeading}">Dynamically adjust Terrain and Object Level of Details</TextBlock>
</WrapPanel>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<TextBlock
Margin="0,10,0,10"
Foreground="Red"
Style="{StaticResource TextBlockLabel}">
*** Experimental and not supported. Use at your own risk since this feature directly modifies MSFS memory. ***
</TextBlock>
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
Enable automatic adjustment of terrain level of detail (TLOD) and object level of detail (OLOD) based on current aircraft's above ground level (in feet).
Enable automatic adjustments of TLOD and OLOD to get the desired targeted frame rate (FPS).
</TextBlock>
</WrapPanel>
<WrapPanel Visibility="{Binding Path=AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">

View file

@ -14,9 +14,9 @@
<RootNamespace>MSFSPopoutPanelManager.MainApp</RootNamespace>
<ApplicationIcon>logo.ico</ApplicationIcon>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<DebugType>embedded</DebugType>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<!-- Publishing options -->

View file

@ -33,15 +33,6 @@ namespace MSFSPopoutPanelManager.Orchestration
public void Initialize()
{
// Add default dynamic LOD configs
if (!AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled &&
AppSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.Count == 0 &&
AppSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.Count == 0)
{
AppSettingData.ApplicationSetting.DynamicLodSetting.AddDefaultTLodConfigs();
AppSettingData.ApplicationSetting.DynamicLodSetting.AddDefaultOLodConfigs();
}
if (AppSettingData.ApplicationSetting.GeneralSetting.CheckForUpdate)
CheckForAutoUpdate();

View file

@ -38,17 +38,6 @@ namespace MSFSPopoutPanelManager.Orchestration
break;
}
};
ApplicationSetting.DynamicLodSetting.TlodConfigs.CollectionChanged += (_, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Reset)
AppSettingDataManager.WriteAppSetting(ApplicationSetting);
};
ApplicationSetting.DynamicLodSetting.OlodConfigs.CollectionChanged += (_, _) =>
{
AppSettingDataManager.WriteAppSetting(ApplicationSetting);
};
}
}
}

View file

@ -1,257 +0,0 @@
using MSFSPopoutPanelManager.DomainModel.DynamicLod;
using MSFSPopoutPanelManager.WindowsAgent;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager.Orchestration
{
public class DynamicLodManager
{
private const string SIMMODULE_NAME = "WwiseLibPCx64P.dll";
private const int PROCESS_VM_OPERATION = 0x0008;
private const int PROCESS_VM_READ = 0x0010;
private const int PROCESS_VM_WRITE = 0x0020;
private const long OFFSET_MODULE_BASE =0x004B2368;
private const long OFFSET_POINTER_MAIN = 0x3D0;
private const long OFFSET_POINTER_TLOD_VR = 0x114;
private const long OFFSET_POINTER_TLOD = 0xC;
private const long OFFSET_POINTER_OLOD = 0xC;
private static bool _isActive;
private static WindowProcess _process;
private static IntPtr _processHandle;
private static long _processModuleAddress;
private static long _addressMain;
private static long _addressTlod;
private static long _addressOlod;
private static long _addressTlodVr;
private static long _addressOlodVr;
private static FlightSimData _flightSimData;
private static AppSettingData _appSettingData;
private static LinkedListNode<LodConfig> _nextTlod;
private static LinkedListNode<LodConfig> _nextOlod;
private static LinkedListNode<LodConfig> _currentTlod;
private static LinkedListNode<LodConfig> _currentOlod;
public static void Attach(FlightSimData flightSimData, AppSettingData appSettingData)
{
if (appSettingData == null || _isActive)
return;
_flightSimData = flightSimData;
_appSettingData = appSettingData;
_flightSimData.OnAltAboveGroundChanged -= HandleOnAltAboveGroundChanged;
_flightSimData.OnAltAboveGroundChanged += HandleOnAltAboveGroundChanged;
_process = WindowProcessManager.SimulatorProcess;
_processModuleAddress = GetSimModuleAddress();
if (_process == null || _processModuleAddress == IntPtr.Zero)
return;
_processHandle = PInvoke.OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, _process.ProcessId);
if (_processHandle == IntPtr.Zero)
return;
_addressMain = ReadMemory<long>(_processModuleAddress + OFFSET_MODULE_BASE) + OFFSET_POINTER_MAIN;
_addressTlod = ReadMemory<long>(_addressMain) + OFFSET_POINTER_TLOD;
_addressTlodVr = ReadMemory<long>(_addressMain) + OFFSET_POINTER_TLOD_VR;
_addressOlod = _addressTlod + OFFSET_POINTER_OLOD;
_addressOlodVr = _addressTlodVr + OFFSET_POINTER_OLOD;
// Set initial LOD if already in cockpit
InitializeData();
_isActive = true;
}
public static void Detach()
{
if (_appSettingData == null)
return;
if (_appSettingData.ApplicationSetting.DynamicLodSetting.ResetEnabled)
{
WriteTlod(_appSettingData.ApplicationSetting.DynamicLodSetting.ResetTlod);
WriteOlod(_appSettingData.ApplicationSetting.DynamicLodSetting.ResetOlod);
}
_isActive = false;
Debug.WriteLine($"Reset to custom LOD: TLOD: {_appSettingData.ApplicationSetting.DynamicLodSetting.ResetTlod}, OLOD: {_appSettingData.ApplicationSetting.DynamicLodSetting.ResetOlod}");
}
private static void HandleOnAltAboveGroundChanged(object sender, EventArgs e)
{
if (!_flightSimData.IsFlightStarted)
return;
var agl = _flightSimData.PlaneAltAboveGround < 0 ? 0 : _flightSimData.PlaneAltAboveGround;
if (_nextTlod != null && _nextTlod.Value.Agl <= agl)
{
_currentTlod = _nextTlod;
_nextTlod = _currentTlod.Next;
DynamicLodManager.WriteTlod(_currentTlod.Value.Lod);
}
else if (_currentTlod is { Previous: not null } && _currentTlod.Value.Agl > agl)
{
_currentTlod = _currentTlod.Previous;
if (_currentTlod != null)
{
_nextTlod = _currentTlod.Next;
DynamicLodManager.WriteTlod(_currentTlod.Value.Lod);
}
}
if (_nextOlod != null && _nextOlod.Value.Agl <= agl)
{
_currentOlod = _nextOlod;
_nextOlod = _currentOlod.Next;
DynamicLodManager.WriteOlod(_currentOlod.Value.Lod);
}
else if (_currentOlod is { Previous: not null } && _currentOlod.Value.Agl > agl)
{
_currentOlod = _currentOlod.Previous;
if (_currentOlod != null)
{
_nextOlod = _currentOlod.Next;
DynamicLodManager.WriteOlod(_currentOlod.Value.Lod);
}
}
if(_currentTlod != null && _currentOlod != null)
Debug.WriteLine($"Altitude: {agl}, TLOD: {_currentTlod.Value.Lod}, OLOD: {_currentOlod.Value.Lod}");
}
private static void WriteTlod(int value, bool isVr = false)
{
WriteMemory(isVr ? _addressTlodVr : _addressTlod, value / 100.0f);
}
private static void WriteOlod(int value, bool isVr = false)
{
WriteMemory(isVr ? _addressOlodVr : _addressOlod, value / 100.0f);
}
private static long GetSimModuleAddress()
{
if (_process == null)
return -1;
foreach (ProcessModule processModule in _process.Modules)
{
if (processModule.ModuleName == SIMMODULE_NAME)
return processModule.BaseAddress;
}
return -1;
}
private static void WriteMemory(long address, object value)
{
try
{
var buffer = StructureToByteArray(value);
PInvoke.NtWriteVirtualMemory(checked((int)_processHandle), address, buffer, buffer.Length, out _);
}
catch
{
// ignored
}
}
private static T ReadMemory<T>(long address) where T : struct
{
try
{
var byteSize = Marshal.SizeOf(typeof(T));
var buffer = new byte[byteSize];
PInvoke.NtReadVirtualMemory(checked((int)_processHandle), address, buffer, buffer.Length, out _);
return ByteArrayToStructure<T>(buffer);
}
catch
{
// ignored
}
return default(T);
}
private static byte[] StructureToByteArray(object obj)
{
var length = Marshal.SizeOf(obj);
var array = new byte[length];
var pointer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(obj, pointer, true);
Marshal.Copy(pointer, array, 0, length);
Marshal.FreeHGlobal(pointer);
return array;
}
private static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
var result = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
if(result != null)
return (T)result;
return default(T);
}
finally
{
handle.Free();
}
}
private static void InitializeData()
{
var agl = _flightSimData.PlaneAltAboveGround < 0 ? 0 : _flightSimData.PlaneAltAboveGround;
var tlod = _appSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.FirstOrDefault(x => x.Agl > agl);
_nextTlod = _appSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.Find(tlod);
tlod = _appSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.LastOrDefault(x => x.Agl <= agl);
_currentTlod = _appSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.Find(tlod) ?? _appSettingData.ApplicationSetting.DynamicLodSetting.TlodConfigs.Last;
if (_currentTlod != null)
DynamicLodManager.WriteTlod(_currentTlod.Value.Lod);
var olod = _appSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.FirstOrDefault(x => x.Agl > agl);
_nextOlod = _appSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.Find(olod);
olod = _appSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.LastOrDefault(x => x.Agl <= agl);
_currentOlod = _appSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.Find(olod) ?? _appSettingData.ApplicationSetting.DynamicLodSetting.OlodConfigs.Last;
if (_currentOlod != null)
DynamicLodManager.WriteOlod(_currentOlod.Value.Lod);
if (_currentTlod != null && _currentOlod != null)
Debug.WriteLine($"Initialize Altitude: {agl}, TLOD: {_currentTlod.Value.Lod}, OLOD: {_currentOlod.Value.Lod}");
}
//private static int ReadTlod(bool isVr = false)
//{
// return Convert.ToInt32(ReadMemory<float>(isVr ? _addressTlodVr : _addressTlod) * 100.0f);
//}
//private static int ReadOlod(bool isVr = false)
//{
// return Convert.ToInt32(ReadMemory<float>(isVr ? _addressOlodVr : _addressOlod) * 100.0f);
//}
}
}

View file

@ -0,0 +1,336 @@
using MSFSPopoutPanelManager.DomainModel.DynamicLod;
using MSFSPopoutPanelManager.DomainModel.Setting;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.SimConnectAgent;
using MSFSPopoutPanelManager.WindowsAgent;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Dynamic;
using System.Runtime.InteropServices;
namespace MSFSPopoutPanelManager.Orchestration
{
public class DynamicLodOrchestrator : BaseOrchestrator
{
private const string SIMMODULE_NAME = "WwiseLibPCx64P.dll";
private const int PROCESS_VM_OPERATION = 0x0008;
private const int PROCESS_VM_READ = 0x0010;
private const int PROCESS_VM_WRITE = 0x0020;
private const long OFFSET_MODULE_BASE =0x004B2368;
private const long OFFSET_POINTER_MAIN = 0x3D0;
private const long OFFSET_POINTER_TLOD_VR = 0x114;
private const long OFFSET_POINTER_TLOD = 0xC;
private const long OFFSET_POINTER_OLOD = 0xC;
private const long OFFSET_POINTER_CLOUDQ = 0x44;
private const long OFFSET_POINTER_CLOUDQ_VR = 0x108;
private const long OFFSET_POINTER_VR_MODE = 0x1C;
private const long OFFSET_POINTER_FG_MODE = 0x4A;
private const long OFFSET_POINTER_ANSIO_FILTER = -0x18;
private const long OFFSET_POINTER_WATER_WAVES = 0x3C;
private bool _isActive;
private WindowProcess _process;
private IntPtr _processHandle;
private long _processModuleAddress;
private long _addressTlod;
private long _addressOlod;
private long _addressTlodVr;
private long _addressOlodVr;
private long _addressCloudQ;
private long _addressCloudQVr;
private long _addressVrMode;
private long _addressFgMode;
private DynamicLodSetting DynamicLodSetting => AppSettingData.ApplicationSetting.DynamicLodSetting;
private DynamicLodSimData SimData => FlightSimData.DynamicLodSimData;
private DateTime _lastLodUpdateTime = DateTime.Now;
private bool _isDecreasedCloudQualityActive = false;
public DynamicLodOrchestrator(SharedStorage sharedStorage) : base(sharedStorage) {}
public void Attach()
{
if (AppSettingData == null || _isActive)
return;
_process = WindowProcessManager.SimulatorProcess;
_processModuleAddress = GetSimModuleAddress();
if (_process == null || _processModuleAddress == IntPtr.Zero)
return;
_processHandle = PInvoke.OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, _process.ProcessId);
if (_processHandle == IntPtr.Zero)
return;
_addressTlod = ReadMemory<long>(_processModuleAddress + OFFSET_MODULE_BASE) + OFFSET_POINTER_MAIN;
if (_addressTlod > 0)
{
_addressTlodVr = ReadMemory<long>(_addressTlod) + OFFSET_POINTER_TLOD_VR;
_addressTlod = ReadMemory<long>(_addressTlod) + OFFSET_POINTER_TLOD;
_addressOlod = _addressTlod + OFFSET_POINTER_OLOD;
_addressOlodVr = _addressTlodVr + OFFSET_POINTER_OLOD;
_addressCloudQ = _addressTlod + OFFSET_POINTER_CLOUDQ;
_addressCloudQVr = _addressCloudQ + OFFSET_POINTER_CLOUDQ_VR;
_addressVrMode = _addressTlod - OFFSET_POINTER_VR_MODE;
_addressFgMode = _addressTlod - OFFSET_POINTER_FG_MODE;
if (!MemoryBoundaryTest())
{
FileLogger.WriteLog("Unable to validate memory space for Dynamic LOD", StatusMessageType.Error);
return;
}
}
_isActive = true;
_lastLodUpdateTime = DateTime.Now;
_isDecreasedCloudQualityActive = false;
}
public void Detach()
{
if (DynamicLodSetting == null)
return;
if (DynamicLodSetting.ResetEnabled)
{
var isVr = ReadIsVr();
WriteMemory(isVr ? _addressTlodVr : _addressTlod, DynamicLodSetting.ResetTlod / 100.0f);
WriteMemory(isVr ? _addressOlodVr : _addressOlod, DynamicLodSetting.ResetOlod / 100.0f);
}
_isActive = false;
Debug.WriteLine($"Reset to custom LOD: TLOD: {DynamicLodSetting.ResetTlod}, OLOD: {DynamicLodSetting.ResetOlod}");
}
public int ReadTlod(bool isVr = false)
{
return Convert.ToInt32(ReadMemory<float>(isVr ? _addressTlodVr : _addressTlod) * 100.0f);
}
public int ReadOlod(bool isVr = false)
{
return Convert.ToInt32(ReadMemory<float>(isVr ? _addressOlodVr : _addressOlod) * 100.0f);
}
public string ReadCloudQuality(bool isVr = false)
{
return ReadCloudQualitySimValue(isVr) switch
{
0 => "Low",
1 => "Medium",
2 => "High",
3 => "Ultra",
_ => "N/A"
};
}
public int ReadCloudQualitySimValue(bool isVr = false)
{
return Convert.ToInt32(ReadMemory<int>(isVr ? _addressCloudQVr : _addressCloudQ));
}
public bool ReadIsVr()
{
return ReadMemory<int>(_addressVrMode) == 1;
}
public bool ReadIsFg(bool isVr)
{
if (isVr)
return false;
return ReadMemory<byte>(_addressFgMode) == 1;
}
public void UpdateLod(bool isVr)
{
if (DateTime.Now - _lastLodUpdateTime <= TimeSpan.FromSeconds(1))
return;
if (!FlightSimData.IsFlightStarted || !FlightSimData.IsInCockpit || (DynamicLodSetting.PauseOutsideCockpitView && FlightSimData.CameraState != CameraState.Cockpit))
return;
var deltaFps = SimData.Fps - DynamicLodSetting.TargetedFps;
if (Math.Abs(deltaFps) < DynamicLodSetting.TargetedFps * DynamicLodSetting.FpsTolerance / 100.0) // within FPS tolerance
return;
_lastLodUpdateTime = DateTime.Now;
SetTlod(deltaFps);
SetOlod();
}
private long GetSimModuleAddress()
{
if (_process == null)
return -1;
foreach (ProcessModule processModule in _process.Modules)
{
if (processModule.ModuleName == SIMMODULE_NAME)
return processModule.BaseAddress;
}
return -1;
}
private void WriteMemory(long address, object value)
{
try
{
var buffer = StructureToByteArray(value);
PInvoke.NtWriteVirtualMemory(checked((int)_processHandle), address, buffer, buffer.Length, out _);
}
catch
{
// ignored
}
}
private T ReadMemory<T>(long address) where T : struct
{
try
{
var byteSize = Marshal.SizeOf(typeof(T));
var buffer = new byte[byteSize];
PInvoke.NtReadVirtualMemory(checked((int)_processHandle), address, buffer, buffer.Length, out _);
return ByteArrayToStructure<T>(buffer);
}
catch
{
// ignored
}
return default(T);
}
private byte[] StructureToByteArray(object obj)
{
var length = Marshal.SizeOf(obj);
var array = new byte[length];
var pointer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(obj, pointer, true);
Marshal.Copy(pointer, array, 0, length);
Marshal.FreeHGlobal(pointer);
return array;
}
private T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
var result = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
if(result != null)
return (T)result;
return default(T);
}
finally
{
handle.Free();
}
}
private bool MemoryBoundaryTest()
{
// Boundary check a few known setting memory addresses to see if any fail which likely indicates MSFS memory map has changed
if (ReadTlod() < 10 || ReadTlod() > 1000 || ReadTlod(true) < 10 || ReadTlod(true) > 1000
|| ReadOlod() < 10 || ReadOlod() > 1000 || ReadOlod(true) < 10 || ReadOlod(true) > 1000
|| ReadCloudQuality() == "N/A" || ReadCloudQuality(true) == "N/A"
|| ReadMemory<int>(_addressVrMode) < 0 || ReadMemory<int>(_addressVrMode) > 1
|| ReadMemory<int>(_addressTlod + OFFSET_POINTER_ANSIO_FILTER) < 1 || ReadMemory<int>(_addressTlod + OFFSET_POINTER_ANSIO_FILTER) > 16
|| !(ReadMemory<int>(_addressTlod + OFFSET_POINTER_WATER_WAVES) == 128 || ReadMemory<int>(_addressTlod + OFFSET_POINTER_WATER_WAVES) == 256 || ReadMemory<int>(_addressTlod + OFFSET_POINTER_WATER_WAVES) == 512))
{
return false;
}
return true;
}
private void SetTlod(int deltaFps, bool isVr = false)
{
var tlodStep = Math.Max(5, Math.Abs(deltaFps / 2));
var newTlod = SimData.Tlod + Math.Sign(deltaFps) * tlodStep;
if (DynamicLodSetting.TlodMinOnGround && SimData.AltAboveGround <= DynamicLodSetting.AltTlodBase)
{
newTlod = DynamicLodSetting.TlodMin;
}
else if (newTlod < DynamicLodSetting.TlodMin)
{
newTlod = DynamicLodSetting.TlodMin;
}
else if (newTlod > DynamicLodSetting.TlodMax)
{
newTlod = DynamicLodSetting.TlodMax;
}
if (ReadTlod(isVr) == newTlod)
return;
// Adjust cloud quality if applicable
if (deltaFps < 0 && newTlod < DynamicLodSetting.CloudRecoveryTlod && !_isDecreasedCloudQualityActive)
{
_isDecreasedCloudQualityActive = true;
WriteMemory(isVr ? _addressCloudQVr : _addressCloudQ, ReadCloudQualitySimValue(isVr) - 1); // High
_lastLodUpdateTime = _lastLodUpdateTime.AddSeconds(2); // Add extra delay for cloud setting to take effect
Debug.WriteLine("New Cloud Quality written - 2.");
return;
}
if (deltaFps > 0 && newTlod >= DynamicLodSetting.CloudRecoveryTlod && _isDecreasedCloudQualityActive)
{
_isDecreasedCloudQualityActive = false;
WriteMemory(isVr ? _addressCloudQVr : _addressCloudQ, ReadCloudQualitySimValue(isVr) + 1); // Ultra
_lastLodUpdateTime = _lastLodUpdateTime.AddSeconds(2);
Debug.WriteLine("New Cloud Quality written - 3.");
return;
}
Debug.WriteLine($"New TLOD written - {newTlod}.");
WriteMemory(isVr ? _addressTlodVr : _addressTlod, newTlod / 100.0f);
}
private void SetOlod(bool isVr = false)
{
int newOlod;
if (SimData.AltAboveGround < DynamicLodSetting.AltOlodBase)
{
newOlod = DynamicLodSetting.OlodBase;
}
else if (SimData.AltAboveGround > DynamicLodSetting.AltOlodTop)
{
newOlod = DynamicLodSetting.OlodTop;
}
else
{
newOlod = Convert.ToInt32(DynamicLodSetting.OlodBase - (DynamicLodSetting.OlodBase - DynamicLodSetting.OlodTop) * (SimData.AltAboveGround - DynamicLodSetting.AltOlodBase) / (DynamicLodSetting.AltOlodTop - DynamicLodSetting.AltOlodBase));
}
if (ReadOlod(isVr) == newOlod)
return;
Debug.WriteLine($"New OLOD written - {newOlod}.");
WriteMemory(isVr ? _addressOlodVr : _addressOlod, newOlod / 100.0f);
}
}
}

View file

@ -2,6 +2,7 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.ComponentModel;
using MSFSPopoutPanelManager.DomainModel.DynamicLod;
using MSFSPopoutPanelManager.SimConnectAgent;
namespace MSFSPopoutPanelManager.Orchestration
@ -15,8 +16,6 @@ namespace MSFSPopoutPanelManager.Orchestration
IsSimConnectActive = false;
}
public event EventHandler OnAltAboveGroundChanged;
public bool IsSimConnectActive { get; set; }
public string AircraftName { get; set; }
@ -43,8 +42,6 @@ namespace MSFSPopoutPanelManager.Orchestration
public bool PlaneInParkingSpot { get; set; }
public int PlaneAltAboveGround { get; set; }
public bool IsSimulatorStarted { get; set; }
public bool IsSimConnectDataReceived { get; set; }
@ -55,6 +52,8 @@ namespace MSFSPopoutPanelManager.Orchestration
public IHudBarData HudBarData { get; set; }
public DynamicLodSimData DynamicLodSimData { get; set; } = new();
[IgnorePropertyChanged]
internal ProfileData ProfileDataRef { get; set; }
@ -65,9 +64,6 @@ namespace MSFSPopoutPanelManager.Orchestration
// Automatic switching of active profile when SimConnect active aircraft change
if (e.PropertyName == "AircraftName")
ProfileDataRef.AutoSwitchProfile();
if(e.PropertyName == "PlaneAltAboveGround")
OnAltAboveGroundChanged?.Invoke(this, EventArgs.Empty);
}
public void Reset()
@ -85,7 +81,6 @@ namespace MSFSPopoutPanelManager.Orchestration
PlaneInParkingSpot = false;
CameraState = CameraState.Unknown;
IsSimulatorStarted = false;
PlaneAltAboveGround = 0;
CameraViewTypeAndIndex1Max = 0;
CameraViewTypeAndIndex2Max = 0;
}

View file

@ -15,11 +15,13 @@ namespace MSFSPopoutPanelManager.Orchestration
private System.Timers.Timer _msfsGameExitDetectionTimer;
private SimConnectProvider _simConnectProvider;
private DynamicLodOrchestrator _dynamicLodOrchestrator;
private bool _isTurnedOnPower;
private bool _isTurnedOnAvionics;
public FlightSimOrchestrator(SharedStorage sharedStorage) : base(sharedStorage)
public FlightSimOrchestrator(SharedStorage sharedStorage, DynamicLodOrchestrator dynamicLodOrchestrator) : base(sharedStorage)
{
_dynamicLodOrchestrator = dynamicLodOrchestrator;
_simConnectProvider = new SimConnectProvider();
}
@ -40,9 +42,7 @@ namespace MSFSPopoutPanelManager.Orchestration
WindowProcessManager.GetSimulatorProcess(); // refresh simulator process
DetectMsfsExit();
// Attach in memory override for Dynamic LOD
if (AppSettingData != null && AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
DynamicLodManager.Attach(FlightSimData, AppSettingData);
StartDynamicLod();
};
_simConnectProvider.OnDisconnected += (_, _) =>
@ -61,61 +61,39 @@ namespace MSFSPopoutPanelManager.Orchestration
_simConnectProvider.OnSimConnectDataRequiredRefreshed += (_, e) =>
{
var electricalMasterBattery = Convert.ToBoolean(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.ElectricalMasterBattery).Value);
if (electricalMasterBattery != FlightSimData.ElectricalMasterBatteryStatus)
FlightSimData.ElectricalMasterBatteryStatus = electricalMasterBattery;
var avionicsMasterSwitch = Convert.ToBoolean(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.AvionicsMasterSwitch).Value);
if (avionicsMasterSwitch != FlightSimData.AvionicsMasterSwitchStatus)
FlightSimData.AvionicsMasterSwitchStatus = avionicsMasterSwitch;
var trackIR = Convert.ToBoolean(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.TrackIREnable).Value);
if (trackIR != FlightSimData.TrackIRStatus)
FlightSimData.TrackIRStatus = trackIR;
var cameraStateInt = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraState).Value);
var result = Enum.TryParse<CameraState>(cameraStateInt.ToString(), out var cameraState);
if (!result)
cameraState = CameraState.Unknown;
if (cameraState != FlightSimData.CameraState)
FlightSimData.CameraState = cameraState;
var cockpitCameraZoom = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CockpitCameraZoom).Value);
if (cockpitCameraZoom != FlightSimData.CockpitCameraZoom)
FlightSimData.CockpitCameraZoom = cockpitCameraZoom;
var planeAltAboveGround = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.PlaneAltAboveGround).Value);
if (planeAltAboveGround != FlightSimData.PlaneAltAboveGround)
FlightSimData.PlaneAltAboveGround = planeAltAboveGround;
var cameraViewTypeAndIndex0 = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex0).Value);
if (cameraViewTypeAndIndex0 != FlightSimData.CameraViewTypeAndIndex0)
FlightSimData.CameraViewTypeAndIndex0 = cameraViewTypeAndIndex0;
var cameraViewTypeAndIndex1 = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex1).Value);
if (cameraViewTypeAndIndex1 != FlightSimData.CameraViewTypeAndIndex1)
FlightSimData.CameraViewTypeAndIndex1 = cameraViewTypeAndIndex1;
var cameraViewTypeAndIndex1Max = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex1Max).Value);
if (cameraViewTypeAndIndex1Max != FlightSimData.CameraViewTypeAndIndex1Max)
FlightSimData.CameraViewTypeAndIndex1Max = cameraViewTypeAndIndex1Max;
var cameraViewTypeAndIndex2Max = Convert.ToInt32(e.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex2Max).Value);
if (cameraViewTypeAndIndex2Max != FlightSimData.CameraViewTypeAndIndex2Max)
FlightSimData.CameraViewTypeAndIndex2Max = cameraViewTypeAndIndex2Max;
FlightSimData.IsSimConnectDataReceived = true;
MapRequiredSimConnectData(e);
};
_simConnectProvider.OnSimConnectDataHudBarRefreshed += (_, e) =>
{
if (ProfileData.ActiveProfile.ProfileSetting.HudBarConfig.IsEnabled)
MapHudBarSimConnectData(e);
if (!ProfileData.ActiveProfile.ProfileSetting.HudBarConfig.IsEnabled || !FlightSimData.IsFlightStarted)
return;
MapHudBarSimConnectData(e);
};
_simConnectProvider.OnSimConnectDataDynamicLodRefreshed += (_, e) =>
{
if (!AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled || !FlightSimData.IsFlightStarted || !WindowActionManager.IsMsfsInFocus())
return;
var isVr = _dynamicLodOrchestrator.ReadIsVr();
MapDynamicLodSimConnectData(e, isVr);
_dynamicLodOrchestrator.UpdateLod(isVr);
};
_simConnectProvider.OnSimConnectDataEventFrameRefreshed += (_, e) =>
{
if (!AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled || !FlightSimData.IsFlightStarted)
return;
MapEventFrameData(e);
};
_simConnectProvider.OnActiveAircraftChanged += (_, e) =>
{
var aircraftName = String.IsNullOrEmpty(e) ? null : e;
var aircraftName = string.IsNullOrEmpty(e) ? null : e;
if (FlightSimData.AircraftName != aircraftName)
{
FlightSimData.AircraftName = aircraftName;
@ -392,9 +370,7 @@ namespace MSFSPopoutPanelManager.Orchestration
FlightSimData.IsFlightStarted = true;
// Attach in memory override for Dynamic LOD
if (AppSettingData != null && AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
DynamicLodManager.Attach(FlightSimData, AppSettingData);
StartDynamicLod();
}
private void HandleOnFlightStopped(object sender, EventArgs e)
@ -409,9 +385,8 @@ namespace MSFSPopoutPanelManager.Orchestration
FlightSimData.IsFlightStarted = false;
// Detach in memory override for Dynamic LOD
if (AppSettingData != null && AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
DynamicLodManager.Detach();
StopDynamicLod();
FlightSimData.DynamicLodSimData.Clear();
}
private void DetectMsfsExit()
@ -436,6 +411,50 @@ namespace MSFSPopoutPanelManager.Orchestration
};
}
private void MapRequiredSimConnectData(List<SimDataItem> simData)
{
var electricalMasterBattery = Convert.ToBoolean(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.ElectricalMasterBattery).Value);
if (electricalMasterBattery != FlightSimData.ElectricalMasterBatteryStatus)
FlightSimData.ElectricalMasterBatteryStatus = electricalMasterBattery;
var avionicsMasterSwitch = Convert.ToBoolean(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.AvionicsMasterSwitch).Value);
if (avionicsMasterSwitch != FlightSimData.AvionicsMasterSwitchStatus)
FlightSimData.AvionicsMasterSwitchStatus = avionicsMasterSwitch;
var trackIR = Convert.ToBoolean(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.TrackIREnable).Value);
if (trackIR != FlightSimData.TrackIRStatus)
FlightSimData.TrackIRStatus = trackIR;
var cameraStateInt = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraState).Value);
var result = Enum.TryParse<CameraState>(cameraStateInt.ToString(), out var cameraState);
if (!result)
cameraState = CameraState.Unknown;
if (cameraState != FlightSimData.CameraState)
FlightSimData.CameraState = cameraState;
var cockpitCameraZoom = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CockpitCameraZoom).Value);
if (cockpitCameraZoom != FlightSimData.CockpitCameraZoom)
FlightSimData.CockpitCameraZoom = cockpitCameraZoom;
var cameraViewTypeAndIndex0 = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex0).Value);
if (cameraViewTypeAndIndex0 != FlightSimData.CameraViewTypeAndIndex0)
FlightSimData.CameraViewTypeAndIndex0 = cameraViewTypeAndIndex0;
var cameraViewTypeAndIndex1 = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex1).Value);
if (cameraViewTypeAndIndex1 != FlightSimData.CameraViewTypeAndIndex1)
FlightSimData.CameraViewTypeAndIndex1 = cameraViewTypeAndIndex1;
var cameraViewTypeAndIndex1Max = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex1Max).Value);
if (cameraViewTypeAndIndex1Max != FlightSimData.CameraViewTypeAndIndex1Max)
FlightSimData.CameraViewTypeAndIndex1Max = cameraViewTypeAndIndex1Max;
var cameraViewTypeAndIndex2Max = Convert.ToInt32(simData.Find(d => d.PropertyName == SimDataDefinitions.PropName.CameraViewTypeAndIndex2Max).Value);
if (cameraViewTypeAndIndex2Max != FlightSimData.CameraViewTypeAndIndex2Max)
FlightSimData.CameraViewTypeAndIndex2Max = cameraViewTypeAndIndex2Max;
FlightSimData.IsSimConnectDataReceived = true;
}
private void MapHudBarSimConnectData(List<SimDataItem> simData)
{
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.ElevatorTrim, FlightSimData.HudBarData.ElevatorTrim, out var newValue))
@ -466,6 +485,49 @@ namespace MSFSPopoutPanelManager.Orchestration
FlightSimData.HudBarData.SimRate = newValue;
}
private void MapDynamicLodSimConnectData(List<SimDataItem> simData, bool isVr)
{
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.PlaneAltAboveGround, FlightSimData.DynamicLodSimData.Agl, out var newValue))
FlightSimData.DynamicLodSimData.Agl = newValue;
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.PlaneAltAboveGround, FlightSimData.DynamicLodSimData.AltAboveGround, out newValue))
FlightSimData.DynamicLodSimData.AltAboveGround = newValue;
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.PlaneAltAboveGroundMinusCg, FlightSimData.DynamicLodSimData.AltAboveGroundMinusCg, out newValue))
FlightSimData.DynamicLodSimData.AltAboveGroundMinusCg = newValue;
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.GroundVelocity, FlightSimData.DynamicLodSimData.GroundVelocity, out newValue))
FlightSimData.DynamicLodSimData.GroundVelocity = newValue;
if (CompareSimConnectData(simData, SimDataDefinitions.PropName.SimOnGround, 1.0f, out newValue))
FlightSimData.DynamicLodSimData.PlaneOnGround = Convert.ToBoolean(newValue);
var tlod = _dynamicLodOrchestrator.ReadTlod(isVr);
if (FlightSimData.DynamicLodSimData.Tlod != tlod)
FlightSimData.DynamicLodSimData.Tlod = tlod;
var olod = _dynamicLodOrchestrator.ReadOlod(isVr);
if (FlightSimData.DynamicLodSimData.Olod != olod)
FlightSimData.DynamicLodSimData.Olod = olod;
var cloudQuality = _dynamicLodOrchestrator.ReadCloudQuality(isVr);
if (FlightSimData.DynamicLodSimData.CloudQuality != cloudQuality)
FlightSimData.DynamicLodSimData.CloudQuality = cloudQuality;
if (FlightSimData.IsFlightStarted && FlightSimData.IsInCockpit &&
(!AppSettingData.ApplicationSetting.DynamicLodSetting.PauseOutsideCockpitView || (AppSettingData.ApplicationSetting.DynamicLodSetting.PauseOutsideCockpitView && FlightSimData.CameraState == CameraState.Cockpit)))
FlightSimData.DynamicLodSimData.Fps = FpsCalc.GetAverageFps(_dynamicLodOrchestrator.ReadIsFg(isVr) ? _currentFps * 2 : _currentFps);
}
private int _currentFps;
private void MapEventFrameData(int fps)
{
if (!AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
return;
_currentFps = fps;
}
private bool CompareSimConnectData(List<SimDataItem> simData, string propName, double source, out double newValue)
{
var propData = simData.Find(d => d.PropertyName == propName);
@ -486,5 +548,26 @@ namespace MSFSPopoutPanelManager.Orchestration
newValue = 0;
return false;
}
private void StartDynamicLod()
{
if (_simConnectProvider != null)
{
// Attach in memory override for Dynamic LOD
if (AppSettingData != null && AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
_dynamicLodOrchestrator.Attach();
_simConnectProvider.StartDynamicLod();
}
}
private void StopDynamicLod()
{
// Detach in memory override for Dynamic LOD
if (AppSettingData != null && AppSettingData.ApplicationSetting.DynamicLodSetting.IsEnabled)
_dynamicLodOrchestrator.Detach();
_simConnectProvider.StopDynamicLod();
}
}
}

View file

@ -11,9 +11,9 @@
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.Orchestration</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>

View file

@ -11,9 +11,9 @@
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.Shared</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>

View file

@ -4,6 +4,7 @@
{
REQUIRED_DEFINITION = 0,
HUDBAR_DEFINITION,
DYNAMICLOD_DEFINITION,
WRITABLE_TRACK_IR_DEFINITION,
WRITABLE_COCKPIT_CAMERA_ZOOM_DEFINITION,
WRITABLE_COCKPIT_CAMERA_STATE_DEFINITION,
@ -17,6 +18,7 @@
{
REQUIRED_REQUEST = 0,
HUDBAR_REQUEST,
DYNAMICLOD_REQUEST,
NA
}

View file

@ -0,0 +1,36 @@
using System;
using System.Linq;
namespace MSFSPopoutPanelManager.SimConnectAgent
{
public class FpsCalc
{
private const int FpsLen = 50;
private static readonly float[] FpsStatistic = new float[FpsLen];
private static int _fpsIndex = -1;
public static int GetAverageFps(int newValue)
{
if (_fpsIndex == -1)
{
for (var i = 0; i < FpsLen; i++)
FpsStatistic[i] = newValue;
_fpsIndex = 1;
}
else
{
FpsStatistic[_fpsIndex] = newValue;
_fpsIndex++;
if (_fpsIndex >= FpsLen)
_fpsIndex = 0;
}
var fps = 0;
if (_fpsIndex != -1)
fps = Convert.ToInt32(FpsStatistic.Sum() / FpsLen);
return fps;
}
}
}

View file

@ -5,15 +5,17 @@
SIM_START,
SIM_STOP,
AIRCRAFT_LOADED,
VIEW
VIEW,
FRAME
}
public enum ActionEvent
{
DUMMY1, // must register 4 dummy events to reserve space for SimConnectEvent above
DUMMY1, // must register 5 dummy events to reserve space for SimConnectEvent above
DUMMY2,
DUMMY3,
DUMMY4,
DUMMY5,
MASTER_BATTERY_SET,
AVIONICS_MASTER_SET,
PAUSE_SET,

View file

@ -11,6 +11,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
{
private const int MSFS_DATA_REFRESH_TIMEOUT = 500;
private const int MSFS_HUDBAR_DATA_REFRESH_TIMEOUT = 200;
private const int MSFS_DYNAMICLOD_DATA_REFRESH_TIMEOUT = 300;
private readonly SimConnector _simConnector;
@ -19,6 +20,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
private System.Timers.Timer _requiredRequestDataTimer;
private System.Timers.Timer _hudBarRequestDataTimer;
private System.Timers.Timer _dynamicLodRequestDataTimer;
private bool _isPowerOnForPopOut;
private bool _isAvionicsOnForPopOut;
private bool _isTrackIRManaged;
@ -33,6 +35,8 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
public event EventHandler OnException;
public event EventHandler<List<SimDataItem>> OnSimConnectDataRequiredRefreshed;
public event EventHandler<List<SimDataItem>> OnSimConnectDataHudBarRefreshed;
public event EventHandler<List<SimDataItem>> OnSimConnectDataDynamicLodRefreshed;
public event EventHandler<int> OnSimConnectDataEventFrameRefreshed;
public event EventHandler<string> OnActiveAircraftChanged;
public SimConnectProvider()
@ -43,7 +47,9 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_simConnector.OnException += HandleSimException;
_simConnector.OnReceiveSystemEvent += HandleReceiveSystemEvent;
_simConnector.OnReceivedRequiredData += HandleRequiredDataReceived;
_simConnector.OnReceivedHudBarData += HandleHudBarDataReceived;
_simConnector.OnReceivedHudBarData += (_, e) => OnSimConnectDataHudBarRefreshed?.Invoke(this, e);
_simConnector.OnReceivedDynamicLodData += (_, e) => OnSimConnectDataDynamicLodRefreshed?.Invoke(this, e);
_simConnector.OnReceivedEventFrameData += (_, e) => OnSimConnectDataEventFrameRefreshed?.Invoke(this, e);
_simConnector.OnActiveAircraftChanged += (_, e) => OnActiveAircraftChanged?.Invoke(this, e);
_isHandlingCriticalError = false;
@ -108,6 +114,29 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_hudBarRequestDataTimer.Stop();
}
public void StartDynamicLod()
{
if (_dynamicLodRequestDataTimer.Enabled)
return;
// shut down data request and wait for the last request to be completed
_dynamicLodRequestDataTimer.Stop();
Thread.Sleep(MSFS_DYNAMICLOD_DATA_REFRESH_TIMEOUT);
_simConnector.SetSimConnectDynamicLodDataDefinition();
_dynamicLodRequestDataTimer.Start();
_simConnector.StartReceiveFrameData();
}
public void StopDynamicLod()
{
_dynamicLodRequestDataTimer.Stop();
_simConnector.StopReceiveFrameData();
}
public void TurnOnPower(bool isRequiredForColdStart)
{
if (!isRequiredForColdStart || _requiredSimData == null)
@ -295,10 +324,29 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
// ignored
}
};
if (_isHudBarDataActive)
SetHudBarConfig(_activeHudBarType);
// Setup dynamic data request timer
_dynamicLodRequestDataTimer = new()
{
Interval = MSFS_DYNAMICLOD_DATA_REFRESH_TIMEOUT,
};
_dynamicLodRequestDataTimer.Stop();
_dynamicLodRequestDataTimer.Elapsed += (_, _) =>
{
try
{
_simConnector.RequestDynamicLodData();
}
catch
{
// ignored
}
};
OnConnected?.Invoke(this, EventArgs.Empty);
}
@ -306,6 +354,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
{
_requiredRequestDataTimer.Stop();
_hudBarRequestDataTimer.Stop();
_dynamicLodRequestDataTimer.Stop();
OnDisconnected?.Invoke(this, EventArgs.Empty);
StopAndReconnect();
}
@ -316,6 +365,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_requiredRequestDataTimer.Stop();
_hudBarRequestDataTimer.Stop();
_dynamicLodRequestDataTimer.Stop();
if (!_isHandlingCriticalError)
{
@ -332,11 +382,6 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
OnSimConnectDataRequiredRefreshed?.Invoke(this, e);
}
private void HandleHudBarDataReceived(object sender, List<SimDataItem> e)
{
OnSimConnectDataHudBarRefreshed?.Invoke(this, e);
}
private CameraState _currentCameraState = CameraState.Unknown;
private void DetectFlightStartedOrStopped(List<SimDataItem> simData)
@ -375,6 +420,8 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_isHudBarDataActive = false;
_hudBarRequestDataTimer.Stop();
_dynamicLodRequestDataTimer.Stop();
}
break;
}

View file

@ -20,15 +20,18 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
private bool _isDisabledReconnect;
private readonly List<SimConnectDataDefinition> _simConnectRequiredDataDefinitions = SimDataDefinitions.GetRequiredDefinitions();
private readonly List<SimConnectDataDefinition> _simConnectDynamicLodDataDefinitions = SimDataDefinitions.GetDynamicLodDefinitions();
private List<SimConnectDataDefinition> _simConnectHudBarDataDefinitions;
private readonly FieldInfo[] _simConnectStructFields = typeof(SimConnectStruct).GetFields(BindingFlags.Public | BindingFlags.Instance);
public event EventHandler<string> OnException;
public event EventHandler<List<SimDataItem>> OnReceivedRequiredData;
public event EventHandler<List<SimDataItem>> OnReceivedHudBarData;
public event EventHandler<List<SimDataItem>> OnReceivedDynamicLodData;
public event EventHandler OnConnected;
public event EventHandler OnDisconnected;
public event EventHandler<SimConnectEvent> OnReceiveSystemEvent;
public event EventHandler<int> OnReceivedEventFrameData;
public event EventHandler<string> OnActiveAircraftChanged;
public bool Connected { get; set; }
@ -66,6 +69,11 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
AddHudBarDataDefinitions();
}
public void SetSimConnectDynamicLodDataDefinition()
{
AddDynamicLodDataDefinitions();
}
private void HandleOnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
{
ReceiveMessage();
@ -124,6 +132,26 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
}
}
public void RequestDynamicLodData()
{
if (_simConnect == null || !Connected)
return;
try
{
if (_simConnectDynamicLodDataDefinitions != null)
_simConnect.RequestDataOnSimObjectType(DataRequest.DYNAMICLOD_REQUEST, DataDefinition.DYNAMICLOD_DEFINITION, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
}
catch
{
if (!_isDisabledReconnect)
_isDisabledReconnect = true;
OnException?.Invoke(this, null);
}
}
public void ReceiveMessage()
{
if (_simConnect == null)
@ -208,6 +236,25 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
}
}
public void StartReceiveFrameData()
{
if (_simConnect == null)
return;
_simConnect.OnRecvEventFrame -= HandleOnRecvEventFrame;
_simConnect.OnRecvEventFrame += HandleOnRecvEventFrame;
_simConnect.UnsubscribeFromSystemEvent(SimConnectEvent.FRAME);
_simConnect.SubscribeToSystemEvent(SimConnectEvent.FRAME, "Frame");
}
public void StopReceiveFrameData()
{
_simConnect.OnRecvEventFrame -= HandleOnRecvEventFrame;
_simConnect.UnsubscribeFromSystemEvent(SimConnectEvent.FRAME);
}
private void InitializeSimConnect()
{
Debug.WriteLine("Trying to start simConnect");
@ -234,8 +281,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_simConnect.SubscribeToSystemEvent(SimConnectEvent.VIEW, "View");
_simConnect.UnsubscribeFromSystemEvent(SimConnectEvent.AIRCRAFT_LOADED);
_simConnect.SubscribeToSystemEvent(SimConnectEvent.AIRCRAFT_LOADED, "AircraftLoaded");
AddRequiredDataDefinitions();
SetupActionEvents();
@ -256,6 +302,14 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
Connected = true;
}
private void HandleOnRecvEventFrame(SimConnect sender, SIMCONNECT_RECV_EVENT_FRAME data)
{
if (data == null)
return;
OnReceivedEventFrameData?.Invoke(this, Convert.ToInt32(data.fFrameRate));
}
private void HandleOnRecvSystemState(SimConnect sender, SIMCONNECT_RECV_SYSTEM_STATE data)
{
switch ((SystemStateRequestId)Enum.Parse(typeof(SystemStateRequestId), data.dwRequestID.ToString()))
@ -361,6 +415,41 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
_simConnect.RegisterDataDefineStruct<SimConnectStruct>(DataDefinition.HUDBAR_DEFINITION);
}
private void AddDynamicLodDataDefinitions()
{
if (_simConnect == null)
return;
_simConnect.ClearDataDefinition(DataDefinition.DYNAMICLOD_DEFINITION);
if (_simConnectDynamicLodDataDefinitions == null)
return;
foreach (var definition in _simConnectDynamicLodDataDefinitions)
{
if (definition.DefinitionId != DataDefinition.DYNAMICLOD_DEFINITION ||
definition.DataDefinitionType != DataDefinitionType.SimConnect) continue;
SIMCONNECT_DATATYPE simConnectDataType;
switch (definition.DataType)
{
case DataType.String:
simConnectDataType = SIMCONNECT_DATATYPE.STRING256;
break;
case DataType.Float64:
simConnectDataType = SIMCONNECT_DATATYPE.FLOAT64;
break;
default:
simConnectDataType = SIMCONNECT_DATATYPE.FLOAT64;
break;
}
_simConnect.AddToDataDefinition(definition.DefinitionId, definition.VariableName, definition.SimConnectUnit, simConnectDataType, 0.0f, SimConnect.SIMCONNECT_UNUSED);
}
_simConnect.RegisterDataDefineStruct<SimConnectStruct>(DataDefinition.DYNAMICLOD_DEFINITION);
}
private void HandleOnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
{
Stop();
@ -404,6 +493,9 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
case (int)DataRequest.HUDBAR_REQUEST:
ParseHudBarReceivedSimData(data);
break;
case (int)DataRequest.DYNAMICLOD_REQUEST:
ParseDynamicLodReceivedSimData(data);
break;
}
}
@ -483,6 +575,46 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
}
}
private void ParseDynamicLodReceivedSimData(SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)
{
try
{
if (_simConnectDynamicLodDataDefinitions == null)
return;
var simData = new List<SimDataItem>();
var simDataStruct = (SimConnectStruct)data.dwData[0];
var i = 0;
lock (_simConnectDynamicLodDataDefinitions)
{
foreach (var definition in _simConnectDynamicLodDataDefinitions)
{
if (definition.DataDefinitionType != DataDefinitionType.SimConnect)
continue;
var dataValue = _simConnectStructFields[i].GetValue(simDataStruct);
var simDataItem = new SimDataItem
{
PropertyName = definition.PropName,
Value = dataValue == null ? 0 : (double)dataValue
};
simData.Add(simDataItem);
i++;
}
}
OnReceivedDynamicLodData?.Invoke(this, simData);
}
catch (Exception ex)
{
FileLogger.WriteException($"SimConnector: SimConnect received dynamic lod data exception - {ex.Message}", ex);
StopAndReconnect();
}
}
private void SetActiveAircraftTitle(string aircraftFilePath)
{
var filePathToken = aircraftFilePath.Split(@"\");

View file

@ -14,7 +14,6 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.PlaneInParkingSpot, VariableName = "ATC ON PARKING SPOT", SimConnectUnit = "Bool", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.CameraState, VariableName = "CAMERA STATE", SimConnectUnit = "Number", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.CockpitCameraZoom, VariableName = "COCKPIT CAMERA ZOOM", SimConnectUnit = "Percentage", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.PlaneAltAboveGround, VariableName = "PLANE ALT ABOVE GROUND", SimConnectUnit = "Feet", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.CameraViewTypeAndIndex0, VariableName = "CAMERA VIEW TYPE AND INDEX:0", SimConnectUnit = "Enum", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.CameraViewTypeAndIndex1, VariableName = "CAMERA VIEW TYPE AND INDEX:1", SimConnectUnit = "Enum", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.REQUIRED_DEFINITION, RequestId = DataRequest.REQUIRED_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.CameraViewTypeAndIndex1Max, VariableName = "CAMERA VIEW TYPE AND INDEX MAX:1", SimConnectUnit = "Number", DataType = DataType.Float64 },
@ -44,6 +43,18 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
}
}
public static List<SimConnectDataDefinition> GetDynamicLodDefinitions()
{
var definitions = new List<SimConnectDataDefinition>
{
new() { DefinitionId = DataDefinition.DYNAMICLOD_DEFINITION, RequestId = DataRequest.DYNAMICLOD_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.PlaneAltAboveGround, VariableName = "PLANE ALT ABOVE GROUND", SimConnectUnit = "feet", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.DYNAMICLOD_DEFINITION, RequestId = DataRequest.DYNAMICLOD_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.PlaneAltAboveGroundMinusCg, VariableName = "PLANE ALT ABOVE GROUND MINUS CG", SimConnectUnit = "feet", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.DYNAMICLOD_DEFINITION, RequestId = DataRequest.DYNAMICLOD_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.SimOnGround, VariableName = "SIM ON GROUND", SimConnectUnit = "Bool", DataType = DataType.Float64 },
new() { DefinitionId = DataDefinition.DYNAMICLOD_DEFINITION, RequestId = DataRequest.DYNAMICLOD_REQUEST, DataDefinitionType = DataDefinitionType.SimConnect, PropName = PropName.GroundVelocity, VariableName = "GROUND VELOCITY", SimConnectUnit = "knots", DataType = DataType.Float64 }
};
return definitions;
}
private static List<SimConnectDataDefinition> GetSharedHudBarDefinitions()
{
var definitions = new List<SimConnectDataDefinition>
@ -80,7 +91,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
};
return definitions;
}
public static class PropName
{
public static string ElectricalMasterBattery = "ElectricalMasterBattery";
@ -96,6 +107,9 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
// Dynamic LOD
public static string PlaneAltAboveGround = "PlaneAltAboveGround";
public static string PlaneAltAboveGroundMinusCg = "PlaneAltAboveGroundMinusCg";
public static string SimOnGround = "SimOnGround";
public static string GroundVelocity = "GroundVelocity";
// Hud Bar data
public static string ElevatorTrim = "ElevatorTrim";

View file

@ -11,9 +11,9 @@
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.SimConnectAgent</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>

View file

@ -293,7 +293,12 @@ namespace MSFSPopoutPanelManager.WindowsAgent
var bottomEdge = rect.Y + rect.Height;
return point.X >= rect.X && point.X <= rightEdge && point.Y >= rect.Y && point.Y <= bottomEdge;
}
public static bool IsMsfsInFocus()
{
var handle = PInvoke.GetForegroundWindow();
return PInvoke.GetWindowText(handle).Substring(0, 26).Equals("Microsoft Flight Simulator", StringComparison.InvariantCultureIgnoreCase);
}
}
}

View file

@ -11,9 +11,9 @@
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.WindowsAgent</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.1.0.4</Version>
<AssemblyVersion>4.1.0.4</AssemblyVersion>
<FileVersion>4.1.0.4</FileVersion>
<Version>4.1.1.0</Version>
<AssemblyVersion>4.1.1.0</AssemblyVersion>
<FileVersion>4.1.1.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>