OSDN Git Service

ReSharperの指摘に従いtypoなどを直す
[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.ComponentModel;\r
18 using System.Diagnostics;\r
19 using System.Drawing;\r
20 using System.Globalization;\r
21 using System.IO;\r
22 using System.Linq;\r
23 using System.Net;\r
24 using System.Runtime.InteropServices;\r
25 using System.Text;\r
26 using System.Text.RegularExpressions;\r
27 using System.Threading.Tasks;\r
28 using System.Windows.Forms;\r
29 using KancolleSniffer.Model;\r
30 using KancolleSniffer.Net;\r
31 using KancolleSniffer.Util;\r
32 using KancolleSniffer.View;\r
33 using Microsoft.CSharp.RuntimeBinder;\r
34 using static System.Math;\r
35 \r
36 namespace KancolleSniffer\r
37 {\r
38     public partial class MainForm : Form\r
39     {\r
40         private readonly Sniffer _sniffer = new Sniffer();\r
41         private readonly Config _config = new Config();\r
42         private readonly ConfigDialog _configDialog;\r
43         private readonly ProxyManager _proxyManager;\r
44         private readonly ResizableToolTip _toolTip = new ResizableToolTip {ShowAlways = true};\r
45         private readonly ResizableToolTip _toolTipQuest = new ResizableToolTip {ShowAlways = true, AutoPopDelay = 10000};\r
46         private readonly ResizableToolTip _tooltipCopy = new ResizableToolTip {AutomaticDelay = 0};\r
47         private int _currentFleet;\r
48         private bool _combinedFleet;\r
49         private readonly Label[] _labelCheckFleets;\r
50         private readonly MainFormLabels _mainLabels;\r
51         private readonly ListForm _listForm;\r
52         private readonly NotificationManager _notificationManager;\r
53         private bool _started;\r
54         private bool _timerEnabled;\r
55         private string _debugLogFile;\r
56         private IEnumerator<string> _playLog;\r
57         private DateTime _prev, _now;\r
58         private bool _inSortie;\r
59 \r
60         private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
61         private readonly ErrorLog _errorLog;\r
62 \r
63         public MainForm()\r
64         {\r
65             InitializeComponent();\r
66             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
67             _configDialog = new ConfigDialog(_config, this);\r
68             _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
69 \r
70             // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
71             // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
72             ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
73                 CurrentAutoScaleDimensions.Height / 12f);\r
74 \r
75             SetupFleetClick();\r
76             _mainLabels = new MainFormLabels();\r
77             _mainLabels.CreateAkashiTimers(panelShipInfo);\r
78             _mainLabels.CreateShipLabels(panelShipInfo, ShowShipOnShipList);\r
79             _mainLabels.CreateAkashiTimers7(panel7Ships);\r
80             _mainLabels.CreateShipLabels7(panel7Ships, ShowShipOnShipList);\r
81             _mainLabels.CreateCombinedShipLabels(panelCombinedFleet, ShowShipOnShipList);\r
82             _mainLabels.CreateNDockLabels(panelDock, labelNDock_Click);\r
83             panelRepairList.CreateLabels(panelRepairList_Click);\r
84             labelPresetAkashiTimer.BackColor = ShipLabel.ColumnColors[1];\r
85             _listForm = new ListForm(_sniffer, _config) {Owner = this};\r
86             _notificationManager = new NotificationManager(Alarm);\r
87             _config.Load();\r
88             _proxyManager = new ProxyManager(_config, this);\r
89             _errorLog = new ErrorLog(_sniffer);\r
90             _proxyManager.UpdatePacFile();\r
91             PerformZoom();\r
92             _mainLabels.AdjustAkashiTimers();\r
93             LoadData();\r
94             _sniffer.RepeatingTimerController = new RepeatingTimerController(_notificationManager, _config);\r
95         }\r
96 \r
97         /// <summary>\r
98         /// パネルのz-orderがくるうのを避ける\r
99         /// https://stackoverflow.com/a/5777090/1429506\r
100         /// </summary>\r
101         private void MainForm_Shown(object sender, EventArgs e)\r
102         {\r
103             // ReSharper disable once NotAccessedVariable\r
104             IntPtr handle;\r
105             foreach (var panel in new[] {panelShipInfo, panel7Ships, panelCombinedFleet})\r
106                 // ReSharper disable once RedundantAssignment\r
107                 handle = panel.Handle;\r
108         }\r
109 \r
110         private readonly FileSystemWatcher _watcher = new FileSystemWatcher\r
111         {\r
112             Path = AppDomain.CurrentDomain.BaseDirectory,\r
113             NotifyFilter = NotifyFilters.LastWrite\r
114         };\r
115 \r
116         private readonly Timer _watcherTimer = new Timer {Interval = 1000};\r
117 \r
118         private void LoadData()\r
119         {\r
120             var target = "";\r
121             _sniffer.LoadState();\r
122             _watcher.SynchronizingObject = this;\r
123             _watcherTimer.Tick += (sender, ev) =>\r
124             {\r
125                 _watcherTimer.Stop();\r
126                 switch (target)\r
127                 {\r
128                     case "status.xml":\r
129                         _sniffer.LoadState();\r
130                         break;\r
131                     case "TP.csv":\r
132                         _sniffer.AdditionalData.LoadTpSpec();\r
133                         break;\r
134                 }\r
135             };\r
136             _watcher.Changed += (sender, ev) =>\r
137             {\r
138                 target = ev.Name;\r
139                 _watcherTimer.Stop();\r
140                 _watcherTimer.Start();\r
141             };\r
142             _watcher.EnableRaisingEvents = true;\r
143         }\r
144 \r
145         private class RepeatingTimerController : Sniffer.IRepeatingTimerController\r
146         {\r
147             private readonly NotificationManager _manager;\r
148             private readonly Config _config;\r
149 \r
150             public RepeatingTimerController(NotificationManager manager, Config config)\r
151             {\r
152                 _manager = manager;\r
153                 _config = config;\r
154             }\r
155 \r
156             public void Stop(string key)\r
157             {\r
158                 _manager.StopRepeat(key,\r
159                     (key == "入渠終了" || key == "遠征終了") &&\r
160                     (_config.Notifications[key].Flags & NotificationType.Cont) != 0);\r
161             }\r
162 \r
163             public void Stop(string key, int fleet) => _manager.StopRepeat(key, fleet);\r
164 \r
165             public void Suspend(string exception = null) => _manager.SuspendRepeat(exception);\r
166 \r
167             public void Resume() => _manager.ResumeRepeat();\r
168         }\r
169 \r
170         private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
171         {\r
172             BeginInvoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
173         }\r
174 \r
175         private void ProcessRequest(HttpProxy.Session session)\r
176         {\r
177             var url = session.Request.PathAndQuery;\r
178             if (!url.Contains("kcsapi/"))\r
179                 return;\r
180             var request = session.Request.BodyAsString;\r
181             var response = session.Response.BodyAsString;\r
182             if (response == null || !response.StartsWith("svdata="))\r
183             {\r
184                 WriteDebugLog(url, request, response);\r
185                 return;\r
186             }\r
187             response = UnEscapeString(response.Remove(0, "svdata=".Length));\r
188             WriteDebugLog(url, request, response);\r
189             ProcessRequestMain(url, request, response);\r
190         }\r
191 \r
192         private void ProcessRequestMain(string url, string request, string response)\r
193         {\r
194             try\r
195             {\r
196                 UpdateInfo(_sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
197                 _errorLog.CheckBattleApi(url, request, response);\r
198             }\r
199 \r
200             catch (RuntimeBinderException e)\r
201             {\r
202                 if (_errorDialog.ShowDialog(this,\r
203                         "艦これに仕様変更があったか、受信内容が壊れています。",\r
204                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
205                     Exit();\r
206             }\r
207             catch (LogIOException e)\r
208             {\r
209                 // ReSharper disable once PossibleNullReferenceException\r
210                 if (_errorDialog.ShowDialog(this, e.Message, e.InnerException.ToString()) == DialogResult.Abort)\r
211                     Exit();\r
212             }\r
213             catch (BattleResultError)\r
214             {\r
215                 if (_errorDialog.ShowDialog(this, "戦闘結果の計算に誤りがあります。",\r
216                         _errorLog.GenerateBattleErrorLog()) == DialogResult.Abort)\r
217                     Exit();\r
218             }\r
219             catch (Exception e)\r
220             {\r
221                 if (_errorDialog.ShowDialog(this, "エラーが発生しました。",\r
222                         _errorLog.GenerateErrorLog(url, request, response, e.ToString())) == DialogResult.Abort)\r
223                     Exit();\r
224             }\r
225         }\r
226 \r
227         private void Exit()\r
228         {\r
229             _proxyManager.Shutdown();\r
230             Environment.Exit(1);\r
231         }\r
232 \r
233         private void WriteDebugLog(string url, string request, string response)\r
234         {\r
235             if (_debugLogFile != null)\r
236             {\r
237                 File.AppendAllText(_debugLogFile,\r
238                     $"date: {DateTime.Now:g}\nurl: {url}\nrequest: {request}\nresponse: {response ?? "(null)"}\n");\r
239             }\r
240         }\r
241 \r
242         private string UnEscapeString(string s)\r
243         {\r
244             try\r
245             {\r
246                 var rx = new Regex(@"\\[uU]([0-9A-Fa-f]{4})");\r
247                 return rx.Replace(s,\r
248                     match => ((char)int.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString());\r
249             }\r
250             catch (ArgumentException)\r
251             {\r
252                 return s;\r
253             }\r
254         }\r
255 \r
256         private void UpdateInfo(Sniffer.Update update)\r
257         {\r
258             if (update == Sniffer.Update.Start)\r
259             {\r
260                 labelLogin.Visible = false;\r
261                 linkLabelGuide.Visible = false;\r
262                 _started = true;\r
263                 return;\r
264             }\r
265             if (!_started)\r
266                 return;\r
267             if (_now == DateTime.MinValue)\r
268                 _now = DateTime.Now;\r
269             if ((update & Sniffer.Update.Item) != 0)\r
270                 UpdateItemInfo();\r
271             if ((update & Sniffer.Update.Timer) != 0)\r
272                 UpdateTimers();\r
273             if ((update & Sniffer.Update.NDock) != 0)\r
274                 UpdateNDocLabels();\r
275             if ((update & Sniffer.Update.Mission) != 0)\r
276                 UpdateMissionLabels();\r
277             if ((update & Sniffer.Update.QuestList) != 0)\r
278                 UpdateQuestList();\r
279             if ((update & Sniffer.Update.Ship) != 0)\r
280                 UpdateShipInfo();\r
281             if ((update & Sniffer.Update.Battle) != 0)\r
282                 UpdateBattleInfo();\r
283             if ((update & Sniffer.Update.Cell) != 0)\r
284                 UpdateCellInfo();\r
285         }\r
286 \r
287         private void MainForm_Load(object sender, EventArgs e)\r
288         {\r
289             RestoreLocation();\r
290             if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
291                 ShowInTaskbar = false;\r
292             if (_config.ShowHpInPercent)\r
293                 _mainLabels.ToggleHpPercent();\r
294             if (_config.ShipList.Visible)\r
295                 _listForm.Show();\r
296             ApplyConfig();\r
297             ApplyDebugLogSetting();\r
298             ApplyLogSetting();\r
299             ApplyProxySetting();\r
300             CheckVersionUp((current, latest) =>\r
301             {\r
302                 if (latest == current)\r
303                     return;\r
304                 linkLabelGuide.Text = $"バージョン{latest}があります。";\r
305                 linkLabelGuide.LinkArea = new LinkArea(0, linkLabelGuide.Text.Length);\r
306                 linkLabelGuide.Click += (obj, ev) =>\r
307                 {\r
308                     Process.Start("https://ja.osdn.net/rel/kancollesniffer/" + latest);\r
309                 };\r
310             });\r
311         }\r
312 \r
313         public async void CheckVersionUp(Action<string, string> action)\r
314         {\r
315             var current = string.Join(".", Application.ProductVersion.Split('.').Take(2));\r
316             try\r
317             {\r
318                 var latest = (await new WebClient().DownloadStringTaskAsync("http://kancollesniffer.osdn.jp/version"))\r
319                     .TrimEnd();\r
320                 try\r
321                 {\r
322                     action(current, latest);\r
323                 }\r
324                 catch (InvalidOperationException)\r
325                 {\r
326                 }\r
327             }\r
328             catch (WebException)\r
329             {\r
330             }\r
331         }\r
332 \r
333         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
334         {\r
335             if (!_config.ExitSilently)\r
336             {\r
337                 using (var dialog = new ConfirmDialog())\r
338                 {\r
339                     if (dialog.ShowDialog(this) != DialogResult.Yes)\r
340                     {\r
341                         e.Cancel = true;\r
342                         return;\r
343                     }\r
344                 }\r
345             }\r
346             e.Cancel = false;\r
347             _sniffer.FlashLog();\r
348             _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
349             _config.ShowHpInPercent = _mainLabels.ShowHpInPercent;\r
350             _config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
351             _config.Save();\r
352             _sniffer.SaveState();\r
353             _proxyManager.Shutdown();\r
354         }\r
355 \r
356         private void MainForm_Resize(object sender, EventArgs e)\r
357         {\r
358             ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
359         }\r
360 \r
361         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
362         {\r
363             NotifyIconOpenToolStripMenuItem_Click(sender, e);\r
364         }\r
365 \r
366         private void NotifyIconOpenToolStripMenuItem_Click(object sender, EventArgs e)\r
367         {\r
368             ShowInTaskbar = true;\r
369             WindowState = FormWindowState.Normal;\r
370             TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
371             Activate();\r
372         }\r
373 \r
374         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
375         {\r
376             Close();\r
377         }\r
378 \r
379         private void ConfigToolStripMenuItem_Click(object sender, EventArgs e)\r
380         {\r
381             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
382             {\r
383                 _config.Save();\r
384                 ApplyConfig();\r
385                 StopRepeatingTimer(_configDialog.RepeatSettingsChanged);\r
386             }\r
387         }\r
388 \r
389         private void StopRepeatingTimer(IEnumerable<string> names)\r
390         {\r
391             foreach (var name in names)\r
392                 _notificationManager.StopRepeat(name);\r
393         }\r
394 \r
395         private void PerformZoom()\r
396         {\r
397             if (_config.Zoom == 100)\r
398                 return;\r
399             var prev = CurrentAutoScaleDimensions;\r
400             foreach (var control in new Control[]\r
401             {\r
402                 this, _listForm, labelLogin, linkLabelGuide,\r
403                 _configDialog, _configDialog.NotificationConfigDialog,\r
404                 contextMenuStripMain, _errorDialog\r
405             })\r
406             {\r
407                 control.Font = new Font(control.Font.FontFamily, control.Font.Size * _config.Zoom / 100);\r
408             }\r
409             foreach (var toolTip in new[]{_toolTip, _toolTipQuest, _tooltipCopy})\r
410             {\r
411                 toolTip.Font = new Font(toolTip.Font.FontFamily, toolTip.Font.Size * _config.Zoom / 100);\r
412             }\r
413             ShipLabel.LatinFont = new Font("Tahoma", 8f * _config.Zoom / 100);\r
414             var cur = CurrentAutoScaleDimensions;\r
415             ShipLabel.ScaleFactor = new SizeF(ShipLabel.ScaleFactor.Width * cur.Width / prev.Width,\r
416                 ShipLabel.ScaleFactor.Height * cur.Height / prev.Height);\r
417         }\r
418 \r
419         private void RestoreLocation()\r
420         {\r
421             if (_config.Location.X == int.MinValue)\r
422                 return;\r
423             if (IsTitleBarOnAnyScreen(_config.Location))\r
424                 Location = _config.Location;\r
425         }\r
426 \r
427         private void ApplyConfig()\r
428         {\r
429             _listForm.TopMost = TopMost = _config.TopMost;\r
430             _sniffer.ShipCounter.Margin = _config.MarginShips;\r
431             UpdateNumOfShips();\r
432             _sniffer.ItemCounter.Margin = _config.MarginEquips;\r
433             UpdateNumOfEquips();\r
434             _sniffer.Achievement.ResetHours = _config.ResetHours;\r
435             labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
436                 labelPresetAkashiTimer.Visible = _config.UsePresetAkashi;\r
437         }\r
438 \r
439         public void ApplyDebugLogSetting()\r
440         {\r
441             _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
442         }\r
443 \r
444         public bool ApplyProxySetting()\r
445         {\r
446             return _proxyManager.ApplyConfig();\r
447         }\r
448 \r
449         public void ApplyLogSetting()\r
450         {\r
451             LogServer.OutputDir = _config.Log.OutputDir;\r
452             LogServer.MaterialHistory = _sniffer.Material.MaterialHistory;\r
453             _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
454             _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
455             _sniffer.LogOutputDir = _config.Log.OutputDir;\r
456         }\r
457 \r
458         public static bool IsTitleBarOnAnyScreen(Point location)\r
459         {\r
460             var rect = new Rectangle(\r
461                 new Point(location.X + SystemInformation.IconSize.Width + SystemInformation.HorizontalFocusThickness,\r
462                     location.Y + SystemInformation.CaptionHeight), new Size(60, 1));\r
463             return Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect));\r
464         }\r
465 \r
466         private void timerMain_Tick(object sender, EventArgs e)\r
467         {\r
468             if (_timerEnabled)\r
469             {\r
470                 try\r
471                 {\r
472                     _now = DateTime.Now;\r
473                     UpdateTimers();\r
474                     NotifyTimers();\r
475                     _prev = _now;\r
476                 }\r
477                 catch (Exception ex)\r
478                 {\r
479                     if (_errorDialog.ShowDialog(this, "エラーが発生しました。", ex.ToString()) == DialogResult.Abort)\r
480                         Exit();\r
481                 }\r
482             }\r
483             if (_playLog == null || _configDialog.Visible)\r
484             {\r
485                 labelPlayLog.Visible = false;\r
486                 return;\r
487             }\r
488             PlayLog();\r
489         }\r
490 \r
491         public void SetPlayLog(string file)\r
492         {\r
493             _playLog = File.ReadLines(file).GetEnumerator();\r
494         }\r
495 \r
496         private void PlayLog()\r
497         {\r
498             var lines = new List<string>();\r
499             foreach (var s in new[] {"url: ", "request: ", "response: "})\r
500             {\r
501                 do\r
502                 {\r
503                     if (!_playLog.MoveNext() || _playLog.Current == null)\r
504                     {\r
505                         labelPlayLog.Visible = false;\r
506                         return;\r
507                     }\r
508                 } while (!_playLog.Current.StartsWith(s));\r
509                 lines.Add(_playLog.Current.Substring(s.Length));\r
510             }\r
511             labelPlayLog.Visible = !labelPlayLog.Visible;\r
512             ProcessRequestMain(lines[0], lines[1], lines[2]);\r
513         }\r
514 \r
515         private void ShowShipOnShipList(object sender, EventArgs ev)\r
516         {\r
517             if (!_listForm.Visible)\r
518                 return;\r
519             var idx = (int)((Control)sender).Tag;\r
520             var ships = _sniffer.Fleets[_currentFleet].ActualShips;\r
521             if (ships.Count <= idx)\r
522                 return;\r
523             _listForm.ShowShip(ships[idx].Id);\r
524         }\r
525 \r
526         private void UpdateItemInfo()\r
527         {\r
528             UpdateNumOfShips();\r
529             UpdateNumOfEquips();\r
530             _notificationManager.Flash();\r
531             labelNumOfBuckets.Text = _sniffer.Material.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
532             UpdateBucketHistory();\r
533             var ac = _sniffer.Achievement.Value;\r
534             if (ac >= 10000)\r
535                 ac = 9999;\r
536             labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
537             _toolTip.SetToolTip(labelAchievement,\r
538                 "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
539                 "EO " + _sniffer.ExMap.Achievement);\r
540             UpdateMaterialHistory();\r
541             if (_listForm.Visible)\r
542                 _listForm.UpdateList();\r
543         }\r
544 \r
545         private void UpdateNumOfShips()\r
546         {\r
547             var ship = _sniffer.ShipCounter;\r
548             labelNumOfShips.Text = $"{ship.Now:D}/{ship.Max:D}";\r
549             labelNumOfShips.ForeColor = ship.TooMany ? CUDColor.Red : Color.Black;\r
550             if (ship.Alarm)\r
551             {\r
552                 var message = $"残り{ship.Rest:D}隻";\r
553                 _notificationManager.Enqueue("艦娘数超過", message);\r
554                 ship.Alarm = false;\r
555             }\r
556         }\r
557 \r
558         private void UpdateNumOfEquips()\r
559         {\r
560             var item = _sniffer.ItemCounter;\r
561             labelNumOfEquips.Text = $"{item.Now:D}/{item.Max:D}";\r
562             labelNumOfEquips.ForeColor = item.TooMany ? CUDColor.Red : Color.Black;\r
563             if (item.Alarm)\r
564             {\r
565                 var message = $"残り{item.Rest:D}個";\r
566                 _notificationManager.Enqueue("装備数超過", message);\r
567                 item.Alarm = false;\r
568             }\r
569         }\r
570 \r
571         private void UpdateBucketHistory()\r
572         {\r
573             var count = _sniffer.Material.MaterialHistory[(int)Material.Bucket];\r
574             var day = CutOverflow(count.Now - count.BegOfDay, 999);\r
575             var week = CutOverflow(count.Now - count.BegOfWeek, 999);\r
576             labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
577         }\r
578 \r
579         private void UpdateMaterialHistory()\r
580         {\r
581             var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
582             var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
583             for (var i = 0; i < labels.Length; i++)\r
584             {\r
585                 var count = _sniffer.Material.MaterialHistory[i];\r
586                 var port = CutOverflow(count.Now - _sniffer.Material.PrevPort[i], 99999);\r
587                 var day = CutOverflow(count.Now - count.BegOfDay, 99999);\r
588                 var week = CutOverflow(count.Now - count.BegOfWeek, 99999);\r
589                 labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
590             }\r
591         }\r
592 \r
593         private int CutOverflow(int value, int limit)\r
594         {\r
595             if (value > limit)\r
596                 return limit;\r
597             if (value < -limit)\r
598                 return -limit;\r
599             return value;\r
600         }\r
601 \r
602         private void UpdateShipInfo()\r
603         {\r
604             SetCurrentFleet();\r
605             UpdatePanelShipInfo();\r
606             NotifyDamagedShip();\r
607             UpdateChargeInfo();\r
608             UpdateRepairList();\r
609             UpdateMissionLabels();\r
610             if (_listForm.Visible)\r
611                 _listForm.UpdateList();\r
612         }\r
613 \r
614         private void SetCurrentFleet()\r
615         {\r
616             var states = _sniffer.Fleets.Select(fleet => fleet.State).ToArray();\r
617             var inSortie = states.Any(state => state >= FleetState.Sortie);\r
618             if (_inSortie || !inSortie)\r
619             {\r
620                 _inSortie = inSortie;\r
621                 return;\r
622             }\r
623             _inSortie = true;\r
624             if (states[0] == FleetState.Sortie && states[1] == FleetState.Sortie)\r
625             {\r
626                 _combinedFleet = true;\r
627                 _currentFleet = 0;\r
628             }\r
629             else\r
630             {\r
631                 _combinedFleet = false;\r
632                 _currentFleet = Array.FindIndex(states, state => state >= FleetState.Sortie);\r
633             }\r
634         }\r
635 \r
636         private void UpdatePanelShipInfo()\r
637         {\r
638             var fleets = _sniffer.Fleets;\r
639             var ships = fleets[_currentFleet].ActualShips;\r
640             panel7Ships.Visible = ships.Count == 7;\r
641             _mainLabels.SetShipLabels(ships);\r
642             if (!_sniffer.IsCombinedFleet)\r
643                 _combinedFleet = false;\r
644             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
645             panelCombinedFleet.Visible = _combinedFleet;\r
646             if (_combinedFleet)\r
647                 _mainLabels.SetCombinedShipLabels(fleets[0].ActualShips, fleets[1].ActualShips);\r
648             for (var i = 0; i < _labelCheckFleets.Length; i++)\r
649                 _labelCheckFleets[i].Visible = _currentFleet == i;\r
650             UpdateAkashiTimer();\r
651             var battle = _sniffer.Battle;\r
652             UpdateFighterPower(_combinedFleet && (battle.BattleState == BattleState.None || battle.EnemyIsCombined));\r
653             UpdateLoS();\r
654             UpdateCondTimers();\r
655         }\r
656 \r
657         private void NotifyDamagedShip()\r
658         {\r
659             if (!_sniffer.BadlyDamagedShips.Any())\r
660                 return;\r
661             _notificationManager.StopRepeat("大破警告");\r
662             SetNotification("大破警告", string.Join(" ", _sniffer.BadlyDamagedShips));\r
663             _notificationManager.Flash();\r
664         }\r
665 \r
666         public void UpdateFighterPower(bool combined)\r
667         {\r
668             var fleets = _sniffer.Fleets;\r
669             var fp = combined\r
670                 ? fleets[0].FighterPower.Zip(fleets[1].FighterPower, (a, b) => a + b).ToArray()\r
671                 : fleets[_currentFleet].FighterPower;\r
672             labelFighterPower.Text = fp[0].ToString("D");\r
673             var cr = combined\r
674                 ? fleets[0].ContactTriggerRate + fleets[1].ContactTriggerRate\r
675                 : fleets[_currentFleet].ContactTriggerRate;\r
676             var text = "制空: " + (fp[0] == fp[1] ? $"{fp[0]}" : $"{fp[0]}~{fp[1]}") +\r
677                        $" 触接: {cr * 100:f1}";\r
678             _toolTip.SetToolTip(labelFighterPower, text);\r
679             _toolTip.SetToolTip(labelFighterPowerCaption, text);\r
680         }\r
681 \r
682         private void UpdateLoS()\r
683         {\r
684             var fleet = _sniffer.Fleets[_currentFleet];\r
685             labelLoS.Text = RoundDown(fleet.GetLineOfSights(1)).ToString("F1");\r
686             var text = $"係数3: {RoundDown(fleet.GetLineOfSights(3)):F1}\r\n" +\r
687                        $"係数4: {RoundDown(fleet.GetLineOfSights(4)):F1}";\r
688             _toolTip.SetToolTip(labelLoS, text);\r
689             _toolTip.SetToolTip(labelLoSCaption, text);\r
690         }\r
691 \r
692         private double RoundDown(double number)\r
693         {\r
694             return Floor(number * 10) / 10.0;\r
695         }\r
696 \r
697         private void UpdateBattleInfo()\r
698         {\r
699             ResetBattleInfo();\r
700             _listForm.UpdateBattleResult();\r
701             if (_sniffer.Battle.BattleState == BattleState.None)\r
702                 return;\r
703             panelBattleInfo.BringToFront();\r
704             var battle = _sniffer.Battle;\r
705             labelFormation.Text = new[] {"同航戦", "反航戦", "T字有利", "T字不利"}[battle.Formation[2] - 1];\r
706             UpdateBattleFighterPower();\r
707             if ((_config.Spoilers & Spoiler.ResultRank) != 0)\r
708                 ShowResultRank();\r
709             if (_sniffer.Battle.BattleState == BattleState.Day)\r
710                 _listForm.UpdateAirBattleResult();\r
711         }\r
712 \r
713         private void UpdateCellInfo()\r
714         {\r
715             _listForm.UpdateCellInfo();\r
716         }\r
717 \r
718         private void ResetBattleInfo()\r
719         {\r
720             labelFormation.Text = "";\r
721             labelEnemyFighterPower.Text = "";\r
722             labelFighterPower.ForeColor = DefaultForeColor;\r
723             labelResultRank.Text = "判定";\r
724             panelBattleInfo.Visible = _sniffer.Battle.BattleState != BattleState.None;\r
725         }\r
726 \r
727         private void UpdateBattleFighterPower()\r
728         {\r
729             var battle = _sniffer.Battle;\r
730             var power = battle.EnemyFighterPower;\r
731             labelEnemyFighterPower.Text = power.AirCombat + power.UnknownMark;\r
732             if (power.AirCombat != power.Interception)\r
733             {\r
734                 var text = "防空: " + power.Interception + power.UnknownMark;\r
735                 _toolTip.SetToolTip(labelEnemyFighterPower, text);\r
736                 _toolTip.SetToolTip(labelEnemyFighterPowerCaption, text);\r
737             }\r
738             else\r
739             {\r
740                 _toolTip.SetToolTip(labelEnemyFighterPower, "");\r
741                 _toolTip.SetToolTip(labelEnemyFighterPowerCaption, "");\r
742             }\r
743             UpdateFighterPower(_sniffer.IsCombinedFleet && battle.EnemyIsCombined);\r
744             labelFighterPower.ForeColor = new[]\r
745                 {DefaultForeColor, DefaultForeColor, CUDColor.Blue, CUDColor.Green, CUDColor.Orange, CUDColor.Red}[\r
746                 battle.AirControlLevel + 1];\r
747         }\r
748 \r
749         private void ShowResultRank()\r
750         {\r
751             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
752             labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
753         }\r
754 \r
755         private void labelResultRank_Click(object sender, EventArgs e)\r
756         {\r
757             ShowResultRank();\r
758         }\r
759 \r
760         private void UpdateChargeInfo()\r
761         {\r
762             var fuelSq = new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4};\r
763             var bullSq = new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4};\r
764 \r
765             for (var i = 0; i < fuelSq.Length; i++)\r
766             {\r
767                 var stat = _sniffer.Fleets[i].ChargeStatus;\r
768                 fuelSq[i].ImageIndex = stat.Fuel;\r
769                 bullSq[i].ImageIndex = stat.Bull;\r
770             }\r
771         }\r
772 \r
773         private void UpdateNDocLabels()\r
774         {\r
775             _mainLabels.SetNDockLabels(_sniffer.NDock);\r
776         }\r
777 \r
778 \r
779         private void labelNDock_Click(object sender, EventArgs e)\r
780         {\r
781             _config.ShowEndTime ^= TimerKind.NDock;\r
782             UpdateTimers();\r
783         }\r
784 \r
785         private void UpdateMissionLabels()\r
786         {\r
787             var nameLabels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};\r
788             var paramsLabels = new[] {labelMissionParams1, labelMissionParams2, labelMissionParams3};\r
789             var names = _sniffer.Missions.Select(mission => mission.Name).ToArray();\r
790             for (var i = 0; i < ShipInfo.FleetCount - 1; i++)\r
791             {\r
792                 paramsLabels[i].Visible = false;\r
793                 if (string.IsNullOrEmpty(names[i]))\r
794                 {\r
795                     paramsLabels[i].Text = GenerateFleetParamsForMission(i + 1);\r
796                     paramsLabels[i].Visible = true;\r
797                 }\r
798                 nameLabels[i].Text = names[i];\r
799             }\r
800         }\r
801 \r
802         private string GenerateFleetParamsForMission(int fleetNumber)\r
803         {\r
804             var result = new List<string>();\r
805             var fleet = _sniffer.Fleets[fleetNumber];\r
806             var kira = fleet.Ships.Count(ship => ship.Cond > 49);\r
807             var plus = fleet.Ships[0].Cond > 49;\r
808             if (kira > 0)\r
809                 result.Add($"キラ{kira}{(plus ? "+" : "")}");\r
810             var drums = fleet.Ships.SelectMany(ship => ship.Slot).Count(item => item.Spec.IsDrum);\r
811             var drumShips = fleet.Ships.Count(ship => ship.Slot.Any(item => item.Spec.IsDrum));\r
812             if (drums > 0)\r
813                 result.Add($"ド{drums}({drumShips}隻)");\r
814             if (fleet.DaihatsuBonus > 0)\r
815                 result.Add($"ダ{fleet.DaihatsuBonus * 100:f1}%");\r
816             return string.Join(" ", result);\r
817         }\r
818 \r
819         private void labelMission_Click(object sender, EventArgs e)\r
820         {\r
821             _config.ShowEndTime ^= TimerKind.Mission;\r
822             UpdateTimers();\r
823         }\r
824 \r
825         private void UpdateTimers()\r
826         {\r
827             var mission = new[] {labelMission1, labelMission2, labelMission3};\r
828             for (var i = 0; i < mission.Length; i++)\r
829             {\r
830                 var entry = _sniffer.Missions[i];\r
831                 SetTimerColor(mission[i], entry.Timer, _now);\r
832                 mission[i].Text = entry.Timer.ToString(_now, (_config.ShowEndTime & TimerKind.Mission) != 0);\r
833             }\r
834             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
835             {\r
836                 var entry = _sniffer.NDock[i];\r
837                 _mainLabels.SetNDockTimer(i, entry.Timer, _now, (_config.ShowEndTime & TimerKind.NDock) != 0);\r
838             }\r
839             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
840             for (var i = 0; i < kdock.Length; i++)\r
841             {\r
842                 var timer = _sniffer.KDock[i];\r
843                 SetTimerColor(kdock[i], timer, _now);\r
844                 kdock[i].Text = timer.ToString(_now);\r
845             }\r
846             UpdateCondTimers();\r
847             UpdateAkashiTimer();\r
848             _timerEnabled = true;\r
849         }\r
850 \r
851         private void NotifyTimers()\r
852         {\r
853             for (var i = 0; i < _sniffer.Missions.Length; i++)\r
854             {\r
855                 var entry = _sniffer.Missions[i];\r
856                 CheckAlarm("遠征終了", entry.Timer, i + 1, entry.Name);\r
857             }\r
858             for (var i = 0; i < _sniffer.NDock.Length; i++)\r
859             {\r
860                 var entry = _sniffer.NDock[i];\r
861                 CheckAlarm("入渠終了", entry.Timer, i, entry.Name);\r
862             }\r
863             for (var i = 0; i < _sniffer.KDock.Length; i++)\r
864             {\r
865                 var timer = _sniffer.KDock[i];\r
866                 CheckAlarm("建造完了", timer, i, "");\r
867             }\r
868             NotifyCondTimers();\r
869             NotifyAkashiTimer();\r
870             _notificationManager.Flash();\r
871         }\r
872 \r
873         private void CheckAlarm(string key, AlarmTimer timer, int fleet, string subject)\r
874         {\r
875             if (timer.CheckAlarm(_prev, _now))\r
876             {\r
877                 SetNotification(key, fleet, subject);\r
878                 return;\r
879             }\r
880             var pre = TimeSpan.FromSeconds(_config.Notifications[key].PreliminaryPeriod);\r
881             if (pre == TimeSpan.Zero)\r
882                 return;\r
883             if (timer.CheckAlarm(_prev + pre, _now + pre))\r
884                 SetPreNotification(key, fleet, subject);\r
885         }\r
886 \r
887         private void SetTimerColor(Label label, AlarmTimer timer, DateTime now)\r
888         {\r
889             label.ForeColor = timer.IsFinished(now) ? CUDColor.Red : Color.Black;\r
890         }\r
891 \r
892         private void UpdateCondTimers()\r
893         {\r
894             DateTime timer;\r
895             if (_combinedFleet)\r
896             {\r
897                 var timer1 = _sniffer.GetConditionTimer(0);\r
898                 var timer2 = _sniffer.GetConditionTimer(1);\r
899                 timer = timer2 > timer1 ? timer2 : timer1;\r
900             }\r
901             else\r
902             {\r
903                 timer = _sniffer.GetConditionTimer(_currentFleet);\r
904             }\r
905             if (timer == DateTime.MinValue)\r
906             {\r
907                 labelCondTimerTitle.Text = "";\r
908                 labelCondTimer.Text = "";\r
909                 return;\r
910             }\r
911             var span = TimeSpan.FromSeconds(Ceiling((timer - _now).TotalSeconds));\r
912             if (span >= TimeSpan.FromMinutes(9))\r
913             {\r
914                 labelCondTimerTitle.Text = "cond40まで";\r
915                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
916                 labelCondTimer.ForeColor = DefaultForeColor;\r
917             }\r
918             else\r
919             {\r
920                 labelCondTimerTitle.Text = "cond49まで";\r
921                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
922                 labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColor.Red : DefaultForeColor;\r
923             }\r
924         }\r
925 \r
926         private void NotifyCondTimers()\r
927         {\r
928             var notice = _sniffer.GetConditionNotice(_prev, _now);\r
929             var pre = TimeSpan.FromSeconds(_config.Notifications["疲労回復"].PreliminaryPeriod);\r
930             var preNotice = pre == TimeSpan.Zero\r
931                 ? new int[ShipInfo.FleetCount]\r
932                 : _sniffer.GetConditionNotice(_prev + pre, _now + pre);\r
933             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
934             {\r
935                 if (_config.NotifyConditions.Contains(notice[i]))\r
936                 {\r
937                     SetNotification("疲労回復" + notice[i], i, "cond" + notice[i]);\r
938                 }\r
939                 else if (_config.NotifyConditions.Contains(preNotice[i]))\r
940                 {\r
941                     SetPreNotification("疲労回復" + preNotice[i], i, "cond" + notice[i]);\r
942                 }\r
943             }\r
944         }\r
945 \r
946         private void UpdateAkashiTimer()\r
947         {\r
948             if (_config.UsePresetAkashi)\r
949                 UpdatePresetAkashiTimer();\r
950             _mainLabels.SetAkashiTimer(_sniffer.Fleets[_currentFleet].ActualShips,\r
951                 _sniffer.AkashiTimer.GetTimers(_currentFleet, _now));\r
952         }\r
953 \r
954         private void UpdatePresetAkashiTimer()\r
955         {\r
956             var akashi = _sniffer.AkashiTimer;\r
957             var span = akashi.GetPresetDeckTimer(_now);\r
958             var color = span == TimeSpan.Zero && akashi.CheckPresetRepairing() ? CUDColor.Red : DefaultForeColor;\r
959             var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
960             labelAkashiRepairTimer.ForeColor = color;\r
961             labelAkashiRepairTimer.Text = text;\r
962             if (akashi.CheckPresetRepairing() && !akashi.CheckRepairing(_currentFleet, _now))\r
963             {\r
964                 labelPresetAkashiTimer.ForeColor = color;\r
965                 labelPresetAkashiTimer.Text = text;\r
966             }\r
967             else\r
968             {\r
969                 labelPresetAkashiTimer.ForeColor = DefaultForeColor;\r
970                 labelPresetAkashiTimer.Text = "";\r
971             }\r
972         }\r
973 \r
974         private void NotifyAkashiTimer()\r
975         {\r
976             var akashi = _sniffer.AkashiTimer;\r
977             var msgs = akashi.GetNotice(_prev, _now);\r
978             if (msgs.Length == 0)\r
979             {\r
980                 _notificationManager.StopRepeat("泊地修理");\r
981                 return;\r
982             }\r
983             if (!akashi.CheckRepairing(_now) && !(akashi.CheckPresetRepairing() && _config.UsePresetAkashi))\r
984             {\r
985                 _notificationManager.StopRepeat("泊地修理");\r
986                 return;\r
987             }\r
988             var skipPreliminary = false;\r
989             if (msgs[0].Proceeded == "20分経過しました。")\r
990             {\r
991                 SetNotification("泊地修理20分経過", msgs[0].Proceeded);\r
992                 msgs[0].Proceeded = "";\r
993                 skipPreliminary = true;\r
994                 // 修理完了がいるかもしれないので続ける\r
995             }\r
996             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
997             {\r
998                 if (msgs[i].Proceeded != "")\r
999                     SetNotification("泊地修理進行", i, msgs[i].Proceeded);\r
1000                 if (msgs[i].Completed != "")\r
1001                     SetNotification("泊地修理完了", i, msgs[i].Completed);\r
1002             }\r
1003             var pre = TimeSpan.FromSeconds(_config.Notifications["泊地修理20分経過"].PreliminaryPeriod);\r
1004             if (skipPreliminary || pre == TimeSpan.Zero)\r
1005                 return;\r
1006             if ((msgs = akashi.GetNotice(_prev + pre, _now + pre))[0].Proceeded == "20分経過しました。")\r
1007                 SetPreNotification("泊地修理20分経過", 0, msgs[0].Proceeded);\r
1008         }\r
1009 \r
1010         private void SetNotification(string key, string subject)\r
1011         {\r
1012             SetNotification(key, 0, subject);\r
1013         }\r
1014 \r
1015         private void SetNotification(string key, int fleet, string subject)\r
1016         {\r
1017             var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
1018             _notificationManager.Enqueue(key, fleet, subject,\r
1019                 (spec.Flags & _config.NotificationFlags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
1020         }\r
1021 \r
1022         private void SetPreNotification(string key, int fleet, string subject)\r
1023         {\r
1024             var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
1025             if ((spec.Flags & NotificationType.Preliminary) != 0)\r
1026                 _notificationManager.Enqueue(key, fleet, subject, 0, true);\r
1027         }\r
1028 \r
1029         private void UpdateRepairList()\r
1030         {\r
1031             panelRepairList.SetRepairList(_sniffer.RepairList);\r
1032         }\r
1033 \r
1034         private void UpdateQuestList()\r
1035         {\r
1036             var category = new[]\r
1037             {\r
1038                 labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
1039                 labelQuestColor6\r
1040             };\r
1041             var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
1042             var count = new[]\r
1043             {\r
1044                 labelQuestCount1, labelQuestCount2, labelQuestCount3, labelQuestCount4, labelQuestCount5,\r
1045                 labelQuestCount6\r
1046             };\r
1047             var progress = new[]\r
1048                 {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
1049             var quests = _sniffer.Quests;\r
1050             for (var i = 0; i < name.Length; i++)\r
1051             {\r
1052                 if (i < quests.Length)\r
1053                 {\r
1054                     category[i].BackColor = quests[i].Color;\r
1055                     name[i].Text = quests[i].Name;\r
1056                     progress[i].Text = $"{quests[i].Progress:D}%";\r
1057                     _toolTipQuest.SetToolTip(name[i], quests[i].ToToolTip());\r
1058                     var c = quests[i].Count;\r
1059                     if (c.Id == 0)\r
1060                     {\r
1061                         count[i].Text = "";\r
1062                         count[i].ForeColor = Color.Black;\r
1063                         _toolTip.SetToolTip(count[i], "");\r
1064                         continue;\r
1065                     }\r
1066                     count[i].Text = " " + c;\r
1067                     count[i].ForeColor = c.Cleared ? CUDColor.Green : Color.Black;\r
1068                     _toolTip.SetToolTip(count[i], c.ToToolTip());\r
1069                 }\r
1070                 else\r
1071                 {\r
1072                     category[i].BackColor = DefaultBackColor;\r
1073                     name[i].Text = count[i].Text = progress[i].Text = "";\r
1074                     _toolTipQuest.SetToolTip(name[i], "");\r
1075                     _toolTip.SetToolTip(count[i], "");\r
1076                 }\r
1077             }\r
1078         }\r
1079 \r
1080         private void Alarm(string balloonTitle, string balloonMessage, string name)\r
1081         {\r
1082             var flags = _config.Notifications[name].Flags;\r
1083             var effective = _config.NotificationFlags & _config.Notifications[name].Flags;\r
1084             if ((effective & NotificationType.FlashWindow) != 0)\r
1085                 Win32API.FlashWindow(Handle);\r
1086             if ((effective & NotificationType.ShowBaloonTip) != 0)\r
1087                 notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
1088             if ((effective & NotificationType.PlaySound) != 0)\r
1089                 PlaySound(_config.Sounds[name], _config.Sounds.Volume);\r
1090             if (_config.Pushbullet.On && (flags & NotificationType.Push) != 0)\r
1091             {\r
1092                 Task.Run(() =>\r
1093                 {\r
1094                     PushNotification.PushToPushbullet(_config.Pushbullet.Token, balloonTitle, balloonMessage);\r
1095                 });\r
1096             }\r
1097             if (_config.Pushover.On && (flags & NotificationType.Push) != 0)\r
1098             {\r
1099                 Task.Run(() =>\r
1100                 {\r
1101                     PushNotification.PushToPushover(_config.Pushover.ApiKey, _config.Pushover.UserKey,\r
1102                         balloonTitle, balloonMessage);\r
1103                 });\r
1104             }\r
1105         }\r
1106 \r
1107         [DllImport("winmm.dll")]\r
1108         private static extern int mciSendString(String command,\r
1109             StringBuilder buffer, int bufferSize, IntPtr hWndCallback);\r
1110 \r
1111 // ReSharper disable InconsistentNaming\r
1112         // ReSharper disable once IdentifierTypo\r
1113         private const int MM_MCINOTIFY = 0x3B9;\r
1114 \r
1115         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
1116 // ReSharper restore InconsistentNaming\r
1117 \r
1118         public void PlaySound(string file, int volume)\r
1119         {\r
1120             if (!File.Exists(file))\r
1121                 return;\r
1122             mciSendString("close sound", null, 0, IntPtr.Zero);\r
1123             if (mciSendString("open \"" + file + "\" type mpegvideo alias sound", null, 0, IntPtr.Zero) != 0)\r
1124                 return;\r
1125             mciSendString("setaudio sound volume to " + volume * 10, null, 0, IntPtr.Zero);\r
1126             mciSendString("play sound notify", null, 0, Handle);\r
1127         }\r
1128 \r
1129         protected override void WndProc(ref Message m)\r
1130         {\r
1131             if (m.Msg == MM_MCINOTIFY && (int)m.WParam == MCI_NOTIFY_SUCCESSFUL)\r
1132                 mciSendString("close sound", null, 0, IntPtr.Zero);\r
1133             base.WndProc(ref m);\r
1134         }\r
1135 \r
1136         private void SetupFleetClick()\r
1137         {\r
1138             var labels = new[]\r
1139             {\r
1140                 new[] {labelFleet1, labelFleet2, labelFleet3, labelFleet4},\r
1141                 new[] {labelFuelSq1, labelFuelSq2, labelFuelSq3, labelFuelSq4},\r
1142                 new[] {labelBullSq1, labelBullSq2, labelBullSq3, labelBullSq4}\r
1143             };\r
1144             foreach (var a in labels)\r
1145             {\r
1146                 for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
1147                 {\r
1148                     a[fleet].Tag = fleet;\r
1149                     a[fleet].Click += labelFleet_Click;\r
1150                     a[fleet].DoubleClick += labelFleet_DoubleClick;\r
1151                 }\r
1152             }\r
1153         }\r
1154 \r
1155         private void labelFleet_Click(object sender, EventArgs e)\r
1156         {\r
1157             if (!_started)\r
1158                 return;\r
1159             var fleet = (int)((Label)sender).Tag;\r
1160             if (_currentFleet == fleet)\r
1161             {\r
1162                 if (fleet > 0)\r
1163                     return;\r
1164                 _combinedFleet = _sniffer.IsCombinedFleet && !_combinedFleet;\r
1165                 UpdatePanelShipInfo();\r
1166                 return;\r
1167             }\r
1168             _combinedFleet = false;\r
1169             _currentFleet = fleet;\r
1170             UpdatePanelShipInfo();\r
1171         }\r
1172 \r
1173         private void labelFleet1_MouseHover(object sender, EventArgs e)\r
1174         {\r
1175             labelFleet1.Text = _currentFleet == 0 && _sniffer.IsCombinedFleet && !_combinedFleet ? "連合" : "第一";\r
1176         }\r
1177 \r
1178         private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
1179         {\r
1180             labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
1181         }\r
1182 \r
1183         private void labelFleet_DoubleClick(object sender, EventArgs e)\r
1184         {\r
1185             if (!_started)\r
1186                 return;\r
1187             var fleet = (int)((Label)sender).Tag;\r
1188             var text = TextGenerator.GenerateFleetData(_sniffer, fleet);\r
1189             if (string.IsNullOrEmpty(text))\r
1190                 return;\r
1191             Clipboard.SetText(text);\r
1192             _tooltipCopy.Active = true;\r
1193             _tooltipCopy.Show("コピーしました。", (Label)sender);\r
1194             Task.Run(async () =>\r
1195             {\r
1196                 await Task.Delay(1000);\r
1197                 _tooltipCopy.Active = false;\r
1198             });\r
1199         }\r
1200 \r
1201         private readonly Color _activeButtonColor = Color.FromArgb(152, 179, 208);\r
1202 \r
1203         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
1204         {\r
1205             if (labelBucketHistory.Visible)\r
1206             {\r
1207                 labelBucketHistory.Visible = false;\r
1208                 labelBucketHistoryButton.BackColor = DefaultBackColor;\r
1209             }\r
1210             else\r
1211             {\r
1212                 labelBucketHistory.Visible = true;\r
1213                 labelBucketHistory.BringToFront();\r
1214                 labelBucketHistoryButton.BackColor = _activeButtonColor;\r
1215             }\r
1216         }\r
1217 \r
1218         private void labelBucketHistory_Click(object sender, EventArgs e)\r
1219         {\r
1220             labelBucketHistory.Visible = false;\r
1221             labelBucketHistoryButton.BackColor = DefaultBackColor;\r
1222         }\r
1223 \r
1224         private void labelMaterialHistoryButton_Click(object sender, EventArgs e)\r
1225         {\r
1226             if (panelMaterialHistory.Visible)\r
1227             {\r
1228                 panelMaterialHistory.Visible = false;\r
1229                 labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1230             }\r
1231             else\r
1232             {\r
1233                 panelMaterialHistory.Visible = true;\r
1234                 panelMaterialHistory.BringToFront();\r
1235                 labelMaterialHistoryButton.BackColor = _activeButtonColor;\r
1236             }\r
1237         }\r
1238 \r
1239         private void panelMaterialHistory_Click(object sender, EventArgs e)\r
1240         {\r
1241             panelMaterialHistory.Visible = false;\r
1242             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
1243         }\r
1244 \r
1245         public void ResetAchievement()\r
1246         {\r
1247             _sniffer.Achievement.Reset();\r
1248             UpdateItemInfo();\r
1249         }\r
1250 \r
1251         private void labelRepairListButton_Click(object sender, EventArgs e)\r
1252         {\r
1253             if (panelRepairList.Visible)\r
1254             {\r
1255                 panelRepairList.Visible = false;\r
1256                 labelRepairListButton.BackColor = DefaultBackColor;\r
1257             }\r
1258             else\r
1259             {\r
1260                 panelRepairList.Visible = true;\r
1261                 panelRepairList.BringToFront();\r
1262                 labelRepairListButton.BackColor = _activeButtonColor;\r
1263             }\r
1264         }\r
1265 \r
1266         private void panelRepairList_Click(object sender, EventArgs e)\r
1267         {\r
1268             panelRepairList.Visible = false;\r
1269             labelRepairListButton.BackColor = DefaultBackColor;\r
1270         }\r
1271 \r
1272         private void ShipListToolStripMenuItem_Click(object sender, EventArgs e)\r
1273         {\r
1274             _listForm.UpdateList();\r
1275             _listForm.Show();\r
1276             if (_listForm.WindowState == FormWindowState.Minimized)\r
1277                 _listForm.WindowState = FormWindowState.Normal;\r
1278             _listForm.Activate();\r
1279         }\r
1280 \r
1281         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
1282         {\r
1283             Process.Start("http://localhost:" + _config.Proxy.Listen + "/");\r
1284         }\r
1285 \r
1286         private void labelClearQuest_Click(object sender, EventArgs e)\r
1287         {\r
1288             _sniffer.ClearQuests();\r
1289             UpdateQuestList();\r
1290         }\r
1291 \r
1292         private void labelClearQuest_MouseDown(object sender, MouseEventArgs e)\r
1293         {\r
1294             labelClearQuest.BackColor = _activeButtonColor;\r
1295         }\r
1296 \r
1297         private void labelClearQuest_MouseUp(object sender, MouseEventArgs e)\r
1298         {\r
1299             labelClearQuest.BackColor = DefaultBackColor;\r
1300         }\r
1301 \r
1302         private void labelQuest_DoubleClick(object sender, EventArgs e)\r
1303         {\r
1304             var label = (Label)sender;\r
1305             if (string.IsNullOrEmpty(label.Text))\r
1306                 return;\r
1307             Clipboard.SetText(label.Text);\r
1308             _tooltipCopy.Active = true;\r
1309             _tooltipCopy.Show("コピーしました。", label);\r
1310             Task.Run(async () =>\r
1311             {\r
1312                 await Task.Delay(1000);\r
1313                 _tooltipCopy.Active = false;\r
1314             });\r
1315         }\r
1316 \r
1317         private void CaptureToolStripMenuItem_Click(object sender, EventArgs e)\r
1318         {\r
1319             try\r
1320             {\r
1321                 var proc = new ProcessStartInfo("BurageSnap.exe") {WorkingDirectory = "Capture"};\r
1322                 Process.Start(proc);\r
1323             }\r
1324             catch (FileNotFoundException)\r
1325             {\r
1326             }\r
1327             catch (Win32Exception)\r
1328             {\r
1329             }\r
1330         }\r
1331     }\r
1332 }