OSDN Git Service

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