OSDN Git Service

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