OSDN Git Service

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