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