OSDN Git Service

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