diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2019-04-02 03:21:23 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2019-04-02 03:21:23 +0900 |
commit | 9ebf7381bbb0e08e7d09e81e8cc56e1e8df70fcb (patch) | |
tree | ce59a30bf399507a867d296d0f4407433b67bf5c | |
parent | 037eb26e47e5fb3633e3901e778ea098b676ee1b (diff) | |
download | wf-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.cs | 72 | ||||
-rw-r--r-- | WarframeClock/BindableBase.cs | 2 | ||||
-rw-r--r-- | WarframeClock/Clock.cs | 7 | ||||
-rw-r--r-- | WarframeClock/EeLogParser.cs | 112 | ||||
-rw-r--r-- | WarframeClock/OverlayWindow.xaml.cs | 2 | ||||
-rw-r--r-- | WarframeClock/OverlayWindowViewModel.cs | 52 | ||||
-rw-r--r-- | WarframeClock/WarframeClock.csproj | 2 |
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> |