OSDN Git Service

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