OSDN Git Service

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