aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2019-04-02 03:21:23 +0900
committerKazuki Yamaguchi <k@rhe.jp>2019-04-02 03:21:23 +0900
commit9ebf7381bbb0e08e7d09e81e8cc56e1e8df70fcb (patch)
treece59a30bf399507a867d296d0f4407433b67bf5c
parent037eb26e47e5fb3633e3901e778ea098b676ee1b (diff)
downloadwf-clock-9ebf7381bbb0e08e7d09e81e8cc56e1e8df70fcb.tar.gz
use bounties expiry data in EE.log2019-04-01
To get more accurate time of day in the Plains of Eidolon. For a host it should now show the exact time. For a non-host it is still inaccurate. I'm not sure if this can even be fixed ever.
-rw-r--r--WarframeClock/App.xaml.cs72
-rw-r--r--WarframeClock/BindableBase.cs2
-rw-r--r--WarframeClock/Clock.cs7
-rw-r--r--WarframeClock/EeLogParser.cs112
-rw-r--r--WarframeClock/OverlayWindow.xaml.cs2
-rw-r--r--WarframeClock/OverlayWindowViewModel.cs52
-rw-r--r--WarframeClock/WarframeClock.csproj2
7 files changed, 213 insertions, 36 deletions
diff --git a/WarframeClock/App.xaml.cs b/WarframeClock/App.xaml.cs
index b3393d3..9de5aae 100644
--- a/WarframeClock/App.xaml.cs
+++ b/WarframeClock/App.xaml.cs
@@ -1,5 +1,7 @@
using System;
+using System.Diagnostics;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
@@ -10,22 +12,21 @@ namespace WarframeClock
/// </summary>
public partial class App : Application
{
- public static readonly string VersionString = "Warframe Clock Overlay 2019-03-12";
+ public static readonly string VersionString = "Warframe Clock Overlay 2019-04-01";
public static readonly string WebsiteUriString = "https://poepoe.org/warframe/clock/";
public static readonly Uri WebsiteUri = new Uri(WebsiteUriString);
- public static readonly bool UseWorldStatePhp = true;
- private Timer _worldStateFetchTimer;
+ public static readonly OverlayWindowViewModel OverlayWindowViewModel = new OverlayWindowViewModel();
+ private Timer _worldStateFetchTimer, _eeLogFetchTimer;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
-
- if (UseWorldStatePhp)
- StartWorldStateFetch();
+ StartWorldStateFetchTimer();
+ StartEeLogFetchTimer();
}
- private void StartWorldStateFetch()
+ private void StartWorldStateFetchTimer()
{
_worldStateFetchTimer = new Timer(state =>
{
@@ -36,25 +37,72 @@ namespace WarframeClock
worldState.SyndicateMissions.FirstOrDefault(mi => mi.Tag == "CetusSyndicate");
if (cetusSyndicate == null)
{
- Console.WriteLine("WorldState: CetusSyndicate missions not found");
+ Trace.WriteLine("WorldState: CetusSyndicate missions not found");
return;
}
var cetusExpiry = cetusSyndicate.Expiry.Date.NumberLong;
- Console.WriteLine($"WorldState: CetusSyndicate missions expire at {cetusExpiry}");
- Clock.CetusExpiry = cetusExpiry;
+ Trace.WriteLine($"WorldState: CetusSyndicate missions expire at {cetusExpiry}");
+ OverlayWindowViewModel.CetusExpiry = new DateTime(1970, 1, 1).AddMilliseconds(cetusExpiry);
}
catch (Exception ex)
{
- Console.WriteLine($"WorldState: Failed to fetch or parse worldState.php: {ex}");
- Console.WriteLine(ex.StackTrace);
+ Trace.WriteLine($"WorldState: Failed to fetch or parse worldState.php: {ex}");
+ Trace.WriteLine(ex.StackTrace);
}
}, null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
}
+ private void StartEeLogFetchTimer()
+ {
+ var parser = new EeLogParser();
+
+ var regexSyncWorldState = new Regex(@"^syncing world state jobs in ([0-9.]+)$");
+ var regexCetusJoin = new Regex(@"^IRC out: JOIN #H_CETUSHUB4_(.+)$");
+ parser.OnLogEntry += (sender, args) =>
+ {
+ Match match;
+
+ if ((match = regexSyncWorldState.Match(args.Content)).Success)
+ {
+ var reset = args.DateTime.AddSeconds(double.Parse(match.Groups[1].Value));
+ if (reset < DateTime.UtcNow)
+ return;
+ OverlayWindowViewModel.NextBountiesReset = reset;
+ Console.WriteLine($"EE.log: Next bounties reset: {reset}");
+ return;
+ }
+
+ if ((match = regexCetusJoin.Match(args.Content)).Success)
+ {
+ Console.WriteLine($"EE.log: Loaded Cetus: {match.Groups[1].Value}");
+ return;
+ }
+ };
+
+ parser.OnLogFileReset += (sender, args) =>
+ {
+ OverlayWindowViewModel.NextBountiesReset = null;
+ };
+
+ _eeLogFetchTimer = new Timer(state =>
+ {
+ try
+ {
+ parser.Update();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"EE.log: Failed to parse EE.log: {ex}");
+ Console.WriteLine(ex.StackTrace);
+ }
+ }, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
+ }
+
internal void Terminate()
{
_worldStateFetchTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ _eeLogFetchTimer.Change(Timeout.Infinite, Timeout.Infinite);
MainWindow?.Close();
}
}
diff --git a/WarframeClock/BindableBase.cs b/WarframeClock/BindableBase.cs
index 3f81f24..d3e6d25 100644
--- a/WarframeClock/BindableBase.cs
+++ b/WarframeClock/BindableBase.cs
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace WarframeClock
{
- internal abstract class BindableBase : INotifyPropertyChanged
+ public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
diff --git a/WarframeClock/Clock.cs b/WarframeClock/Clock.cs
deleted file mode 100644
index 6f86ea6..0000000
--- a/WarframeClock/Clock.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace WarframeClock
-{
- public static class Clock
- {
- public static long? CetusExpiry { get; set; }
- }
-}
diff --git a/WarframeClock/EeLogParser.cs b/WarframeClock/EeLogParser.cs
new file mode 100644
index 0000000..2a05810
--- /dev/null
+++ b/WarframeClock/EeLogParser.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace WarframeClock
+{
+ internal class EeLogParser
+ {
+ private static readonly string EeLogFullPath =
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Warframe\EE.log";
+ private readonly Regex _logLineDelimiter = new Regex(@"\r\n\d+.\d\d\d \w+ \[\w+\]: ", RegexOptions.Compiled);
+ private int _previousSize;
+ private DateTime? _timeOffset;
+ private bool _isUpdating;
+
+ public event EventHandler OnLogFileReset;
+ public event EventHandler<OnEntryEventArgs> OnLogEntry;
+
+ public class OnEntryEventArgs : EventArgs
+ {
+ public DateTime DateTime { get; set; }
+ public string Content { get; set; }
+ }
+
+ public void Update()
+ {
+ if (_isUpdating)
+ return;
+ _isUpdating = true;
+ try
+ {
+ DoUpdate();
+ }
+ finally
+ {
+ _isUpdating = false;
+ }
+ }
+
+ private void DoUpdate()
+ {
+ var info = new FileInfo(EeLogFullPath);
+ var newSize = (int)info.Length; // EE.log won't be bigger than 2GiB, no?
+ var startReading = _previousSize;
+ if (startReading > newSize)
+ {
+ // Assuming the game is restarted
+ Trace.WriteLine("EE.log: Reset");
+ _timeOffset = default;
+ startReading = 0;
+ OnLogFileReset?.Invoke(this, new EventArgs());
+ }
+
+ var buf = new byte[newSize - startReading];
+ using (var fs = new FileStream(EeLogFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ {
+ fs.Seek(startReading, SeekOrigin.Begin);
+ fs.Read(buf, 0, buf.Length);
+ }
+
+ var rawString = Encoding.ASCII.GetString(buf);
+ var pos = 0;
+ var lines = 0;
+ while (true)
+ {
+ var dMatch = _logLineDelimiter.Match(rawString, pos);
+ if (!dMatch.Success)
+ {
+ _previousSize = startReading + pos;
+ break;
+ }
+
+ var newLinePos = dMatch.Index;
+ var line = rawString.Substring(pos, newLinePos - pos);
+ pos = newLinePos + "\r\n".Length;
+
+ // #.### XX [YY]: ZZ
+ var entryOffset = double.Parse(line.Substring(0, line.IndexOf(' ')));
+ var entryContent = line.Substring(line.IndexOf(':') + 2);
+
+ if (_timeOffset == null && entryContent.StartsWith("Current time: "))
+ {
+ var match = new Regex(@"^Current time: .+ \[UTC: (.+)\]$", RegexOptions.Compiled)
+ .Match(entryContent);
+ if (match.Success)
+ {
+ var formats = new[] { "ddd MMM dd HH:mm:ss yyyy", "ddd MMM d HH:mm:ss yyyy" };
+ var utc = DateTime.ParseExact(match.Groups[1].Value, formats,
+ CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite);
+ _timeOffset = utc.AddSeconds(-entryOffset);
+ }
+ }
+
+ if (_timeOffset != null)
+ {
+ OnLogEntry?.Invoke(this, new OnEntryEventArgs
+ {
+ DateTime = _timeOffset.Value.AddSeconds(entryOffset),
+ Content = entryContent
+ });
+ }
+
+ lines++;
+ }
+
+ Trace.WriteLine($"EE.log: Read {lines} lines");
+ }
+ }
+}
diff --git a/WarframeClock/OverlayWindow.xaml.cs b/WarframeClock/OverlayWindow.xaml.cs
index cd0f46b..98c7750 100644
--- a/WarframeClock/OverlayWindow.xaml.cs
+++ b/WarframeClock/OverlayWindow.xaml.cs
@@ -19,7 +19,7 @@ namespace WarframeClock
public OverlayWindow()
{
InitializeComponent();
- DataContext = _vm = new OverlayWindowViewModel();
+ DataContext = _vm = App.OverlayWindowViewModel;
var iconMenu = new System.Windows.Forms.ContextMenu();
iconMenu.MenuItems.Add(new System.Windows.Forms.MenuItem(App.VersionString) { Enabled = false });
diff --git a/WarframeClock/OverlayWindowViewModel.cs b/WarframeClock/OverlayWindowViewModel.cs
index b467fc5..aacab45 100644
--- a/WarframeClock/OverlayWindowViewModel.cs
+++ b/WarframeClock/OverlayWindowViewModel.cs
@@ -4,8 +4,11 @@ using System.Windows.Media;
namespace WarframeClock
{
- internal class OverlayWindowViewModel : BindableBase
+ public class OverlayWindowViewModel : BindableBase
{
+ public DateTime? CetusExpiry { get; set; }
+ public DateTime? NextBountiesReset { get; set; }
+
private string _overlayText = "!!!PLACEHOLDER!!!";
public string OverlayText
{
@@ -19,25 +22,46 @@ namespace WarframeClock
public void RefreshOverlayText()
{
- var currentTime = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds / 1000;
- const double cetusLen = 8998.8748, cetusDayLen = cetusLen * 2 / 3;
- var cetusBegin = (Clock.CetusExpiry ?? 1548159123053) / 1000.0 - cetusLen;
- cetusBegin += Math.Floor((currentTime - cetusBegin) / cetusLen) * cetusLen;
-
- string plainsEmoji;
- double plainsRemaining;
- if (currentTime < cetusBegin + cetusDayLen)
+ var currentTime = DateTime.UtcNow;
+ const double cetusLen = 8998.8748, cetusNightLen = cetusLen * 1 / 3;
+
+ string FormatPlainsText(DateTime cetusEnd)
+ {
+ var cetusDayEnd = cetusEnd.AddSeconds(-cetusNightLen);
+ string sign;
+ TimeSpan time;
+ if (currentTime < cetusDayEnd)
+ {
+ sign = "☀";
+ time = cetusDayEnd.Subtract(currentTime);
+ }
+ else
+ {
+ sign = "🌙";
+ time = cetusEnd.Subtract(currentTime);
+ }
+ return $"{sign} {Math.Floor(time.TotalMinutes)}m{Math.Floor(time.TotalSeconds % 60)}s";
+ }
+
+ var cetusEndByWorldState = CetusExpiry ?? new DateTime(1970, 1, 1).AddMilliseconds(1548159123053);
+ cetusEndByWorldState = cetusEndByWorldState.AddSeconds(
+ (Math.Floor(currentTime.Subtract(cetusEndByWorldState).TotalSeconds / cetusLen) + 1) * cetusLen);
+
+ string ret;
+ if (NextBountiesReset > currentTime)
{
- plainsEmoji = "☀";
- plainsRemaining = cetusBegin + cetusDayLen - currentTime;
+ var cetusEndByEeLog = NextBountiesReset.Value;
+ ret = FormatPlainsText(cetusEndByEeLog);
+ var diff = (cetusEndByEeLog.Subtract(cetusEndByWorldState).TotalSeconds + cetusLen) % cetusLen;
+ if (Math.Abs(diff) >= 1.0)
+ ret += $" [{diff:0.0}s {(diff > 0 ? "late" : "early")}]";
}
else
{
- plainsEmoji = "🌙";
- plainsRemaining = cetusBegin + cetusLen - currentTime;
+ ret = $"({FormatPlainsText(cetusEndByWorldState)})";
}
- OverlayText = $"{plainsEmoji} {Math.Floor(plainsRemaining / 60)}m{Math.Floor(plainsRemaining % 60)}s";
+ OverlayText = ret;
}
private bool _settingsMode;
diff --git a/WarframeClock/WarframeClock.csproj b/WarframeClock/WarframeClock.csproj
index 0a22939..81117d3 100644
--- a/WarframeClock/WarframeClock.csproj
+++ b/WarframeClock/WarframeClock.csproj
@@ -79,6 +79,7 @@
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="BindableBase.cs" />
+ <Compile Include="EeLogParser.cs" />
<Compile Include="OverlayWindowBase.cs" />
<Compile Include="OverlayWindowViewModel.cs" />
<Compile Include="Settings.cs" />
@@ -91,7 +92,6 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
- <Compile Include="Clock.cs" />
<Compile Include="Native.cs" />
<Compile Include="OverlayWindow.xaml.cs">
<DependentUpon>OverlayWindow.xaml</DependentUpon>