OSDN Git Service

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