Skip to content
This repository was archived by the owner on Aug 10, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions PRUNner/App/ViewModels/LogisticsViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PRUNner.Backend.Data;
using PRUNner.Backend.BasePlanner;
using ReactiveUI;
using Avalonia.Media.Imaging;
using System.IO;
using PRUNner.App.Controls;
using DynamicData.Kernel;
using PRUNner.Backend.Utility;

namespace PRUNner.App.ViewModels
{
public class LogisticsViewModel : ViewModelBase
{
public LogisticsViewModel()
{
Tickers = new Dictionary<string, bool>();
}

public LogisticsViewModel(Empire empire)
{
this.empire = empire;
Tickers = new Dictionary<string, bool>();
}

public bool GraphvizInstalled { get; set; } = true;

public IBitmap GraphvizImage { get; set; }

public Dictionary<string, bool> Tickers { get; set; }

private List<PlanetaryBase> FindSourcesFor(string ticker)
{
//TODO make this more readable / efficient. LINQ?
return empire.PlanetaryBases.AsList().FindAll(b => b.ProductionTable.Outputs.AsList().Exists(o => o.Material.Ticker == ticker));
}

private string GenerateDotGraph()
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.AppendLine("digraph {");
strBuilder.AppendLine("concentrate=true"); // makes graphs with many edges going in the same direction more readable
strBuilder.AppendLine("ranksep=2.0");

// add all bases as nodes
// names like KI-401b have to be in quotes, so just put quotes around every planet name to be safe
strBuilder.AppendLine("# one node for each base");
foreach (var plBase in empire.PlanetaryBases)
{
strBuilder.AppendFormat("\"{0}\"", plBase.Planet.Name);
strBuilder.AppendLine();
}

foreach (var plBase in empire.PlanetaryBases)
{
strBuilder.AppendLine();
strBuilder.AppendFormat("# adding edges for {0}", plBase.Planet.Name);
strBuilder.AppendLine();
foreach (var product in plBase.ProductionTable.Inputs)
{
if (!Tickers.GetValueOrDefault(product.Material.Ticker, false)) continue;

var sources = FindSourcesFor(product.Material.Ticker);
foreach (var source in sources)
{
strBuilder.AppendFormat("\"{0}\" -> \"{1}\" [label={2} color={3} fontcolor={3}]", source.Planet.Name, plBase.Planet.Name, product.Material.Ticker, MaterialToDotColorString(product.Material));
strBuilder.AppendLine();
}

if (sources.Count == 0)
{
strBuilder.AppendFormat("CX -> \"{0}\" [label={1} color={2} fontcolor={2}]", plBase.Planet.Name, product.Material.Ticker, MaterialToDotColorString(product.Material));
}
}
}
strBuilder.AppendLine("}");

return strBuilder.ToString();
}

private static string MaterialToDotColorString(MaterialData material)
{
var materialColors = ItemBoxControl.MaterialColors[material.Category];
return string.Format("\"#{0:X}{1:X}{2:X}\"", materialColors.Foreground.Color.R, materialColors.Foreground.Color.G, materialColors.Foreground.Color.B);
}

private void GenerateGraphBitmap()
{
const string dotExecutable = "dot";
const string dotFile = "logistics.dot";
const string bitmapFile = "logistics.png";

try
{
File.WriteAllText(dotFile, GenerateDotGraph());

System.Diagnostics.Process dotProcess = new();
dotProcess.StartInfo.RedirectStandardOutput = true;
dotProcess.StartInfo.UseShellExecute = false;
dotProcess.StartInfo.CreateNoWindow = true;
dotProcess.EnableRaisingEvents = true;
dotProcess.StartInfo.FileName = dotExecutable;
dotProcess.StartInfo.Arguments = string.Format(@"{0} -Kdot -Tpng -o {1}", dotFile, bitmapFile);
dotProcess.Exited += new EventHandler(OnDotProcessExited);
dotProcess.Start();
} catch (Exception ex)
{
GraphvizInstalled = false;
this.RaisePropertyChanged(nameof(GraphvizInstalled));
}
GetAllInputTickers();
}

private void OnDotProcessExited(object sender, EventArgs e)
{
const string bitmapFile = "logistics.png";
Bitmap result = new(bitmapFile);
//GraphvizImage = result.CreateScaledBitmap(new Avalonia.PixelSize(2000, 1200));
GraphvizImage = result;
GraphvizInstalled = true;
this.RaisePropertyChanged(nameof(GraphvizInstalled));
this.RaisePropertyChanged(nameof(GraphvizImage));
}

public void OnTickerStateChanged(string ticker)
{
Tickers[ticker] = !Tickers[ticker];
GenerateGraphBitmap();
}

public void OnChecked()
{

}
public void OnUnchecked()
{

}

public void Update()
{
var tickers = new Dictionary<string, bool>();
// refresh Tickers, keep previous values for existing keys
foreach (var ticker in GetAllInputTickers())
{
tickers[ticker] = Tickers.GetValueOrDefault(ticker, true);
}
Tickers = tickers;
GenerateGraphBitmap();
}

public void SetEmpire(Empire empire)
{
this.empire = empire;
}

private HashSet<string> GetAllInputTickers()
{
HashSet<string> result = new HashSet<string>();
foreach (var row in empire.PlanetaryBases)
{
var tickers = from input in row.ProductionTable.Inputs
select input.Material.Ticker;
result.UnionWith(tickers);
}
return result;
}

public void OpenGraphvizSite()
{
Utils.OpenWebsite("https://graphviz.org/download/");
}

private Empire empire;
}
}
14 changes: 14 additions & 0 deletions PRUNner/App/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class MainWindowViewModel : ViewModelBase
public readonly EmpireViewModel EmpireViewModel;
public readonly BasePlannerViewModel BasePlannerViewModel;
public readonly PlanetFinderViewModel PlanetFinderViewModel;
public readonly LogisticsViewModel LogisticsViewModel;

[Reactive] public string StatusBar { get; private set; }

Expand All @@ -43,6 +44,7 @@ public MainWindowViewModel()
EmpireViewModel = new EmpireViewModel(this);
BasePlannerViewModel = new ();
PlanetFinderViewModel = new ();
LogisticsViewModel = new(EmpireViewModel.Empire);

ActiveView = PlanetFinderViewModel;

Expand All @@ -56,6 +58,12 @@ public void ViewPlanetFinder()
ActiveView = PlanetFinderViewModel;
}

public void ViewLogistics()
{
LogisticsViewModel.Update();
ActiveView = LogisticsViewModel;
}

private void PlanetFinderSelectPlanetEvent(object? sender, PlanetData planet)
{
var planetaryBase = EmpireViewModel.StartNewBase(planet);
Expand Down Expand Up @@ -94,6 +102,11 @@ public void OpenApexHandbookCommunityResourceSite()
Utils.OpenWebsite("https://handbook.apex.prosperousuniverse.com/wiki/community-resources/");
}

public void OpenGraphvizSite()
{
Utils.OpenWebsite("https://graphviz.org/download/");
}

public void ResetFioCache()
{
if (Directory.Exists(FioImporter.CacheFolder))
Expand All @@ -120,6 +133,7 @@ public void LoadFromDisk(bool initialize = false)
}

EmpireViewModel.SetEmpire(empire);
LogisticsViewModel.SetEmpire(empire);
if (EmpireViewModel.Empire.PlanetaryBases.Count <= 0)
{
return;
Expand Down
29 changes: 29 additions & 0 deletions PRUNner/App/Views/LogisticsView.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:viewModels="clr-namespace:PRUNner.App.ViewModels"
x:Class="PRUNner.App.Views.LogisticsView">
<Design.DataContext>
<viewModels:LogisticsViewModel />
</Design.DataContext>
<DockPanel>
<StackPanel Orientation="Vertical" DockPanel.Dock="Top" IsVisible="{Binding !GraphvizInstalled}>">
<TextBlock DockPanel.Dock="Top" IsVisible="{Binding !GraphvizInstalled}">Install Graphviz to use the logistics tab!</TextBlock>
<Button Command="{Binding OpenGraphvizSite}">Download Graphviz</Button>
</StackPanel>
<ItemsRepeater DockPanel.Dock="Left" Items="{Binding Tickers}" IsVisible="{Binding GraphvizInstalled}">
<ItemsRepeater.Layout>
<StackLayout Spacing="5"
Orientation="Vertical" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Key}" IsChecked="{Binding Value}" Command="{Binding Path=$parent.DataContext.OnTickerStateChanged}" CommandParameter="{Binding Key}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<Image Source="{Binding GraphvizImage}"/>
</DockPanel>
</UserControl>
18 changes: 18 additions & 0 deletions PRUNner/App/Views/LogisticsView.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace PRUNner.App.Views
{
public class LogisticsView : UserControl
{
public LogisticsView()
{
InitializeComponent();
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
4 changes: 4 additions & 0 deletions PRUNner/App/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<MenuItem Header="_Empire Overview" Command="{Binding ViewEmpire}" />
<MenuItem Header="_Base Planner" Command="{Binding ViewBasePlanner}" />
<MenuItem Header="Planet _Finder" Command="{Binding ViewPlanetFinder}" />
<MenuItem Header="_Logistics" Command="{Binding ViewLogistics}" />
<Separator MinWidth="100"/>
<MenuItem Header="_Update Price Data" Command="{Binding UpdatePriceData}" />
<Separator MinWidth="100"/>
Expand All @@ -40,6 +41,9 @@
<ToolTip.Tip>PRUNner has a dedicated channel for questions and discussions here!</ToolTip.Tip>
</MenuItem>
<MenuItem Header="Other Community Resources" Command="{Binding OpenApexHandbookCommunityResourceSite}"/>
<MenuItem Header="Graphviz" Command="{Binding OpenGraphvizSite}">
<ToolTip.Tip>Needed to use the logistics tab</ToolTip.Tip>
</MenuItem>
</MenuItem>
<Separator MinWidth="50" />
<MenuItem Header="🌙 Theme" Command="{Binding ToggleTheme}" />
Expand Down
3 changes: 3 additions & 0 deletions PRUNner/PRUNner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<DependentUpon>PriceDataPreferenceControl.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="App\Views\LogisticsView.axaml.cs">
<DependentUpon>LogisticsView.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" />
Expand Down