OSDN Git Service

Pushoverによるプッシュ通知に対応する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Diagnostics;\r
18 using System.Drawing;\r
19 using System.Globalization;\r
20 using System.IO;\r
21 using System.Linq;\r
22 using System.Net;\r
23 using System.Runtime.InteropServices;\r
24 using System.Text;\r
25 using System.Text.RegularExpressions;\r
26 using System.Threading.Tasks;\r
27 using System.Windows.Forms;\r
28 using Microsoft.CSharp.RuntimeBinder;\r
29 using static System.Math;\r
30 \r
31 namespace KancolleSniffer\r
32 {\r
33     public partial class MainForm : Form\r
34     {\r
35         private readonly Sniffer _sniffer = new Sniffer();\r
36         private readonly Config _config = new Config();\r
37         private readonly ConfigDialog _configDialog;\r
38         private readonly ProxyManager _proxyManager;\r
39         private int _currentFleet;\r
40         private bool _combinedFleet;\r
41         private readonly Label[] _labelCheckFleets;\r
42         private readonly ShipLabels _shipLabels;\r
43         private readonly ListForm _listForm;\r
44         private readonly NotificationManager _notificationManager;\r
45         private bool _started;\r
46         private string _debugLogFile;\r
47         private IEnumerator<string> _playLog;\r
48 \r
49         private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
50         private bool _missionFinishTimeMode;\r
51         private bool _ndockFinishTimeMode;\r
52         private readonly KancolleDb _kancolleDb = new KancolleDb();\r
53         private readonly ErrorLog _errorLog;\r
54 \r
55         public MainForm()\r
56         {\r
57             InitializeComponent();\r
58             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
59             _configDialog = new ConfigDialog(_config, this);\r
60             _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
61 \r
62             // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
63             // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
64             ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
65                 CurrentAutoScaleDimensions.Height / 12f);\r
66 \r
67             SetupFleetClick();\r
68             _shipLabels = new ShipLabels();\r
69             _shipLabels.CreateAkashiTimers(panelShipInfo);\r
70             _shipLabels.CreateShipLabels(panelShipInfo, ShowShipOnShipList);\r
71             _shipLabels.CreateAkashiTimers7(panel7Ships);\r
72             _shipLabels.CreateShipLabels7(panel7Ships, ShowShipOnShipList);\r
73             _shipLabels.CreateCombinedShipLabels(panelCombinedFleet, ShowShipOnShipList);\r
74             _shipLabels.CreateNDockLabels(panelDock, labelNDock_Click);\r
75             panelRepairList.CreateLabels(panelRepairList_Click);\r
76             labelPresetAkashiTimer.BackColor = ShipLabels.ColumnColors[1];\r
77             _listForm = new ListForm(_sniffer, _config) {Owner = this};\r
78             _notificationManager = new NotificationManager(Ring);\r
79             try\r
80             {\r
81                 _config.Load();\r
82             }\r
83             catch (Exception ex)\r
84             {\r
85                 throw new ConfigFileException("設定ファイルが壊れています。", ex);\r
86             }\r
87             _proxyManager = new ProxyManager(_config, this);\r
88             _errorLog = new ErrorLog(_sniffer);\r
89             PerformZoom();\r
90             _shipLabels.AdjustAkashiTimers();\r
91             _sniffer.LoadState();\r
92             _sniffer.RepeatingTimerController = new RepeatingTimerController(_notificationManager);\r
93         }\r
94 \r
95         private class RepeatingTimerController : Sniffer.IRepeatingTimerController\r
96         {\r
97             private readonly NotificationManager _manager;\r
98 \r
99             public RepeatingTimerController(NotificationManager manager)\r
100             {\r
101                 _manager = manager;\r
102             }\r
103 \r
104             public void Stop(string key) => _manager.StopRepeat(key);\r
105 \r
106             public void Suspend() => _manager.SuspendRepeat();\r
107 \r
108             public void Resume() => _manager.ResumeRepeat();\r
109         }\r
110 \r
111         public class ConfigFileException : Exception\r
112         {\r
113             public ConfigFileException(string message, Exception innerException) : base(message, innerException)\r
114             {\r
115             }\r
116         }\r
117 \r
118         private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
119         {\r
120             Invoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
121         }\r
122 \r
123         private void ProcessRequest(HttpProxy.Session session)\r
124         {\r
125             var url = session.Request.PathAndQuery;\r
126             if (!url.Contains("kcsapi/"))\r
127                 return;\r
128             var request = session.Request.BodyAsString;\r
129             var response = session.Response.BodyAsString;\r
130             if (response == null || !response.StartsWith("svdata="))\r
131             {\r
132                 WriteDebugLog(url, request, response);\r
133                 return;\r
134             }\r
135             if (_config.KancolleDb.On)\r
136                 _kancolleDb.Send(url, request, response);\r
137             response = UnescapeString(response.Remove(0, "svdata=".Length));\r
138             WriteDebugLog(url, request, response);\r
139             ProcessRequestMain(url, request, response);\r
140         }\r
141 \r
142         private void ProcessRequestMain(string url, string request, string response)\r
143         {\r
144             try\r
145             {\r
146                 UpdateInfo(_sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
147                 _errorLog.CheckBattleApi(url, request, response);\r
148             }\r
149 \r
150             catch (RuntimeBinderException e)\r
151             {\r
152                 if (_errorDialog.ShowDialog(this,\r
153                         "艦これに仕様変更があったか、受信内容が壊れています。",\r
154                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
155                     Application.Exit();\r
156             }\r
157             catch (LogIOException e)\r
158             {\r
159                 // ReSharper disable once PossibleNullReferenceException\r
160                 if (_errorDialog.ShowDialog(this, e.Message, e.InnerException.ToString()) == DialogResult.Abort)\r
161                     Application.Exit();\r
162             }\r
163             catch (BattleResultError)\r
164             {\r
165                 if (_errorDialog.ShowDialog(this, "戦闘結果の計算に誤りがあります。",\r
166                         _errorLog.GenerateBattleErrorLog()) == DialogResult.Abort)\r
167                     Application.Exit();\r
168             }\r
169             catch (Exception e)\r
170             {\r
171                 if (_errorDialog.ShowDialog(this, "エラーが発生しました。",\r
172                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
173                     Application.Exit();\r
174             }\r
175         }\r
176 \r
177         private void WriteDebugLog(string url, string request, string response)\r
178         {\r
179             if (_debugLogFile != null)\r
180             {\r
181                 File.AppendAllText(_debugLogFile,\r
182                     $"date: {DateTime.Now:g}\nurl: {url}\nrequest: {request}\nresponse: {response ?? "(null)"}\n");\r
183             }\r
184         }\r
185 \r
186         private string UnescapeString(string s)\r
187         {\r
188             try\r
189             {\r
190                 var rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");\r
191                 return rx.Replace(s,\r
192                     match => ((char)int.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());\r
193             }\r
194             catch (ArgumentException)\r
195             {\r
196                 return s;\r
197             }\r
198         }\r
199 \r
200         private void UpdateInfo(Sniffer.Update update)\r
201         {\r
202             if (update == Sniffer.Update.Start)\r
203             {\r
204                 labelLogin.Visible = false;\r
205                 linkLabelGuide.Visible = false;\r
206                 _started = true;\r
207                 return;\r
208             }\r
209             if (!_started)\r
210                 return;\r
211             if ((update & Sniffer.Update.Item) != 0)\r
212                 UpdateItemInfo();\r
213             if ((update & Sniffer.Update.Timer) != 0)\r
214                 UpdateTimers();\r
215             if ((update & Sniffer.Update.NDock) != 0)\r
216                 UpdateNDocLabels();\r
217             if ((update & Sniffer.Update.Mission) != 0)\r
218                 UpdateMissionLabels();\r
219             if ((update & Sniffer.Update.QuestList) != 0)\r
220                 UpdateQuestList();\r
221             if ((update & Sniffer.Update.Ship) != 0)\r
222                 UpdateShipInfo();\r
223             if ((update & Sniffer.Update.Battle) != 0)\r
224                 UpdateBattleInfo();\r
225         }\r
226 \r
227         private void MainForm_Load(object sender, EventArgs e)\r
228         {\r
229             RestoreLocation();\r
230             if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
231                 ShowInTaskbar = false;\r
232             if (_config.ShowHpInPercent)\r
233                 _shipLabels.ToggleHpPercent();\r
234             if (_config.ShipList.Visible)\r
235                 _listForm.Show();\r
236             ApplyConfig();\r
237             ApplyDebugLogSetting();\r
238             ApplyLogSetting();\r
239             ApplyProxySetting();\r
240             if (_config.KancolleDb.On)\r
241                 _kancolleDb.Start(_config.KancolleDb.Token);\r
242             CheckVersionUp((current, latest) =>\r
243             {\r
244                 if (double.Parse(latest) <= double.Parse(current))\r
245                     return;\r
246                 linkLabelGuide.Text = $"バージョン{latest}があります。";\r
247                 linkLabelGuide.LinkArea = new LinkArea(0, linkLabelGuide.Text.Length);\r
248                 linkLabelGuide.Click += (obj, ev) =>\r
249                 {\r
250                     Process.Start("https://ja.osdn.net/rel/kancollesniffer/" + latest);\r
251                 };\r
252             });\r
253         }\r
254 \r
255         public async void CheckVersionUp(Action<string, string> action)\r
256         {\r
257             var current = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
258             try\r
259             {\r
260                 var latest = (await new WebClient().DownloadStringTaskAsync("http://kancollesniffer.osdn.jp/version"))\r
261                     .TrimEnd();\r
262                 try\r
263                 {\r
264                     action(current, latest);\r
265                 }\r
266                 catch (InvalidOperationException)\r
267                 {\r
268                 }\r
269             }\r
270             catch (WebException)\r
271             {\r
272             }\r
273         }\r
274 \r
275         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
276         {\r
277             if (!_config.ExitSilently)\r
278             {\r
279                 using (var dialog = new ConfirmDialog())\r
280                 {\r
281                     if (dialog.ShowDialog(this) != DialogResult.Yes)\r
282                     {\r
283                         e.Cancel = true;\r
284                         return;\r
285                     }\r
286                 }\r
287             }\r
288             e.Cancel = false;\r
289             _sniffer.FlashLog();\r
290             _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
291             _config.ShowHpInPercent = _shipLabels.ShowHpInPercent;\r
292             _config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
293             _config.Save();\r
294             _proxyManager.Shutdown();\r
295             _kancolleDb.Stop();\r
296         }\r
297 \r
298         private void MainForm_Resize(object sender, EventArgs e)\r
299         {\r
300             ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
301         }\r
302 \r
303         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
304         {\r
305             NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
306         }\r
307 \r
308         private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
309         {\r
310             ShowInTaskbar = true;\r
311             WindowState = FormWindowState.Normal;\r
312             TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
313             Activate();\r
314         }\r
315 \r
316         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
317         {\r
318             Close();\r
319         }\r
320 \r
321         private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
322         {\r
323             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
324             {\r
325                 ApplyConfig();\r
326                 StopRepeatingTimer(_configDialog.RepeatSettingsChanged);\r
327             }\r
328         }\r
329 \r
330         private void StopRepeatingTimer(IEnumerable<string> names)\r
331         {\r
332             foreach (var name in names)\r
333                 _notificationManager.StopRepeat(name);\r
334         }\r
335 \r
336         private void PerformZoom()\r
337         {\r
338             if (_config.Zoom == 100)\r
339                 return;\r
340             var prev = CurrentAutoScaleDimensions;\r
341             foreach (var control in new Control[]\r
342             {\r
343                 this, _listForm, labelLogin, linkLabelGuide,\r
344                 _configDialog, contextMenuStripMain, _errorDialog\r
345             })\r
346             {\r
347                 control.Font = new Font(control.Font.FontFamily, control.Font.Size * _config.Zoom / 100);\r
348             }\r
349             ShipLabel.LatinFont = new Font("Tahoma", 8f * _config.Zoom / 100);\r
350             var cur = CurrentAutoScaleDimensions;\r
351             ShipLabel.ScaleFactor = new SizeF(ShipLabel.ScaleFactor.Width * cur.Width / prev.Width,\r
352                 ShipLabel.ScaleFactor.Height * cur.Height / prev.Height);\r
353         }\r
354 \r
355         private void RestoreLocation()\r
356         {\r
357             if (_config.Location.X == int.MinValue)\r
358                 return;\r
359             if (IsTitleBarOnAnyScreen(_config.Location))\r
360                 Location = _config.Location;\r
361         }\r
362 \r
363         private void ApplyConfig()\r
364         {\r
365             _listForm.TopMost = TopMost = _config.TopMost;\r
366             _sniffer.Item.MarginShips = _config.MarginShips;\r
367             UpdateNumOfShips();\r
368             _sniffer.Item.MarginEquips = _config.MarginEquips;\r
369             UpdateNumOfEquips();\r
370             _sniffer.Achievement.ResetHours = _config.ResetHours;\r
371             labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
372                 labelPresetAkashiTimer.Visible = _config.UsePresetAkashi;\r
373             if (_config.KancolleDb.On)\r
374                 _kancolleDb.Start(_config.KancolleDb.Token);\r
375         }\r
376 \r
377         public void ApplyDebugLogSetting()\r
378         {\r
379             _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
380         }\r
381 \r
382         public bool ApplyProxySetting()\r
383         {\r
384             return _proxyManager.ApplyConfig();\r
385         }\r
386 \r
387         public void ApplyLogSetting()\r
388         {\r
389             LogServer.OutputDir = _config.Log.OutputDir;\r
390             LogServer.MaterialHistory = _sniffer.Material.MaterialHistory;\r
391             _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
392             _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
393             _sniffer.LogOutputDir = _config.Log.OutputDir;\r
394         }\r
395 \r
396         public static bool IsTitleBarOnAnyScreen(Point location)\r
397         {\r
398             var rect = new Rectangle(\r
399                 new Point(location.X + SystemInformation.IconSize.Width + SystemInformation.HorizontalFocusThickness,\r
400                     location.Y + SystemInformation.CaptionHeight), new Size(60, 1));\r
401             return Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect));\r
402         }\r
403 \r
404         private void timerMain_Tick(object sender, EventArgs e)\r
405         {\r
406             if (_started)\r
407             {\r
408                 try\r
409                 {\r
410                     UpdateTimers();\r
411                 }\r
412                 catch (Exception ex)\r
413                 {\r
414                     if (_errorDialog.ShowDialog(this, "エラーが発生しました。", ex.ToString()) == DialogResult.Abort)\r
415                         Application.Exit();\r
416                 }\r
417             }\r
418             if (_playLog == null || _configDialog.Visible)\r
419             {\r
420                 labelPlayLog.Visible = false;\r
421                 return;\r
422             }\r
423             PlayLog();\r
424         }\r
425 \r
426         public void SetPlayLog(string file)\r
427         {\r
428             _playLog = File.ReadLines(file).GetEnumerator();\r
429         }\r
430 \r
431         private void PlayLog()\r
432         {\r
433             var lines = new List<string>();\r
434             foreach (var s in new[] {"url: ", "request: ", "response: "})\r
435             {\r
436                 do\r
437                 {\r
438                     if (!_playLog.MoveNext() || _playLog.Current == null)\r
439                     {\r
440                         labelPlayLog.Visible = false;\r
441                         return;\r
442                     }\r
443                 } while (!_playLog.Current.StartsWith(s));\r
444                 lines.Add(_playLog.Current.Substring(s.Length));\r
445             }\r
446             labelPlayLog.Visible = !labelPlayLog.Visible;\r
447             ProcessRequestMain(lines[0], lines[1], lines[2]);\r
448         }\r
449 \r
450         private void ShowShipOnShipList(object sender, EventArgs ev)\r
451         {\r
452             if (!_listForm.Visible)\r
453                 return;\r
454             var idx = (int)((Control)sender).Tag;\r
455             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
456             if (statuses.Length <= idx)\r
457                 return;\r
458             _listForm.ShowShip(statuses[idx].Id);\r
459         }\r
460 \r
461         private void UpdateItemInfo()\r
462         {\r
463             UpdateNumOfShips();\r
464             UpdateNumOfEquips();\r
465             labelNumOfBuckets.Text = _sniffer.Material.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
466             UpdateBucketHistory();\r
467             var ac = _sniffer.Achievement.Value;\r
468             if (ac >= 10000)\r
469                 ac = 9999;\r
470             labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
471             toolTipAchievement.SetToolTip(labelAchievement,\r
472                 "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
473                 "EO " + _sniffer.ExMap.Achievement);\r
474             UpdateMaterialHistry();\r
475             if (_listForm.Visible)\r
476                 _listForm.UpdateList();\r
477         }\r
478 \r
479         private void UpdateNumOfShips()\r
480         {\r
481             var item = _sniffer.Item;\r
482             labelNumOfShips.Text = $"{item.NowShips:D}/{item.MaxShips:D}";\r
483             labelNumOfShips.ForeColor = item.TooManyShips ? CUDColor.Red : Color.Black;\r
484             if (item.RingShips)\r
485             {\r
486                 var message = $"残り{_sniffer.Item.MaxShips - _sniffer.Item.NowShips:D}隻";\r
487                 _notificationManager.Enqueue("艦娘数超過", message);\r
488                 item.RingShips = false;\r
489             }\r
490         }\r
491 \r
492         private void UpdateNumOfEquips()\r
493         {\r
494             var item = _sniffer.Item;\r
495             labelNumOfEquips.Text = $"{item.NowEquips:D}/{item.MaxEquips:D}";\r
496             labelNumOfEquips.ForeColor = item.TooManyEquips ? CUDColor.Red : Color.Black;\r
497             if (item.RingEquips)\r
498             {\r
499                 var message = $"残り{_sniffer.Item.MaxEquips - _sniffer.Item.NowEquips:D}個";\r
500                 _notificationManager.Enqueue("装備数超過", message);\r
501                 item.RingEquips = false;\r
502             }\r
503         }\r
504 \r
505         private void UpdateBucketHistory()\r
506         {\r
507             var count = _sniffer.Material.MaterialHistory[(int)Material.Bucket];\r
508             var day = CutOverflow(count.Now - count.BegOfDay, 999);\r
509             var week = CutOverflow(count.Now - count.BegOfWeek, 999);\r
510             labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
511         }\r
512 \r
513         private void UpdateMaterialHistry()\r
514         {\r
515             var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
516             var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
517             for (var i = 0; i < labels.Length; i++)\r
518             {\r
519                 var count = _sniffer.Material.MaterialHistory[i];\r
520                 var port = CutOverflow(count.Now - _sniffer.Material.PrevPort[i], 99999);\r
521                 var day = CutOverflow(count.Now - count.BegOfDay, 99999);\r
522                 var week = CutOverflow(count.Now - count.BegOfWeek, 99999);\r
523                 labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
524             }\r
525         }\r
526 \r
527         private int CutOverflow(int value, int limit)\r
528         {\r
529             if (value > limit)\r
530                 return limit;\r
531             if (value < -limit)\r
532                 return -limit;\r
533             return value;\r
534         }\r
535 \r
536         private void UpdateShipInfo()\r
537         {\r
538             UpdatePanelShipInfo();\r
539             NotifyDamagedShip();\r
540             UpdateChargeInfo();\r
541             UpdateRepairList();\r
542             if (_listForm.Visible)\r
543                 _listForm.UpdateList();\r
544         }\r
545 \r
546         private void UpdatePanelShipInfo()\r
547         {\r
548             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
549             panel7Ships.Visible = statuses.Length == 7;\r
550             _shipLabels.SetShipLabels(statuses);\r
551             if (_sniffer.CombinedFleetType == 0)\r
552                 _combinedFleet = false;\r
553             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
554             panelCombinedFleet.Visible = _combinedFleet;\r
555             if (_combinedFleet)\r
556                 _shipLabels.SetCombinedShipLabels(_sniffer.GetShipStatuses(0), _sniffer.GetShipStatuses(1));\r
557             UpdateAkashiTimer();\r
558             UpdateFighterPower(_combinedFleet);\r
559             UpdateLoS();\r
560             UpdateCondTimers();\r
561         }\r
562 \r
563         private void NotifyDamagedShip()\r
564         {\r
565             if (_sniffer.BadlyDamagedShips.Any())\r
566                 _notificationManager.Enqueue("大破警告", string.Join(" ", _sniffer.BadlyDamagedShips));\r
567         }\r
568 \r
569         private void NotifyAkashiTimer()\r
570         {\r
571             var akashi = _sniffer.AkashiTimer;\r
572             var msgs = akashi.GetNotice();\r
573             if (msgs.Length == 0)\r
574             {\r
575                 _notificationManager.StopRepeat("泊地修理");\r
576                 return;\r
577             }\r
578             if (!akashi.CheckReparing() && !(akashi.CheckPresetReparing() && _config.UsePresetAkashi))\r
579             {\r
580                 _notificationManager.StopRepeat("泊地修理");\r
581                 return;\r
582             }\r
583             if (msgs[0].Proceeded == "20分経過しました。")\r
584             {\r
585                 SetNotification("泊地修理20分経過", msgs[0].Proceeded);\r
586                 msgs[0].Proceeded = "";\r
587                 // 修理完了がいるかもしれないので続ける\r
588             }\r
589             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
590             {\r
591                 if (msgs[i].Proceeded != "")\r
592                     SetNotification("泊地修理進行", i, msgs[i].Proceeded);\r
593                 if (msgs[i].Completed != "")\r
594                     SetNotification("泊地修理完了", i, msgs[i].Completed);\r
595             }\r
596         }\r
597 \r
598         public void UpdateFighterPower(bool combined)\r
599         {\r
600             var fp = combined\r
601                 ? _sniffer.GetFighterPower(0).Zip(_sniffer.GetFighterPower(1), (a, b) => a + b).ToArray()\r
602                 : _sniffer.GetFighterPower(_currentFleet);\r
603             labelFighterPower.Text = fp[0].ToString("D");\r
604             var cr = combined\r
605                 ? _sniffer.GetContactTriggerRate(0) + _sniffer.GetContactTriggerRate(1)\r
606                 : _sniffer.GetContactTriggerRate(_currentFleet);\r
607             var text = "制空: " + (fp[0] == fp[1] ? $"{fp[0]}" : $"{fp[0]}~{fp[1]}") +\r
608                        $" 触接: {cr * 100:f1}";\r
609             toolTipFighterPower.SetToolTip(labelFighterPower, text);\r
610             toolTipFighterPower.SetToolTip(labelFighterPowerCaption, text);\r
611         }\r
612 \r
613         private void UpdateLoS()\r
614         {\r
615             labelLoS.Text = RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 1)).ToString("F1");\r
616             var text = $"係数3: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 3)):F1}\r\n" +\r
617                        $"係数4: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 4)):F1}";\r
618             toolTipLoS.SetToolTip(labelLoS, text);\r
619             toolTipLoS.SetToolTip(labelLoSCaption, text);\r
620         }\r
621 \r
622         private double RoundDown(double number)\r
623         {\r
624             return Floor(number * 10) / 10.0;\r
625         }\r
626 \r
627         private void UpdateBattleInfo()\r
628         {\r
629             ResetBattleInfo();\r
630             if (_sniffer.Battle.BattleState == BattleState.None)\r
631                 return;\r
632             panelBattleInfo.BringToFront();\r
633             var battle = _sniffer.Battle;\r
634             labelFormation.Text = battle.Formation;\r
635             UpdateBattleFighterPower();\r
636             if (_config.AlwaysShowResultRank)\r
637                 ShowResultRank();\r
638             if (_sniffer.Battle.BattleState == BattleState.Day)\r
639                 _listForm.UpdateAirBattleResult();\r
640         }\r
641 \r
642         private void ResetBattleInfo()\r
643         {\r
644             labelFormation.Text = "";\r
645             labelEnemyFighterPower.Text = "";\r
646             labelFighterPower.ForeColor = DefaultForeColor;\r
647             labelResultRank.Text = "判定";\r
648             panelBattleInfo.Visible = _sniffer.Battle.BattleState != BattleState.None;\r
649         }\r
650 \r
651         private void UpdateBattleFighterPower()\r
652         {\r
653             var battle = _sniffer.Battle;\r
654             var power = battle.EnemyFighterPower;\r
655             labelEnemyFighterPower.Text = power.AirCombat + power.UnknownMark;\r
656             if (power.AirCombat != power.Interception)\r
657             {\r
658                 var text = "防空: " + power.Interception + power.UnknownMark;\r
659                 toolTipFighterPower.SetToolTip(labelEnemyFighterPower, text);\r
660                 toolTipFighterPower.SetToolTip(labelEnemyFighterPowerCaption, text);\r
661             }\r
662             UpdateFighterPower(_sniffer.CombinedFleetType > 0 && battle.EnemyIsCombined);\r
663             labelFighterPower.ForeColor = new[]\r
664                 {DefaultForeColor, DefaultForeColor, CUDColor.Blue, CUDColor.Green, CUDColor.Orange, CUDColor.Red}[\r
665                 battle.AirControlLevel + 1];\r
666         }\r
667 \r
668         private void ShowResultRank()\r
669         {\r
670             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
671             labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
672         }\r
673 \r
674         private void labelResultRank_Click(object sender, EventArgs e)\r
675         {\r
676             ShowResultRank();\r
677         }\r
678 \r
679         private void UpdateChargeInfo()\r
680         {\r
681             var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
682             var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
683 \r
684             for (var i = 0; i < fuelSq.Length; i++)\r
685             {\r
686                 var stat = _sniffer.ChargeStatuses[i];\r
687                 fuelSq[i].ImageIndex = stat.Fuel;\r
688                 bullSq[i].ImageIndex = stat.Bull;\r
689             }\r
690         }\r
691 \r
692         private void UpdateNDocLabels()\r
693         {\r
694             _shipLabels.SetNDockLabels(_sniffer.NDock);\r
695         }\r
696 \r
697 \r
698         private void labelNDock_Click(object sender, EventArgs e)\r
699         {\r
700             _ndockFinishTimeMode = !_ndockFinishTimeMode;\r
701             UpdateTimers();\r
702         }\r
703 \r
704         private void UpdateMissionLabels()\r
705         {\r
706             foreach (var entry in\r
707                 new[] {labelMissionName1, labelMissionName2, labelMissionName3}.Zip(_sniffer.Missions,\r
708                     (label, mission) => new {label, mission.Name}))\r
709                 entry.label.Text = entry.Name;\r
710         }\r
711 \r
712         private void labelMission_Click(object sender, EventArgs e)\r
713         {\r
714             _missionFinishTimeMode = !_missionFinishTimeMode;\r
715             UpdateTimers();\r
716         }\r
717 \r
718         private void UpdateTimers()\r
719         {\r
720             foreach (var entry in\r
721                 new[] {labelMission1, labelMission2, labelMission3}.Zip(_sniffer.Missions,\r
722                     (label, mission) => new {label, mission.Name, mission.Timer}))\r
723             {\r
724                 entry.Timer.Update();\r
725                 SetTimerColor(entry.label, entry.Timer);\r
726                 entry.label.Text = entry.Timer.ToString(_missionFinishTimeMode);\r
727                 if (!entry.Timer.NeedRing)\r
728                     continue;\r
729                 SetNotification("遠征終了", entry.Name);\r
730                 entry.Timer.NeedRing = false;\r
731             }\r
732             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
733             {\r
734                 var entry = _sniffer.NDock[i];\r
735                 entry.Timer.Update();\r
736                 _shipLabels.SetNDockTimer(i, entry.Timer, _ndockFinishTimeMode);\r
737                 if (!entry.Timer.NeedRing)\r
738                     continue;\r
739                 SetNotification("入渠終了", entry.Name);\r
740                 entry.Timer.NeedRing = false;\r
741             }\r
742             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
743             for (var i = 0; i < kdock.Length; i++)\r
744             {\r
745                 var timer = _sniffer.KDock[i];\r
746                 timer.Update();\r
747                 SetTimerColor(kdock[i], timer);\r
748 \r
749                 kdock[i].Text = timer.ToString();\r
750                 if (!timer.NeedRing)\r
751                     continue;\r
752                 SetNotification("建造完了", $"第{i + 1:D}ドック");\r
753                 timer.NeedRing = false;\r
754             }\r
755             UpdateCondTimers();\r
756             UpdateAkashiTimer();\r
757         }\r
758 \r
759         private void SetTimerColor(Label label, RingTimer timer)\r
760         {\r
761             label.ForeColor = timer.IsFinished ? CUDColor.Red : Color.Black;\r
762         }\r
763 \r
764         private void UpdateCondTimers()\r
765         {\r
766             DateTime timer;\r
767             if (_combinedFleet)\r
768             {\r
769                 var timer1 = _sniffer.GetConditionTimer(0);\r
770                 var timer2 = _sniffer.GetConditionTimer(1);\r
771                 timer = timer2 > timer1 ? timer2 : timer1;\r
772             }\r
773             else\r
774             {\r
775                 timer = _sniffer.GetConditionTimer(_currentFleet);\r
776             }\r
777             var now = DateTime.Now;\r
778             if (timer == DateTime.MinValue)\r
779             {\r
780                 labelCondTimerTitle.Text = "";\r
781                 labelCondTimer.Text = "";\r
782                 return;\r
783             }\r
784             var span = TimeSpan.FromSeconds(Ceiling((timer - now).TotalSeconds));\r
785             if (span >= TimeSpan.FromMinutes(9))\r
786             {\r
787                 labelCondTimerTitle.Text = "cond40まで";\r
788                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
789                 labelCondTimer.ForeColor = DefaultForeColor;\r
790             }\r
791             else\r
792             {\r
793                 labelCondTimerTitle.Text = "cond49まで";\r
794                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
795                 labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColor.Red : DefaultForeColor;\r
796             }\r
797             var notice = _sniffer.GetConditionNotice();\r
798             if (notice == null)\r
799                 return;\r
800             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
801             {\r
802                 if (!_config.NotifyConditions.Contains(notice[i]))\r
803                     return;\r
804                 SetNotification("疲労回復" + notice[i], i, "cond" + notice[i]);\r
805             }\r
806         }\r
807 \r
808         private void SetNotification(string key, string subject)\r
809         {\r
810             SetNotification(key, 0, subject);\r
811         }\r
812 \r
813         private void SetNotification(string key, int fleet, string subject)\r
814         {\r
815             var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
816             _notificationManager.Enqueue(key, fleet, subject,\r
817                 (spec.Flags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
818         }\r
819 \r
820         private void UpdateAkashiTimer()\r
821         {\r
822             if (_config.UsePresetAkashi)\r
823                 UpdatePresetAkashiTimer();\r
824             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
825             _shipLabels.SetAkashiTimer(statuses,\r
826                 _sniffer.AkashiTimer.GetTimers(_currentFleet));\r
827             NotifyAkashiTimer();\r
828         }\r
829 \r
830         private void UpdatePresetAkashiTimer()\r
831         {\r
832             var akashi = _sniffer.AkashiTimer;\r
833             var span = akashi.PresetDeckTimer;\r
834             var color = span == TimeSpan.Zero && akashi.CheckPresetReparing() ? CUDColor.Red : DefaultForeColor;\r
835             var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
836             labelAkashiRepairTimer.ForeColor = color;\r
837             labelAkashiRepairTimer.Text = text;\r
838             if (akashi.CheckPresetReparing() && !akashi.CheckReparing(_currentFleet))\r
839             {\r
840                 labelPresetAkashiTimer.ForeColor = color;\r
841                 labelPresetAkashiTimer.Text = text;\r
842             }\r
843             else\r
844             {\r
845                 labelPresetAkashiTimer.ForeColor = DefaultForeColor;\r
846                 labelPresetAkashiTimer.Text = "";\r
847             }\r
848         }\r
849 \r
850         private void UpdateRepairList()\r
851         {\r
852             panelRepairList.SetRepairList(_sniffer.RepairList);\r
853         }\r
854 \r
855         private void UpdateQuestList()\r
856         {\r
857             var category = new[]\r
858             {\r
859                 labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
860                 labelQuestColor6\r
861             };\r
862             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
863             var progress = new[]\r
864                 {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
865             var quests = _sniffer.Quests;\r
866             for (var i = 0; i < name.Length; i++)\r
867             {\r
868                 if (i < quests.Length)\r
869                 {\r
870                     category[i].BackColor = quests[i].Color;\r
871                     name[i].Text = quests[i].Name;\r
872                     progress[i].Text = $"{quests[i].Progress:D}%";\r
873                 }\r
874                 else\r
875                 {\r
876                     category[i].BackColor = DefaultBackColor;\r
877                     name[i].Text = progress[i].Text = "";\r
878                 }\r
879             }\r
880         }\r
881 \r
882         private void Ring(string balloonTitle, string balloonMessage, string name)\r
883         {\r
884             if (_config.FlashWindow && (_config.Notifications[name].Flags & NotificationType.FlashWindow) != 0)\r
885                 Win32API.FlashWindow(Handle);\r
886             if (_config.ShowBaloonTip && (_config.Notifications[name].Flags & NotificationType.ShowBaloonTip) != 0)\r
887                 notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
888             if (_config.PlaySound && (_config.Notifications[name].Flags & NotificationType.PlaySound) != 0)\r
889                 PlaySound(_config.Sounds[name], _config.Sounds.Volume);\r
890             if (_config.Pushbullet.On && (_config.Notifications[name].Flags & NotificationType.Pushbullet) != 0)\r
891             {\r
892                 Task.Run(() =>\r
893                 {\r
894                     PushNotification.PushToPushbullet(_config.Pushbullet.Token, balloonTitle, balloonMessage);\r
895                 });\r
896             }\r
897             if (_config.Pushover.On && (_config.Notifications[name].Flags & NotificationType.Pushbullet) != 0)\r
898             {\r
899                 Task.Run(() =>\r
900                 {\r
901                     PushNotification.PushToPushover(_config.Pushover.ApiKey, _config.Pushover.UserKey,\r
902                         balloonTitle, balloonMessage);\r
903                 });\r
904             }\r
905         }\r
906 \r
907         [DllImport("winmm.dll")]\r
908         private static extern int mciSendString(String command,\r
909             StringBuilder buffer, int bufferSize, IntPtr hwndCallback);\r
910 \r
911 // ReSharper disable InconsistentNaming\r
912         private const int MM_MCINOTIFY = 0x3B9;\r
913 \r
914         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
915 // ReSharper restore InconsistentNaming\r
916 \r
917         public void PlaySound(string file, int volume)\r
918         {\r
919             if (!File.Exists(file))\r
920                 return;\r
921             mciSendString("close sound", null, 0, IntPtr.Zero);\r
922             if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
923                 return;\r
924             mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
925             mciSendString("play sound notify", null, 0, Handle);\r
926         }\r
927 \r
928         protected override void WndProc(ref Message m)\r
929         {\r
930             if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
931                 mciSendString("close sound", null, 0, IntPtr.Zero);\r
932             base.WndProc(ref m);\r
933         }\r
934 \r
935         private void SetupFleetClick()\r
936         {\r
937             var labels = new[]\r
938             {\r
939                 new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
940                 new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
941                 new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
942             };\r
943             foreach (var a in labels)\r
944             {\r
945                 for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
946                 {\r
947                     a[fleet].Tag = fleet;\r
948                     a[fleet].Click += labelFleet_Click;\r
949                 }\r
950             }\r
951         }\r
952 \r
953         private void labelFleet_Click(object sender, EventArgs e)\r
954         {\r
955             if (!_started)\r
956                 return;\r
957             var fleet = (int)((Label)sender).Tag;\r
958             if (_currentFleet == fleet)\r
959             {\r
960                 if (fleet > 0)\r
961                     return;\r
962                 _combinedFleet = _sniffer.CombinedFleetType > 0 && !_combinedFleet;\r
963                 UpdatePanelShipInfo();\r
964                 return;\r
965             }\r
966             _combinedFleet = false;\r
967             _currentFleet = fleet;\r
968             foreach (var label in _labelCheckFleets)\r
969                 label.Visible = false;\r
970             _labelCheckFleets[fleet].Visible = true;\r
971             UpdatePanelShipInfo();\r
972         }\r
973 \r
974         private void labelFleet1_MouseHover(object sender, EventArgs e)\r
975         {\r
976             labelFleet1.Text = _currentFleet == 0 && _sniffer.CombinedFleetType > 0 && !_combinedFleet ? "連合" : "第一";\r
977         }\r
978 \r
979         private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
980         {\r
981             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
982         }\r
983 \r
984         private readonly Color _activeButtonColor = Color.FromArgb(152, 179, 208);\r
985 \r
986         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
987         {\r
988             if (labelBucketHistory.Visible)\r
989             {\r
990                 labelBucketHistory.Visible = false;\r
991                 labelBucketHistoryButton.BackColor = DefaultBackColor;\r
992             }\r
993             else\r
994             {\r
995                 labelBucketHistory.Visible = true;\r
996                 labelBucketHistory.BringToFront();\r
997                 labelBucketHistoryButton.BackColor = _activeButtonColor;\r
998             }\r
999         }\r
1000 \r
1001         private void labelBucketHistory_Click(object sender, EventArgs e)\r
1002         {\r
1003             labelBucketHistory.Visible = false;\r
1004             labelBucketHistoryButton.BackColor = DefaultBackColor;\r
1005         }\r
1006 \r
1007         private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
1008         {\r
1009             if (panelMaterialHistory.Visible)\r
1010             {\r
1011                 panelMaterialHistory.Visible = false;\r
1012                 labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1013             }\r
1014             else\r
1015             {\r
1016                 panelMaterialHistory.Visible = true;\r
1017                 panelMaterialHistory.BringToFront();\r
1018                 labelMaterialHistoryButton.BackColor = _activeButtonColor;\r
1019             }\r
1020         }\r
1021 \r
1022         private void panelMaterialHistory_Click(object sender, EventArgs e)\r
1023         {\r
1024             panelMaterialHistory.Visible = false;\r
1025             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1026         }\r
1027 \r
1028         public void ResetAchievemnt()\r
1029         {\r
1030             _sniffer.Achievement.Reset();\r
1031             UpdateItemInfo();\r
1032         }\r
1033 \r
1034         private void labelRepairListButton_Click(object sender, EventArgs e)\r
1035         {\r
1036             if (panelRepairList.Visible)\r
1037             {\r
1038                 panelRepairList.Visible = false;\r
1039                 labelRepairListButton.BackColor = DefaultBackColor;\r
1040             }\r
1041             else\r
1042             {\r
1043                 panelRepairList.Visible = true;\r
1044                 panelRepairList.BringToFront();\r
1045                 labelRepairListButton.BackColor = _activeButtonColor;\r
1046             }\r
1047         }\r
1048 \r
1049         private void panelRepairList_Click(object sender, EventArgs e)\r
1050         {\r
1051             panelRepairList.Visible = false;\r
1052             labelRepairListButton.BackColor = DefaultBackColor;\r
1053         }\r
1054 \r
1055         private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
1056         {\r
1057             _listForm.UpdateList();\r
1058             _listForm.Show();\r
1059             if (_listForm.WindowState == FormWindowState.Minimized)\r
1060                 _listForm.WindowState = FormWindowState.Normal;\r
1061             _listForm.Activate();\r
1062         }\r
1063 \r
1064         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
1065         {\r
1066             Process.Start("http://localhost:" + _config.Proxy.Listen + "/");\r
1067         }\r
1068 \r
1069         private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
1070         {\r
1071             try\r
1072             {\r
1073                 var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
1074                 Process.Start(proc);\r
1075             }\r
1076             catch (FileNotFoundException)\r
1077             {\r
1078             }\r
1079         }\r
1080     }\r
1081 }