OSDN Git Service

サーバーからの応答が異常なときにその旨を表示する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
1 // Copyright (C) 2013, 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 // \r
3 // This program is part of KancolleSniffer.\r
4 //\r
5 // KancolleSniffer is free software: you can redistribute it and/or modify\r
6 // it under the terms of the GNU General Public License as published by\r
7 // the Free Software Foundation, either version 3 of the License, or\r
8 // (at your option) any later version.\r
9 //\r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 //\r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.\r
17 \r
18 using System;\r
19 using System.Collections.Generic;\r
20 using System.Diagnostics;\r
21 using System.Drawing;\r
22 using System.IO;\r
23 using System.Linq;\r
24 using System.Net;\r
25 using System.Net.Sockets;\r
26 using System.Runtime.InteropServices;\r
27 using System.Text;\r
28 using System.Threading;\r
29 using System.Threading.Tasks;\r
30 using System.Windows.Forms;\r
31 using System.Xml;\r
32 using Codeplex.Data;\r
33 using Microsoft.CSharp.RuntimeBinder;\r
34 using Microsoft.Win32;\r
35 using Nekoxy;\r
36 using static System.Math;\r
37 using Timer = System.Windows.Forms.Timer;\r
38 \r
39 namespace KancolleSniffer\r
40 {\r
41     public partial class MainForm : Form\r
42     {\r
43         private readonly Sniffer _sniffer = new Sniffer();\r
44         private readonly Config _config = new Config();\r
45         private readonly ConfigDialog _configDialog;\r
46         private int _currentFleet;\r
47         private readonly Label[] _labelCheckFleets;\r
48         private readonly ShipLabels _shipLabels;\r
49         private readonly ShipListForm _shipListForm;\r
50         private readonly NoticeQueue _noticeQueue;\r
51         private bool _started;\r
52         private string _debugLogFile;\r
53         private IEnumerator<string> _playLog;\r
54         private LogServer _logServer;\r
55         private int _prevProxyPort;\r
56         private readonly SystemProxy _systemProxy = new SystemProxy();\r
57 \r
58         public MainForm()\r
59         {\r
60             InitializeComponent();\r
61             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
62             _configDialog = new ConfigDialog(_config, this);\r
63             _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
64 \r
65             // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
66             // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
67             ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
68                 CurrentAutoScaleDimensions.Height / 12f);\r
69 \r
70             SetupFleetClick();\r
71             _shipLabels = new ShipLabels();\r
72             _shipLabels.CreateAkashiTimers(panelShipInfo);\r
73             _shipLabels.CreateLabels(panelShipInfo, ShowShipOnShipList);\r
74             _shipLabels.CreateDamagedShipList(panelDamagedShipList, panelDamagedShipList_Click);\r
75             _shipLabels.CreateNDockLabels(panelDock);\r
76             _shipListForm = new ShipListForm(_sniffer, _config) {Owner = this};\r
77             _noticeQueue = new NoticeQueue(Ring);\r
78         }\r
79 \r
80         private void HttpProxy_AfterSessionComplete(Session session)\r
81         {\r
82             Invoke(new Action<Session>(ProcessRequest), session);\r
83         }\r
84 \r
85         private void ProcessRequest(Session session)\r
86         {\r
87             var url = session.Request.PathAndQuery;\r
88             var request = session.Request.BodyAsString;\r
89             var response = session.Response.BodyAsString;\r
90             if (!url.Contains("kcsapi/"))\r
91                 return;\r
92             if (response == null || !response.StartsWith("svdata="))\r
93             {\r
94                 WriteDebugLog(url, request, response ?? "(null)");\r
95                 ShowServerError();\r
96                 return;\r
97             }\r
98             response = response.Remove(0, "svdata=".Length);\r
99             try\r
100             {\r
101                 var json = DynamicJson.Parse(response);\r
102                 WriteDebugLog(url, request, json.ToString());\r
103                 UpdateInfo(_sniffer.Sniff(url, request, json));\r
104             }\r
105             catch (XmlException)\r
106             {\r
107                 WriteDebugLog(url, request, response);\r
108                 ShowServerError();\r
109             }\r
110             catch (RuntimeBinderException)\r
111             {\r
112                 labelLogin.Text = "現在の艦これに対応していません。\n新しいバージョンを利用してください。";\r
113                 labelLogin.Visible = true;\r
114             }\r
115         }\r
116 \r
117         private void WriteDebugLog(string url, string request, string response)\r
118         {\r
119             if (_debugLogFile != null)\r
120             {\r
121                 File.AppendAllText(_debugLogFile, $"url: {url}\nrequest: {request}\nresponse: {response}\n");\r
122             }\r
123         }\r
124 \r
125         private void UpdateInfo(Sniffer.Update update)\r
126         {\r
127             if (update == Sniffer.Update.Error)\r
128             {\r
129                 ShowServerError();\r
130                 return;\r
131             }\r
132             if (update == Sniffer.Update.Start)\r
133             {\r
134                 labelLogin.Visible = false;\r
135                 _started = true;\r
136                 return;\r
137             }\r
138             if (!_started)\r
139                 return;\r
140             if ((update & Sniffer.Update.Item) != 0)\r
141                 UpdateItemInfo();\r
142             if ((update & Sniffer.Update.Timer) != 0)\r
143                 UpdateTimers();\r
144             if ((update & Sniffer.Update.NDock) != 0)\r
145                 UpdateNDocLabels();\r
146             if ((update & Sniffer.Update.Mission) != 0)\r
147                 UpdateMissionLabels();\r
148             if ((update & Sniffer.Update.QuestList) != 0)\r
149                 UpdateQuestList();\r
150             if ((update & Sniffer.Update.Ship) != 0)\r
151                 UpdateShipInfo();\r
152             if ((update & Sniffer.Update.Battle) != 0)\r
153                 UpdateBattleInfo();\r
154         }\r
155 \r
156         private void ShowServerError()\r
157         {\r
158             labelLogin.Text = "サーバーからの応答が異常です。";\r
159             labelLogin.Visible = true;\r
160         }\r
161 \r
162         private void MainForm_Load(object sender, EventArgs e)\r
163         {\r
164             _config.Load();\r
165             RestoreLocation();\r
166             if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
167                 ShowInTaskbar = false;\r
168             ApplyConfig();\r
169             ApplyDebugLogSetting();\r
170             ApplyLogSetting();\r
171             _sniffer.LoadState();\r
172             ApplyProxySetting();\r
173             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;\r
174         }\r
175 \r
176         private void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)\r
177         {\r
178             if (e.Mode != PowerModes.Resume)\r
179                 return;\r
180             Task.Run(() =>\r
181             {\r
182                 for (var i = 0; i < 5; Thread.Sleep(10000), i++)\r
183                 {\r
184                     WebResponse res = null;\r
185                     SystemProxy.Refresh();\r
186                     var system = WebRequest.GetSystemWebProxy();\r
187                     if (!system.GetProxy(new Uri("http://125.6.184.16/")).IsLoopback)\r
188                     {\r
189                         File.AppendAllText("wakeup.log",\r
190                             $"[{DateTime.Now.ToString("g")}] proxy settings doesn't work.\r\n");\r
191                         continue;\r
192                     }\r
193                     try\r
194                     {\r
195                         var req = WebRequest.Create("http://kancollesniffer.osdn.jp/version");\r
196                         res = req.GetResponse();\r
197                         break;\r
198                     }\r
199                     catch\r
200                     {\r
201                         File.AppendAllText("wakeup.log",\r
202                             $"[{DateTime.Now.ToString("g")}] failed to connect internet.\r\n");\r
203                     }\r
204                     finally\r
205                     {\r
206                         res?.Close();\r
207                     }\r
208                 }\r
209             });\r
210         }\r
211 \r
212         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
213         {\r
214             e.Cancel = false;\r
215             _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
216             _config.Save();\r
217             Task.Run(() => ShutdownProxy());\r
218             _logServer?.Stop();\r
219             if (_config.Proxy.Auto)\r
220                 _systemProxy.RestoreSettings();\r
221             SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;\r
222         }\r
223 \r
224         private void ShutdownProxy()\r
225         {\r
226             HttpProxy.Shutdown();\r
227         }\r
228 \r
229         private void MainForm_Resize(object sender, EventArgs e)\r
230         {\r
231             ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
232         }\r
233 \r
234         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
235         {\r
236             NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
237         }\r
238 \r
239         private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
240         {\r
241             ShowInTaskbar = true;\r
242             WindowState = FormWindowState.Normal;\r
243             TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
244             Activate();\r
245         }\r
246 \r
247         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
248         {\r
249             Close();\r
250         }\r
251 \r
252         private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
253         {\r
254             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
255                 ApplyConfig();\r
256         }\r
257 \r
258         private void RestoreLocation()\r
259         {\r
260             if (_config.Location.X == int.MinValue)\r
261                 return;\r
262             var newBounds = Bounds;\r
263             newBounds.Location = _config.Location;\r
264             if (IsVisibleOnAnyScreen(newBounds))\r
265                 Location = _config.Location;\r
266         }\r
267 \r
268         private void ApplyConfig()\r
269         {\r
270             _shipListForm.TopMost = TopMost = _config.TopMost;\r
271             _sniffer.Item.MarginShips = _config.MarginShips;\r
272             _sniffer.Item.MarginEquips = _config.MarginEquips;\r
273             _sniffer.Achievement.ResetHours = _config.ResetHours;\r
274         }\r
275 \r
276         public void ApplyDebugLogSetting()\r
277         {\r
278             _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
279         }\r
280 \r
281         public bool ApplyProxySetting()\r
282         {\r
283             if (!_config.Proxy.Auto)\r
284                 _systemProxy.RestoreSettings();\r
285             if (_config.Proxy.UseUpstream)\r
286             {\r
287                 HttpProxy.UpstreamProxyHost = "127.0.0.1";\r
288                 HttpProxy.UpstreamProxyPort = _config.Proxy.UpstreamPort;\r
289             }\r
290             HttpProxy.IsEnableUpstreamProxy = _config.Proxy.UseUpstream;\r
291             var result = true;\r
292             if (!HttpProxy.IsInListening || _config.Proxy.Listen != _prevProxyPort)\r
293             {\r
294                 ShutdownProxy();\r
295                 result = StartProxy();\r
296             }\r
297             if (_config.Proxy.Auto && result)\r
298             {\r
299                 _systemProxy.SetAutoProxyUrl(HttpProxy.LocalPort == 8080\r
300                     ? ProxyConfig.AutoConfigUrl\r
301                     : ProxyConfig.AutoConfigUrlWithPort + HttpProxy.LocalPort);\r
302             }\r
303             _prevProxyPort = _config.Proxy.Listen;\r
304             return result;\r
305         }\r
306 \r
307         private bool StartProxy()\r
308         {\r
309             try\r
310             {\r
311                 HttpProxy.Startup(_config.Proxy.Listen, false, false);\r
312             }\r
313             catch (SocketException e)\r
314             {\r
315                 if (e.SocketErrorCode != SocketError.AddressAlreadyInUse)\r
316                     throw;\r
317                 if (WarnConflictPortNumber("プロキシサーバー", _config.Proxy.Listen, _config.Proxy.Auto) == DialogResult.No ||\r
318                     !_config.Proxy.Auto)\r
319                 {\r
320                     _systemProxy.RestoreSettings();\r
321                     return false;\r
322                 }\r
323                 HttpProxy.Startup(0, false, false);\r
324                 _config.Proxy.Listen = HttpProxy.LocalPort;\r
325             }\r
326             return true;\r
327         }\r
328 \r
329         private DialogResult WarnConflictPortNumber(string name, int port, bool auto)\r
330         {\r
331             var msg = $"{name}のポート番号{port}は他のアプリケーションが使用中です。";\r
332             var cap = "ポート番号の衝突";\r
333             return auto\r
334                 ? MessageBox.Show(this, msg + "自動的に別の番号を割り当てますか?", cap,\r
335                     MessageBoxButtons.YesNo, MessageBoxIcon.Question)\r
336                 : MessageBox.Show(this, msg + "設定ダイアログでポート番号を変更してください。", cap,\r
337                     MessageBoxButtons.OK, MessageBoxIcon.Exclamation);\r
338         }\r
339 \r
340         public bool ApplyLogSetting()\r
341         {\r
342             var result = true;\r
343             if (_config.Log.ServerOn)\r
344             {\r
345                 result = StartLogServer();\r
346             }\r
347             else\r
348             {\r
349                 _logServer?.Stop();\r
350                 _logServer = null;\r
351             }\r
352             _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
353             _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
354             _sniffer.LogOutputDir = _config.Log.OutputDir;\r
355             return result;\r
356         }\r
357 \r
358         private bool StartLogServer()\r
359         {\r
360             var port = _config.Log.Listen;\r
361             if (_logServer?.Port == port)\r
362                 return true;\r
363             _logServer?.Stop();\r
364             _logServer = null;\r
365             try\r
366             {\r
367                 _logServer = new LogServer(port);\r
368                 _logServer.Start();\r
369             }\r
370             catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)\r
371             {\r
372                 if (WarnConflictPortNumber("閲覧サーバー", port, true) == DialogResult.No)\r
373                     return false;\r
374                 _logServer = new LogServer(0);\r
375                 _logServer.Start();\r
376                 _config.Log.Listen = _logServer.Port;\r
377             }\r
378             _logServer.OutputDir = _config.Log.OutputDir;\r
379             return true;\r
380         }\r
381 \r
382         public static bool IsVisibleOnAnyScreen(Rectangle rect)\r
383         {\r
384             return Screen.AllScreens.Any(screen => screen.WorkingArea.IntersectsWith(rect));\r
385         }\r
386 \r
387         private void timerMain_Tick(object sender, EventArgs e)\r
388         {\r
389             if (_started)\r
390                 UpdateTimers();\r
391             if (_playLog == null || _configDialog.Visible)\r
392             {\r
393                 labelPlayLog.Visible = false;\r
394                 return;\r
395             }\r
396             PlayLog();\r
397         }\r
398 \r
399         public void SetPlayLog(string file)\r
400         {\r
401             _playLog = File.ReadLines(file).GetEnumerator();\r
402         }\r
403 \r
404         private void PlayLog()\r
405         {\r
406             var lines = new List<string>();\r
407             foreach (var s in new[] {"url: ", "request: ", "response: "})\r
408             {\r
409                 if (!_playLog.MoveNext() || !_playLog.Current.StartsWith(s))\r
410                 {\r
411                     labelPlayLog.Visible = false;\r
412                     return;\r
413                 }\r
414                 lines.Add(_playLog.Current.Substring(s.Count()));\r
415             }\r
416             labelPlayLog.Visible = !labelPlayLog.Visible;\r
417             var json = DynamicJson.Parse(lines[2]);\r
418             UpdateInfo(_sniffer.Sniff(lines[0], lines[1], json));\r
419         }\r
420 \r
421         private void ShowShipOnShipList(object sender, EventArgs ev)\r
422         {\r
423             if (!_shipListForm.Visible)\r
424                 return;\r
425             var idx = (int)((Control)sender).Tag;\r
426             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
427             if (statuses.Length <= idx)\r
428                 return;\r
429             _shipListForm.ShowShip(statuses[idx].Id);\r
430         }\r
431 \r
432         private void UpdateItemInfo()\r
433         {\r
434             UpdateNumOfShips();\r
435             UpdateNumOfEquips();\r
436             labelNumOfBuckets.Text = _sniffer.Item.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
437             UpdateBucketHistory();\r
438             var ac = _sniffer.Achievement.Value;\r
439             if (ac >= 10000)\r
440                 ac = 9999;\r
441             labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
442             toolTipAchievement.SetToolTip(labelAchievement,\r
443                 "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
444                 "EO " + _sniffer.ExMap.Achievement);\r
445             UpdateMaterialHistry();\r
446             if (_shipListForm.Visible)\r
447                 _shipListForm.UpdateList();\r
448         }\r
449 \r
450         private void UpdateNumOfShips()\r
451         {\r
452             var item = _sniffer.Item;\r
453             labelNumOfShips.Text = $"{item.NowShips:D}/{item.MaxShips:D}";\r
454             labelNumOfShips.ForeColor = item.TooManyShips ? Color.Red : Color.Black;\r
455             if (item.RingShips)\r
456             {\r
457                 var message = $"残り{_sniffer.Item.MaxShips - _sniffer.Item.NowShips:D}隻";\r
458                 _noticeQueue.Enqueue("艦娘が多すぎます", message, _config.MaxShipsSoundFile);\r
459                 item.RingShips = false;\r
460             }\r
461         }\r
462 \r
463         private void UpdateNumOfEquips()\r
464         {\r
465             var item = _sniffer.Item;\r
466             labelNumOfEquips.Text = $"{item.NowEquips:D}/{item.MaxEquips:D}";\r
467             labelNumOfEquips.ForeColor = item.TooManyEquips ? Color.Red : Color.Black;\r
468             if (item.RingEquips)\r
469             {\r
470                 var message = $"残り{_sniffer.Item.MaxEquips - _sniffer.Item.NowEquips:D}個";\r
471                 _noticeQueue.Enqueue("装備が多すぎます", message, _config.MaxEquipsSoundFile);\r
472                 item.RingEquips = false;\r
473             }\r
474         }\r
475 \r
476         private void UpdateBucketHistory()\r
477         {\r
478             var count = _sniffer.Item.MaterialHistory[(int)Material.Bucket];\r
479             var day = count.Now - count.BegOfDay;\r
480             var week = count.Now - count.BegOfWeek;\r
481             if (day >= 1000)\r
482                 day = 999;\r
483             if (week >= 1000)\r
484                 week = 999;\r
485             labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
486         }\r
487 \r
488         private void UpdateMaterialHistry()\r
489         {\r
490             var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
491             var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
492             for (var i = 0; i < labels.Length; i++)\r
493             {\r
494                 var count = _sniffer.Item.MaterialHistory[i];\r
495                 var port = count.Now - _sniffer.Item.PrevPort[i];\r
496                 if (port >= 100000)\r
497                     port = 99999;\r
498                 var day = count.Now - count.BegOfDay;\r
499                 if (day >= 100000)\r
500                     day = 99999;\r
501                 var week = count.Now - count.BegOfWeek;\r
502                 if (week >= 100000)\r
503                     week = 99999;\r
504                 labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
505             }\r
506         }\r
507 \r
508         private void UpdateShipInfo()\r
509         {\r
510             UpdatePanelShipInfo();\r
511             NotifyDamagedShip();\r
512             UpdateChargeInfo();\r
513             UpdateDamagedShipList();\r
514             if (_shipListForm.Visible)\r
515                 _shipListForm.UpdateList();\r
516         }\r
517 \r
518         private void UpdatePanelShipInfo()\r
519         {\r
520             var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
521             _shipLabels.SetShipInfo(statuses);\r
522             UpdateAkashiTimer();\r
523             UpdateFighterPower();\r
524             UpdateLoS();\r
525             UpdateCondTimers();\r
526         }\r
527 \r
528         private void NotifyDamagedShip()\r
529         {\r
530             if (_sniffer.Battle.HasDamagedShip)\r
531                 _noticeQueue.Enqueue("大破した艦娘がいます", string.Join(" ", _sniffer.Battle.DamagedShipNames),\r
532                     _config.DamagedShipSoundFile);\r
533         }\r
534 \r
535         private void NotifyAkashiTimer()\r
536         {\r
537             var msgs = _sniffer.GetAkashiTimerNotice();\r
538             if (msgs.Length == 0)\r
539                 return;\r
540             if (msgs[0].Proceeded == "20分経過しました。")\r
541             {\r
542                 _noticeQueue.Enqueue("泊地修理", msgs[0].Proceeded, _config.Akashi20MinSoundFile);\r
543                 msgs[0].Proceeded = "";\r
544                 // 修理完了がいるかもしれないので続ける\r
545             }\r
546             var fn = new[] {"第一艦隊", "第二艦隊", "第三艦隊", "第四艦隊"};\r
547             for (var i = 0; i < fn.Length; i++)\r
548             {\r
549                 if (msgs[i].Proceeded != "")\r
550                     _noticeQueue.Enqueue("泊地修理 " + fn[i], "修理進行:" + msgs[i].Proceeded, _config.AkashiProgressSoundFile);\r
551                 if (msgs[i].Completed != "")\r
552                     _noticeQueue.Enqueue("泊地修理 " + fn[i], "修理完了:" + msgs[i].Completed, _config.AkashiCompleteSoundFile);\r
553             }\r
554         }\r
555 \r
556         public void UpdateFighterPower()\r
557         {\r
558             labelFighterPower.Text = _sniffer.GetFighterPower(_currentFleet, true).ToString("D");\r
559             toolTipFigherPower.SetToolTip(labelFighterPower, "熟練度抜き " + _sniffer.GetFighterPower(_currentFleet, false));\r
560         }\r
561 \r
562         private void UpdateLoS()\r
563         {\r
564             labelLoS.Text = _sniffer.GetFleetLineOfSights(_currentFleet).ToString("F1");\r
565         }\r
566 \r
567         private void UpdateBattleInfo()\r
568         {\r
569             labelFormation.Text = "";\r
570             labelEnemyFighterPower.Text = "";\r
571             labelFighterPower.ForeColor = DefaultForeColor;\r
572             labelResultRank.Text = "判定";\r
573             panelBattleInfo.Visible = _sniffer.Battle.InBattle;\r
574             if (!_sniffer.Battle.InBattle)\r
575                 return;\r
576             panelBattleInfo.BringToFront();\r
577             var battle = _sniffer.Battle;\r
578             var color = new[] {DefaultForeColor, DefaultForeColor, Color.Blue, Color.Green, Color.Orange, Color.Red};\r
579             labelFormation.Text = battle.Formation;\r
580             var enemyFp = battle.EnemyFighterPower;\r
581             labelEnemyFighterPower.Text = (enemyFp & BattleInfo.IncollectFighterPowerFlag) == 0\r
582                 ? enemyFp.ToString()\r
583                 : (enemyFp & ~BattleInfo.IncollectFighterPowerFlag) + "+";\r
584             labelFighterPower.ForeColor = color[battle.AirControlLevel + 1];\r
585             if (_config.AlwaysShowResultRank)\r
586                 ShowResultRank();\r
587         }\r
588 \r
589         private void ShowResultRank()\r
590         {\r
591             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
592             labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
593         }\r
594 \r
595         private void labelResultRank_Click(object sender, EventArgs e)\r
596         {\r
597             ShowResultRank();\r
598         }\r
599 \r
600         private void UpdateChargeInfo()\r
601         {\r
602             var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
603             var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
604 \r
605             for (var i = 0; i < fuelSq.Length; i++)\r
606             {\r
607                 var stat = _sniffer.ChargeStatuses[i];\r
608                 fuelSq[i].ImageIndex = stat.Fuel;\r
609                 bullSq[i].ImageIndex = stat.Bull;\r
610             }\r
611         }\r
612 \r
613         private void UpdateNDocLabels()\r
614         {\r
615             _shipLabels.SetNDockLabels(_sniffer.NDock);\r
616         }\r
617 \r
618         private void UpdateMissionLabels()\r
619         {\r
620             foreach (var entry in\r
621                 new[] {labelMissionName1, labelMissionName2, labelMissionName3}.Zip(_sniffer.Missions,\r
622                     (label, mission) => new {label, mission.Name}))\r
623                 entry.label.Text = entry.Name;\r
624         }\r
625 \r
626         private void UpdateTimers()\r
627         {\r
628             foreach (var entry in\r
629                 new[] {labelMission1, labelMission2, labelMission3}.Zip(_sniffer.Missions,\r
630                     (label, mission) => new {label, mission.Name, mission.Timer}))\r
631             {\r
632                 entry.Timer.Update();\r
633                 SetTimerColor(entry.label, entry.Timer);\r
634                 var rest = entry.Timer.Rest;\r
635                 entry.label.Text = rest.Days == 0 ? rest.ToString(@"hh\:mm\:ss") : rest.ToString(@"d\.hh\:mm");\r
636                 if (!entry.Timer.NeedRing)\r
637                     continue;\r
638                 _noticeQueue.Enqueue("遠征が終わりました", entry.Name, _config.MissionSoundFile);\r
639                 entry.Timer.NeedRing = false;\r
640             }\r
641             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
642             {\r
643                 var entry = _sniffer.NDock[i];\r
644                 entry.Timer.Update();\r
645                 _shipLabels.SetNDockTimer(i, entry.Timer);\r
646                 if (!entry.Timer.NeedRing)\r
647                     continue;\r
648                 _noticeQueue.Enqueue("入渠が終わりました", entry.Name, _config.NDockSoundFile);\r
649                 entry.Timer.NeedRing = false;\r
650             }\r
651             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
652             for (var i = 0; i < kdock.Length; i++)\r
653             {\r
654                 var timer = _sniffer.KDock[i];\r
655                 timer.Update();\r
656                 SetTimerColor(kdock[i], timer);\r
657                 kdock[i].Text = timer.Rest.ToString(@"hh\:mm\:ss");\r
658                 if (!timer.NeedRing)\r
659                     continue;\r
660                 _noticeQueue.Enqueue("建造が終わりました", $"第{i + 1:D}ドック", _config.KDockSoundFile);\r
661                 timer.NeedRing = false;\r
662             }\r
663             UpdateCondTimers();\r
664             UpdateAkashiTimer();\r
665         }\r
666 \r
667         private void SetTimerColor(Label label, RingTimer timer)\r
668         {\r
669             label.ForeColor = timer.IsFinished ? Color.Red : Color.Black;\r
670         }\r
671 \r
672         private void UpdateCondTimers()\r
673         {\r
674             var timer = _sniffer.GetConditionTimer(_currentFleet);\r
675             var now = DateTime.Now;\r
676             if (timer == DateTime.MinValue)\r
677             {\r
678                 labelCondTimerTitle.Text = "";\r
679                 labelCondTimer.Text = "";\r
680                 return;\r
681             }\r
682             var span = TimeSpan.FromSeconds(Ceiling((timer - now).TotalSeconds));\r
683             if (span >= TimeSpan.FromMinutes(9))\r
684             {\r
685                 labelCondTimerTitle.Text = "cond40まで";\r
686                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
687             }\r
688             else\r
689             {\r
690                 labelCondTimerTitle.Text = "cond49まで";\r
691                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
692             }\r
693             var notice = _sniffer.GetConditionNotice();\r
694             if (notice == null)\r
695                 return;\r
696             var fn = new[] {"第一艦隊", "第二艦隊", "第三艦隊", "第四艦隊"};\r
697             for (var i = 0; i < fn.Length; i++)\r
698             {\r
699                 if (!_config.NotifyConditions.Contains(notice[i]))\r
700                     return;\r
701                 _noticeQueue.Enqueue("疲労が回復しました", fn[i] + " cond" + notice[i].ToString("D"), _config.ConditionSoundFile);\r
702             }\r
703         }\r
704 \r
705         private void UpdateAkashiTimer()\r
706         {\r
707             _shipLabels.SetAkashiTimer(_sniffer.GetShipStatuses(_currentFleet),\r
708                 _sniffer.GetAkashiTimers(_currentFleet));\r
709             NotifyAkashiTimer();\r
710         }\r
711 \r
712         private void UpdateDamagedShipList()\r
713         {\r
714             _shipLabels.SetDamagedShipList(_sniffer.DamagedShipList);\r
715         }\r
716 \r
717         private void UpdateQuestList()\r
718         {\r
719             var category = new[]\r
720             {\r
721                 labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
722                 labelQuestColor6\r
723             };\r
724             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
725             var progress = new[]\r
726             {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
727             var color = new[]\r
728             {\r
729                 Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),\r
730                 Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),\r
731                 Color.FromArgb(200, 148, 231)\r
732             };\r
733 \r
734             var quests = _sniffer.Quests;\r
735             for (var i = 0; i < name.Length; i++)\r
736             {\r
737                 if (i < quests.Length)\r
738                 {\r
739                     category[i].BackColor = color[quests[i].Category - 1];\r
740                     name[i].Text = quests[i].Name;\r
741                     progress[i].Text = $"{quests[i].Progress:D}%";\r
742                 }\r
743                 else\r
744                 {\r
745                     category[i].BackColor = DefaultBackColor;\r
746                     name[i].Text = progress[i].Text = "";\r
747                 }\r
748             }\r
749         }\r
750 \r
751         private class NoticeQueue\r
752         {\r
753             private readonly Action<string, string, string> _ring;\r
754             private readonly Queue<Tuple<string, string, string>> _queue = new Queue<Tuple<string, string, string>>();\r
755             private readonly Timer _timer = new Timer {Interval = 2000};\r
756 \r
757             public NoticeQueue(Action<string, string, string> ring)\r
758             {\r
759                 _ring = ring;\r
760                 _timer.Tick += TimerOnTick;\r
761             }\r
762 \r
763             private void TimerOnTick(object obj, EventArgs e)\r
764             {\r
765                 if (_queue.Count == 0)\r
766                 {\r
767                     _timer.Stop();\r
768                     return;\r
769                 }\r
770                 var notice = _queue.Dequeue();\r
771                 _ring(notice.Item1, notice.Item2, notice.Item3);\r
772             }\r
773 \r
774             public void Enqueue(string title, string message, string soundFile)\r
775             {\r
776                 if (_timer.Enabled)\r
777                 {\r
778                     _queue.Enqueue(new Tuple<string, string, string>(title, message, soundFile));\r
779                 }\r
780                 else\r
781                 {\r
782                     _ring(title, message, soundFile);\r
783                     _timer.Start();\r
784                 }\r
785             }\r
786         }\r
787 \r
788         private void Ring(string baloonTitle, string baloonMessage, string soundFile)\r
789         {\r
790             if (_config.FlashWindow)\r
791                 Win32API.FlashWindow(Handle);\r
792             if (_config.ShowBaloonTip)\r
793                 notifyIconMain.ShowBalloonTip(20000, baloonTitle, baloonMessage, ToolTipIcon.Info);\r
794             if (_config.PlaySound)\r
795                 PlaySound(soundFile, _config.SoundVolume);\r
796         }\r
797 \r
798         [DllImport("winmm.dll")]\r
799         private static extern int mciSendString(String command,\r
800             StringBuilder buffer, int bufferSize, IntPtr hwndCallback);\r
801 \r
802 // ReSharper disable InconsistentNaming\r
803         private const int MM_MCINOTIFY = 0x3B9;\r
804         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
805 // ReSharper restore InconsistentNaming\r
806 \r
807         public void PlaySound(string file, int volume)\r
808         {\r
809             if (!File.Exists(file))\r
810                 return;\r
811             mciSendString("close sound", null, 0, IntPtr.Zero);\r
812             if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
813                 return;\r
814             mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
815             mciSendString("play sound notify", null, 0, Handle);\r
816         }\r
817 \r
818         protected override void WndProc(ref Message m)\r
819         {\r
820             if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
821                 mciSendString("close sound", null, 0, IntPtr.Zero);\r
822             base.WndProc(ref m);\r
823         }\r
824 \r
825         private void SetupFleetClick()\r
826         {\r
827             var labels = new[]\r
828             {\r
829                 new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
830                 new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
831                 new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
832             };\r
833             foreach (var a in labels)\r
834             {\r
835                 for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
836                 {\r
837                     a[fleet].Tag = fleet;\r
838                     a[fleet].Click += labelFleet_Click;\r
839                 }\r
840             }\r
841         }\r
842 \r
843         private void labelFleet_Click(object sender, EventArgs e)\r
844         {\r
845             var fleet = (int)((Label)sender).Tag;\r
846             if (_currentFleet == fleet)\r
847                 return;\r
848             _currentFleet = fleet;\r
849             foreach (var label in _labelCheckFleets)\r
850                 label.Visible = false;\r
851             _labelCheckFleets[fleet].Visible = true;\r
852             if (!_started)\r
853                 return;\r
854             UpdatePanelShipInfo();\r
855         }\r
856 \r
857         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
858         {\r
859             if (labelBucketHistory.Visible)\r
860             {\r
861                 labelBucketHistory.Visible = false;\r
862                 labelBucketHistoryButton.BackColor = DefaultBackColor;\r
863             }\r
864             else\r
865             {\r
866                 labelBucketHistory.Visible = true;\r
867                 labelBucketHistory.BringToFront();\r
868                 labelBucketHistoryButton.BackColor = SystemColors.ActiveCaption;\r
869             }\r
870         }\r
871 \r
872         private void labelBucketHistory_Click(object sender, EventArgs e)\r
873         {\r
874             labelBucketHistory.Visible = false;\r
875             labelBucketHistoryButton.BackColor = DefaultBackColor;\r
876         }\r
877 \r
878         private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
879         {\r
880             if (panelMaterialHistory.Visible)\r
881             {\r
882                 panelMaterialHistory.Visible = false;\r
883                 labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
884             }\r
885             else\r
886             {\r
887                 panelMaterialHistory.Visible = true;\r
888                 panelMaterialHistory.BringToFront();\r
889                 labelMaterialHistoryButton.BackColor = SystemColors.ActiveCaption;\r
890             }\r
891         }\r
892 \r
893         private void panelMaterialHistory_Click(object sender, EventArgs e)\r
894         {\r
895             panelMaterialHistory.Visible = false;\r
896             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
897         }\r
898 \r
899         public void ResetAchievemnt()\r
900         {\r
901             _sniffer.Achievement.Reset();\r
902             UpdateItemInfo();\r
903         }\r
904 \r
905         private void labelDamgedShipListButton_Click(object sender, EventArgs e)\r
906         {\r
907             if (panelDamagedShipList.Visible)\r
908             {\r
909                 panelDamagedShipList.Visible = false;\r
910                 labelDamgedShipListButton.BackColor = DefaultBackColor;\r
911             }\r
912             else\r
913             {\r
914                 panelDamagedShipList.Visible = true;\r
915                 panelDamagedShipList.BringToFront();\r
916                 labelDamgedShipListButton.BackColor = SystemColors.ActiveCaption;\r
917             }\r
918         }\r
919 \r
920         private void panelDamagedShipList_Click(object sender, EventArgs e)\r
921         {\r
922             panelDamagedShipList.Visible = false;\r
923             labelDamgedShipListButton.BackColor = DefaultBackColor;\r
924         }\r
925 \r
926         private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
927         {\r
928             _shipListForm.UpdateList();\r
929             _shipListForm.Show();\r
930             if (_shipListForm.WindowState == FormWindowState.Minimized)\r
931                 _shipListForm.WindowState = FormWindowState.Normal;\r
932             _shipListForm.Activate();\r
933         }\r
934 \r
935         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
936         {\r
937             Process.Start("http://localhost:" + _config.Log.Listen + "/");\r
938         }\r
939 \r
940         private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
941         {\r
942             try\r
943             {\r
944                 var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
945                 Process.Start(proc);\r
946             }\r
947             catch (FileNotFoundException)\r
948             {\r
949             }\r
950         }\r
951     }\r
952 }