Skip to content

Commit

Permalink
Merge pull request #409 from Vivelin/linux-auto-tracking
Browse files Browse the repository at this point in the history
Implemented linux auto tracking
  • Loading branch information
MattEqualsCoder authored Oct 13, 2023
2 parents dc11b28 + b8152e0 commit a14a21b
Show file tree
Hide file tree
Showing 39 changed files with 1,427 additions and 782 deletions.
22 changes: 10 additions & 12 deletions src/Randomizer.App/Windows/OptionsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
xmlns:vm="clr-namespace:Randomizer.App.ViewModels"
xmlns:options="clr-namespace:Randomizer.Data.Options;assembly=Randomizer.Data"
xmlns:app="clr-namespace:Randomizer.App"
xmlns:enums="clr-namespace:Randomizer.Shared.Enums;assembly=Randomizer.Shared"
x:Name="Self"
ResizeMode="NoResize"
mc:Ignorable="d"
Expand Down Expand Up @@ -177,10 +178,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<ComboBox ItemsSource="{Binding QuickLaunchOptions}"
SelectedIndex="{Binding LaunchButton}"
Height="23"
VerticalAlignment="Top">
<ComboBox SelectedItem="{Binding LaunchButtonOption}"
ItemsSource="{Binding Source={app:EnumBindingSource {x:Type options:LaunchButtonOptions}}}"
MinWidth="75">
</ComboBox>
</Grid>
</controls:LabeledControl>
Expand All @@ -192,10 +192,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<ComboBox ItemsSource="{Binding TrackerVoiceFrequencyOptions}"
SelectedIndex="{Binding VoiceFrequency}"
Height="23"
VerticalAlignment="Top">
<ComboBox SelectedItem="{Binding TrackerVoiceFrequency}"
ItemsSource="{Binding Source={app:EnumBindingSource {x:Type enums:TrackerVoiceFrequency}}}"
MinWidth="75">
</ComboBox>
</Grid>
</controls:LabeledControl>
Expand All @@ -218,10 +217,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<ComboBox ItemsSource="{Binding AutoTrackerConnectorOptions}"
SelectedIndex="{Binding AutoTrackerDefaultConnector}"
Height="23"
VerticalAlignment="Top">
<ComboBox SelectedItem="{Binding AutoTrackerDefaultConnectionType}"
ItemsSource="{Binding Source={app:EnumBindingSource {x:Type options:EmulatorConnectorType}}}"
MinWidth="75">
</ComboBox>
</Grid>
</controls:LabeledControl>
Expand Down
4 changes: 2 additions & 2 deletions src/Randomizer.App/Windows/TrackerWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,9 +1048,9 @@ private async void LoadSavedStateMenuItem_Click(object sender, RoutedEventArgs e
private async Task SaveStateAsync()
{
// If there is a rom, save it to the database
if (GeneratedRom.IsValid(Rom))
if (GeneratedRom.IsValid(Tracker.Rom))
{
await Tracker.SaveAsync(Rom);
await Tracker.SaveAsync();
}

SavedState?.Invoke(this, EventArgs.Empty);
Expand Down
284 changes: 284 additions & 0 deletions src/Randomizer.CrossPlatform/ConsoleTrackerDisplayService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Randomizer.Data.Options;
using Randomizer.Data.WorldData;
using Randomizer.Data.WorldData.Regions;
using Randomizer.Shared;
using Randomizer.Shared.Enums;
using Randomizer.Shared.Models;
using Randomizer.SMZ3.Generation;
using Randomizer.SMZ3.Tracking;
using Randomizer.SMZ3.Tracking.AutoTracking;
using Randomizer.SMZ3.Tracking.Services;

namespace Randomizer.CrossPlatform;

public class ConsoleTrackerDisplayService
{
private readonly System.Timers.Timer _timer;
private readonly Smz3GeneratedRomLoader _romLoaderService;
private readonly TrackerOptionsAccessor _trackerOptionsAccessor;
private readonly RandomizerOptions _options;
private readonly IServiceProvider _serviceProvider;
private Tracker _tracker = null!;
private World _world = null!;
private IWorldService _worldService = null!;
private Region _lastRegion = null!;

public ConsoleTrackerDisplayService(IServiceProvider serviceProvider, Smz3GeneratedRomLoader romLoaderService, TrackerOptionsAccessor trackerOptionsAccessor, OptionsFactory optionsFactory)
{
_romLoaderService = romLoaderService;
_trackerOptionsAccessor = trackerOptionsAccessor;
_options = optionsFactory.Create();
_serviceProvider = serviceProvider;
_timer = new System.Timers.Timer(TimeSpan.FromMilliseconds(250));
_timer.Elapsed += delegate { UpdateScreen(); };
}

public async Task StartTracking(GeneratedRom rom, string romPath)
{
_trackerOptionsAccessor.Options = _options.GeneralOptions.GetTrackerOptions();
_world = _romLoaderService.LoadGeneratedRom(rom).First(x => x.IsLocalWorld);
_worldService = _serviceProvider.GetRequiredService<IWorldService>();
_tracker = _serviceProvider.GetRequiredService<Tracker>();
_tracker.Load(rom, romPath);
_tracker.TryStartTracking();
_tracker.AutoTracker?.SetConnector(_options.AutoTrackerDefaultConnector, _options.AutoTrackerQUsb2SnesIp);

if (_tracker.AutoTracker != null)
{
_tracker.AutoTracker.AutoTrackerConnected += delegate
{
UpdateScreen();
if (!_timer.Enabled)
{
_timer.Start();
}
};

_tracker.LocationCleared += delegate(object? _, LocationClearedEventArgs args)
{
_lastRegion = args.Location.Region;
};
}

while (true)
{
Console.ReadKey();
_timer.Stop();
Console.Clear();
Console.Write("Do you want to quit? (y/n) ");
var response = Console.ReadLine();

if ("y".Equals(response, StringComparison.OrdinalIgnoreCase))
{
await _tracker.SaveAsync();
break;
}

_timer.Start();
UpdateScreen();
}
}

private void UpdateScreen()
{
var columnWidth = (Console.WindowWidth-6)/2;

var topLines = GetHeaderLines(columnWidth);

var leftColumnLines = new List<string>();
leftColumnLines.AddRange(GetInventoryLines(columnWidth));
leftColumnLines.Add("");
leftColumnLines.AddRange(GetDungeonLines(columnWidth));

var rightColumnLines = new List<string>();
rightColumnLines.AddRange(GetLocationLines(columnWidth));

while (leftColumnLines.Count < Console.WindowHeight - 1)
{
leftColumnLines.Add("");
}

while (rightColumnLines.Count < Console.WindowHeight - 1)
{
rightColumnLines.Add("");
}

var sb = new StringBuilder();

foreach (var line in topLines)
{
sb.AppendLine(line);
}

for (var i = 0; i < leftColumnLines.Count && i < Console.WindowHeight - topLines.Count - 1; i++)
{
// Retrieve and pad left column, trimming if needed
var leftColumn = leftColumnLines[i];
if (leftColumn.Length > columnWidth)
{
leftColumn = leftColumn[..(columnWidth - 3)] + "... ";
}
else
{
leftColumn = leftColumn.PadRight(columnWidth);
}

// Retrieve right column, trimming if needed
var rightColumn = rightColumnLines[i];
if (rightColumn.Length > columnWidth)
{
rightColumn = rightColumn[..(columnWidth - 3)] + "...";
}

sb.AppendLine($"{leftColumn} {rightColumn}");
}

Console.Clear();
Console.Write(sb.ToString());
}

private List<string> GetHeaderLines(int columnWidth)
{
var lines = new List<string>();

var connected = $"Connected: {_tracker.AutoTracker?.IsConnected == true}";

switch (_tracker.AutoTracker?.CurrentGame)
{
case Game.Zelda:
lines.Add($"{connected} | {_tracker.AutoTracker.ZeldaState}");
break;
case Game.SM:
lines.Add($"{connected} | {_tracker.AutoTracker.MetroidState}");
break;
default:
lines.Add(connected);
break;
}

lines.Add(new string('-', columnWidth * 2 + 5));

return lines;
}

private IEnumerable<string> GetDungeonLines(int columnWidth)
{
var lines = new List<string> { "Dungeons", new('-', columnWidth) };

var dungeons = _world.Dungeons
.Select(x => GetDungeonDetails(x).PadRight(18))
.ToList();

var dungeonLine = "";
foreach (var dungeon in dungeons)
{
if (dungeonLine == "")
{
dungeonLine = dungeon;
}
else if ($"{dungeonLine}{dungeon}".Length > columnWidth)
{
lines.Add(dungeonLine);
dungeonLine = dungeon;
}
else
{
dungeonLine += dungeon;
}
}
lines.Add(dungeonLine);

return lines;
}

private IEnumerable<string> GetLocationLines(int columnWidth)
{
var lines = new List<string> { "Locations", new('-', columnWidth) };

var locations = _worldService.Locations(unclearedOnly: true, outOfLogic: false, assumeKeys: true,
sortByTopRegion: true, regionFilter: RegionFilter.None).ToList();

var regionCounts = locations
.GroupBy(x => x.Region)
.OrderByDescending(x => x.Count())
.ToDictionary(x => x.Key, x => x.Count());

var locationNames = locations
.OrderByDescending(x => x.Region == _lastRegion)
.ThenByDescending(x => regionCounts[x.Region])
.ThenBy(x => x.ToString())
.Select(x => x.ToString());
lines.AddRange(locationNames);

return lines;
}

private IEnumerable<string> GetInventoryLines(int columnWidth)
{
var lines = new List<string> { "Inventory", new('-', columnWidth) };

var itemNames = _world.AllItems.Where(x => x.State.TrackingState > 0)
.DistinctBy(x => x.Type)
.Where(x => !x.Type.IsInAnyCategory(ItemCategory.Junk, ItemCategory.Map, ItemCategory.Compass, ItemCategory.Keycard, ItemCategory.BigKey, ItemCategory.SmallKey) || x.Type is ItemType.Missile or ItemType.Super or ItemType.PowerBomb or ItemType.ETank)
.OrderBy(x => x.Type.IsInCategory(ItemCategory.Metroid))
.ThenBy(x => x.Name)
.Select(x => x.Metadata.HasStages || x.Metadata.Multiple
? $"{x.Name} ({x.State.TrackingState})"
: x.Name)
.ToList();

for (var i = 0; i < itemNames.Count; i += 2)
{
if (i < itemNames.Count - 1)
{
lines.Add(itemNames[i].PadRight(columnWidth/2) + itemNames[i+1]);
}
else
{
lines.Add(itemNames[i]);
}
}

return lines;
}

private string GetDungeonDetails(IDungeon dungeon)
{
var state = dungeon.DungeonState.Cleared ? "\u2713" : "\u274c";
var abbreviation = dungeon.DungeonMetadata.Abbreviation;

var reward = dungeon is IHasReward
? dungeon.MarkedReward switch
{
RewardType.None => "??",
RewardType.CrystalBlue => "BC",
RewardType.CrystalRed => "RC",
RewardType.PendantBlue => "BP",
RewardType.PendantGreen => "GP",
RewardType.PendantRed => "RP",
_ => null
}
: null;

var requirement = dungeon is INeedsMedallion
? dungeon.MarkedMedallion switch
{
ItemType.Quake => "Q",
ItemType.Bombos => "B",
ItemType.Ether => "E",
_ => "?"
}
: null;

var rewardRequirement = "";
if (reward != null)
{
rewardRequirement = requirement == null ? $" ({reward})" : $" ({reward}/{requirement})";
}

return $"{state} {abbreviation}{rewardRequirement}: {dungeon.DungeonState.RemainingTreasure}";
}

}
11 changes: 5 additions & 6 deletions src/Randomizer.CrossPlatform/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Randomizer.Data.Services;
using Randomizer.SMZ3.Generation;
using Serilog;
using Serilog.Events;

namespace Randomizer.CrossPlatform;

Expand All @@ -17,13 +16,8 @@ public static class Program

public static void Main(string[] args)
{
var logLevel = LogEventLevel.Warning;
if (args.Any(x => x == "-v"))
logLevel = LogEventLevel.Information;

Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(logLevel)
.WriteTo.File(LogPath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30)
.CreateLogger();

Expand All @@ -46,6 +40,8 @@ public static void Main(string[] args)
return;
}

Console.WriteLine($"Using Randomizer Options file at {optionsFile.FullName}");

var result = DisplayMenu("What do you want to do?", new List<string>()
{
"Generate & Play a Rom",
Expand Down Expand Up @@ -82,6 +78,7 @@ public static void Main(string[] args)
var romPath = Path.Combine(randomizerOptions.RomOutputPath, results.Rom!.RomPath);
Console.WriteLine($"Rom generated successfully: {romPath}");
Launch(romPath, randomizerOptions);
_ = s_services.GetRequiredService<ConsoleTrackerDisplayService>().StartTracking(results.Rom, romPath);
}
else
{
Expand All @@ -103,7 +100,9 @@ public static void Main(string[] args)
var selectedRom = roms[result.Value.Item1];
var romPath = Path.Combine(randomizerOptions.RomOutputPath, selectedRom.RomPath);
Launch(romPath, randomizerOptions);
_ = s_services.GetRequiredService<ConsoleTrackerDisplayService>().StartTracking(selectedRom, romPath);
}

}
// Deletes rom(s)
else if (result.Value.Item1 == 2)
Expand Down
Loading

0 comments on commit a14a21b

Please sign in to comment.