OSDN Git Service

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