OSDN Git Service

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