OSDN Git Service

1-6のチェックを汎用化する
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / MainForm.cs
index 6a2f36f..aee95f2 100644 (file)
@@ -14,6 +14,7 @@
 \r
 using System;\r
 using System.Collections.Generic;\r
+using System.ComponentModel;\r
 using System.Diagnostics;\r
 using System.Drawing;\r
 using System.Globalization;\r
@@ -23,26 +24,31 @@ using System.Net;
 using System.Runtime.InteropServices;\r
 using System.Text;\r
 using System.Text.RegularExpressions;\r
+using System.Threading;\r
 using System.Threading.Tasks;\r
 using System.Windows.Forms;\r
+using KancolleSniffer.Log;\r
+using KancolleSniffer.Model;\r
+using KancolleSniffer.Net;\r
+using KancolleSniffer.Util;\r
+using KancolleSniffer.View;\r
 using Microsoft.CSharp.RuntimeBinder;\r
 using static System.Math;\r
+using Timer = System.Windows.Forms.Timer;\r
 \r
 namespace KancolleSniffer\r
 {\r
     public partial class MainForm : Form\r
     {\r
-        private readonly Sniffer _sniffer = new Sniffer();\r
-        private readonly Config _config = new Config();\r
         private readonly ConfigDialog _configDialog;\r
         private readonly ProxyManager _proxyManager;\r
-        private readonly ToolTip _toolTipQuest = new ToolTip {ShowAlways = true, AutoPopDelay = 10000};\r
-        private readonly ToolTip _toolTipCount = new ToolTip {ShowAlways = true};\r
-        private readonly ToolTip _tooltipCopy = new ToolTip {AutomaticDelay = 0};\r
+        private readonly ResizableToolTip _toolTip = new ResizableToolTip();\r
+        private readonly ResizableToolTip _tooltipCopy = new ResizableToolTip {ShowAlways = false, AutomaticDelay = 0};\r
         private int _currentFleet;\r
         private bool _combinedFleet;\r
-        private readonly Label[] _labelCheckFleets;\r
-        private readonly MainFormLabels _mainLabels;\r
+        private readonly MainShipLabels _mainLabels = new MainShipLabels();\r
+        private readonly MainNDockLabels _ndockLabels = new MainNDockLabels();\r
+        private NumberAndHistory _numberAndHistory;\r
         private readonly ListForm _listForm;\r
         private readonly NotificationManager _notificationManager;\r
         private bool _started;\r
@@ -50,50 +56,101 @@ namespace KancolleSniffer
         private string _debugLogFile;\r
         private IEnumerator<string> _playLog;\r
         private DateTime _prev, _now;\r
-        private bool _inSortie;\r
 \r
         private readonly ErrorDialog _errorDialog = new ErrorDialog();\r
         private readonly ErrorLog _errorLog;\r
 \r
+        public Sniffer Sniffer { get; } = new Sniffer();\r
+        public Config Config { get; } = new Config();\r
+\r
         public MainForm()\r
         {\r
             InitializeComponent();\r
             HttpProxy.AfterSessionComplete += HttpProxy_AfterSessionComplete;\r
-            _configDialog = new ConfigDialog(_config, this);\r
-            _labelCheckFleets = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
-\r
-            // この時点でAutoScaleDimensions == CurrentAutoScaleDimensionsなので、\r
-            // MainForm.Designer.csのAutoScaleDimensionsの6f,12fを使う。\r
-            ShipLabel.ScaleFactor = new SizeF(CurrentAutoScaleDimensions.Width / 6f,\r
-                CurrentAutoScaleDimensions.Height / 12f);\r
+            Config.Load();\r
+            _configDialog = new ConfigDialog(this);\r
+            _listForm = new ListForm(this);\r
+            _notificationManager = new NotificationManager(Alarm);\r
+            SetupView(_notificationManager);\r
+            _proxyManager = new ProxyManager(this);\r
+            _proxyManager.UpdatePacFile();\r
+            _errorLog = new ErrorLog(Sniffer);\r
+            LoadData();\r
+            Sniffer.RepeatingTimerController = new RepeatingTimerController(this);\r
+        }\r
 \r
+        private void SetupView(NotificationManager manager)\r
+        {\r
+            SetScaleFactorOfDpiScaling();\r
             SetupFleetClick();\r
-            _mainLabels = new MainFormLabels();\r
-            _mainLabels.CreateAkashiTimers(panelShipInfo);\r
-            _mainLabels.CreateShipLabels(panelShipInfo, ShowShipOnShipList);\r
-            _mainLabels.CreateAkashiTimers7(panel7Ships);\r
-            _mainLabels.CreateShipLabels7(panel7Ships, ShowShipOnShipList);\r
-            _mainLabels.CreateCombinedShipLabels(panelCombinedFleet, ShowShipOnShipList);\r
-            _mainLabels.CreateNDockLabels(panelDock, labelNDock_Click);\r
+            CreateMainLabels();\r
+            CreateNumberAndHistory(manager);\r
+            labelPresetAkashiTimer.BackColor = CustomColors.ColumnColors.Bright;\r
+            SetupQuestPanel();\r
             panelRepairList.CreateLabels(panelRepairList_Click);\r
-            labelPresetAkashiTimer.BackColor = ShipLabel.ColumnColors[1];\r
-            _listForm = new ListForm(_sniffer, _config) {Owner = this};\r
-            _notificationManager = new NotificationManager(Alarm);\r
-            try\r
+            PerformZoom();\r
+        }\r
+\r
+        private void SetScaleFactorOfDpiScaling()\r
+        {\r
+            var autoScaleDimensions = new SizeF(6f, 12f); // AutoScaleDimensionの初期値\r
+            Scaler.Factor = new SizeF(CurrentAutoScaleDimensions.Width / autoScaleDimensions.Width,\r
+                CurrentAutoScaleDimensions.Height / autoScaleDimensions.Height);\r
+        }\r
+\r
+        private void SetupQuestPanel()\r
+        {\r
+            int prevHeight = questPanel.Height;\r
+            questPanel.CreateLabels(Config.QuestLines, labelQuest_DoubleClick);\r
+            Height += questPanel.Height - prevHeight;\r
+        }\r
+\r
+        private void CreateMainLabels()\r
+        {\r
+            _mainLabels.CreateAllShipLabels(new MainShipPanels\r
+            {\r
+                PanelShipInfo = panelShipInfo,\r
+                Panel7Ships = panel7Ships,\r
+                PanelCombinedFleet = panelCombinedFleet,\r
+            }, ShowShipOnShipList);\r
+            _ndockLabels.Create(panelDock, labelNDock_Click);\r
+        }\r
+\r
+        private void CreateNumberAndHistory(NotificationManager manager)\r
+        {\r
+            _numberAndHistory = new NumberAndHistory(new NumberAndHistoryLabels\r
+            {\r
+                NumOfShips = labelNumOfShips,\r
+                NumOfEquips = labelNumOfEquips,\r
+                NumOfBuckets = labelNumOfBuckets,\r
+                BucketHistory = labelBucketHistory,\r
+                Achievement = labelAchievement,\r
+                FuelHistory = labelFuelHistory,\r
+                BulletHistory = labelBulletHistory,\r
+                SteelHistory = labelSteelHistory,\r
+                BauxiteHistory = labelBouxiteHistory,\r
+                ToolTip = _toolTip\r
+            }, Sniffer, new NotifySubmitter(manager));\r
+        }\r
+\r
+        private class NotifySubmitter : INotifySubmitter\r
+        {\r
+            private readonly NotificationManager _manager;\r
+\r
+            public NotifySubmitter(NotificationManager manager)\r
             {\r
-                _config.Load();\r
+                _manager = manager;\r
             }\r
-            catch (Exception ex)\r
+\r
+            public void Flash()\r
             {\r
-                throw new ConfigFileException("設定ファイルが壊れています。", ex);\r
+                _manager.Flash();\r
+            }\r
+\r
+            public void Enqueue(string key, string subject)\r
+            {\r
+                _manager.Enqueue(key, subject);\r
             }\r
-            _proxyManager = new ProxyManager(_config, this);\r
-            _errorLog = new ErrorLog(_sniffer);\r
-            _proxyManager.UpdatePacFile();\r
-            PerformZoom();\r
-            _mainLabels.AdjustAkashiTimers();\r
-            LoadData();\r
-            _sniffer.RepeatingTimerController = new RepeatingTimerController(_notificationManager, _config);\r
         }\r
 \r
         /// <summary>\r
@@ -120,8 +177,7 @@ namespace KancolleSniffer
         private void LoadData()\r
         {\r
             var target = "";\r
-            _sniffer.LoadState();\r
-            DataLoader.LoadTpSpec();\r
+            Sniffer.LoadState();\r
             _watcher.SynchronizingObject = this;\r
             _watcherTimer.Tick += (sender, ev) =>\r
             {\r
@@ -129,10 +185,10 @@ namespace KancolleSniffer
                 switch (target)\r
                 {\r
                     case "status.xml":\r
-                        _sniffer.LoadState();\r
+                        Sniffer.LoadState();\r
                         break;\r
                     case "TP.csv":\r
-                        DataLoader.LoadTpSpec();\r
+                        Sniffer.AdditionalData.LoadTpSpec();\r
                         break;\r
                 }\r
             };\r
@@ -150,10 +206,10 @@ namespace KancolleSniffer
             private readonly NotificationManager _manager;\r
             private readonly Config _config;\r
 \r
-            public RepeatingTimerController(NotificationManager manager, Config config)\r
+            public RepeatingTimerController(MainForm main)\r
             {\r
-                _manager = manager;\r
-                _config = config;\r
+                _manager = main._notificationManager;\r
+                _config = main.Config;\r
             }\r
 \r
             public void Stop(string key)\r
@@ -165,18 +221,11 @@ namespace KancolleSniffer
 \r
             public void Stop(string key, int fleet) => _manager.StopRepeat(key, fleet);\r
 \r
-            public void Suspend() => _manager.SuspendRepeat();\r
+            public void Suspend(string exception = null) => _manager.SuspendRepeat(exception);\r
 \r
             public void Resume() => _manager.ResumeRepeat();\r
         }\r
 \r
-        public class ConfigFileException : Exception\r
-        {\r
-            public ConfigFileException(string message, Exception innerException) : base(message, innerException)\r
-            {\r
-            }\r
-        }\r
-\r
         private void HttpProxy_AfterSessionComplete(HttpProxy.Session session)\r
         {\r
             BeginInvoke(new Action<HttpProxy.Session>(ProcessRequest), session);\r
@@ -189,12 +238,13 @@ namespace KancolleSniffer
                 return;\r
             var request = session.Request.BodyAsString;\r
             var response = session.Response.BodyAsString;\r
+            Privacy.Remove(ref url, ref request, ref response);\r
             if (response == null || !response.StartsWith("svdata="))\r
             {\r
                 WriteDebugLog(url, request, response);\r
                 return;\r
             }\r
-            response = UnescapeString(response.Remove(0, "svdata=".Length));\r
+            response = UnEscapeString(response.Remove(0, "svdata=".Length));\r
             WriteDebugLog(url, request, response);\r
             ProcessRequestMain(url, request, response);\r
         }\r
@@ -203,7 +253,7 @@ namespace KancolleSniffer
         {\r
             try\r
             {\r
-                UpdateInfo(_sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
+                UpdateInfo(Sniffer.Sniff(url, request, JsonParser.Parse(response)));\r
                 _errorLog.CheckBattleApi(url, request, response);\r
             }\r
 \r
@@ -249,7 +299,7 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        private string UnescapeString(string s)\r
+        private string UnEscapeString(string s)\r
         {\r
             try\r
             {\r
@@ -270,6 +320,7 @@ namespace KancolleSniffer
                 labelLogin.Visible = false;\r
                 linkLabelGuide.Visible = false;\r
                 _started = true;\r
+                _notificationManager.StopAllRepeat();\r
                 return;\r
             }\r
             if (!_started)\r
@@ -290,16 +341,19 @@ namespace KancolleSniffer
                 UpdateShipInfo();\r
             if ((update & Sniffer.Update.Battle) != 0)\r
                 UpdateBattleInfo();\r
+            if ((update & Sniffer.Update.Cell) != 0)\r
+                UpdateCellInfo();\r
         }\r
 \r
         private void MainForm_Load(object sender, EventArgs e)\r
         {\r
+            SuppressActivate.Start();\r
             RestoreLocation();\r
-            if (_config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
+            if (Config.HideOnMinimized && WindowState == FormWindowState.Minimized)\r
                 ShowInTaskbar = false;\r
-            if (_config.ShowHpInPercent)\r
+            if (Config.ShowHpInPercent)\r
                 _mainLabels.ToggleHpPercent();\r
-            if (_config.ShipList.Visible)\r
+            if (Config.ShipList.Visible)\r
                 _listForm.Show();\r
             ApplyConfig();\r
             ApplyDebugLogSetting();\r
@@ -307,7 +361,7 @@ namespace KancolleSniffer
             ApplyProxySetting();\r
             CheckVersionUp((current, latest) =>\r
             {\r
-                if (double.Parse(latest) <= double.Parse(current))\r
+                if (latest == current)\r
                     return;\r
                 linkLabelGuide.Text = $"バージョン{latest}があります。";\r
                 linkLabelGuide.LinkArea = new LinkArea(0, linkLabelGuide.Text.Length);\r
@@ -340,7 +394,7 @@ namespace KancolleSniffer
 \r
         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)\r
         {\r
-            if (!_config.ExitSilently)\r
+            if (!Config.ExitSilently)\r
             {\r
                 using (var dialog = new ConfirmDialog())\r
                 {\r
@@ -351,19 +405,66 @@ namespace KancolleSniffer
                     }\r
                 }\r
             }\r
-            e.Cancel = false;\r
-            _sniffer.FlashLog();\r
-            _config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
-            _config.ShowHpInPercent = _mainLabels.ShowHpInPercent;\r
-            _config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
-            _config.Save();\r
-            _sniffer.SaveState();\r
+            Config.ShipList.Visible = _listForm.Visible && _listForm.WindowState == FormWindowState.Normal;\r
+            _listForm.Close();\r
+            Sniffer.FlashLog();\r
+            Config.Location = (WindowState == FormWindowState.Normal ? Bounds : RestoreBounds).Location;\r
+            Config.ShowHpInPercent = _mainLabels.ShowHpInPercent;\r
+            Config.Save();\r
+            Sniffer.SaveState();\r
             _proxyManager.Shutdown();\r
         }\r
 \r
         private void MainForm_Resize(object sender, EventArgs e)\r
         {\r
-            ShowInTaskbar = !(_config.HideOnMinimized && WindowState == FormWindowState.Minimized);\r
+            if (_listForm == null) // DPIが100%でないときにInitializeComponentから呼ばれるので\r
+                return;\r
+            SuppressActivate.Start();\r
+            if (WindowState == FormWindowState.Minimized)\r
+            {\r
+                if (Config.HideOnMinimized)\r
+                    ShowInTaskbar = false;\r
+            }\r
+            _listForm.ChangeWindowState(WindowState);\r
+        }\r
+\r
+        public TimeOutChecker SuppressActivate = new TimeOutChecker();\r
+\r
+        private void MainForm_Activated(object sender, EventArgs e)\r
+        {\r
+            if (SuppressActivate.Check())\r
+                return;\r
+            if (NeedRaise)\r
+                RaiseBothWindows();\r
+        }\r
+\r
+        private bool NeedRaise => _listForm.Visible && WindowState != FormWindowState.Minimized;\r
+\r
+        private void RaiseBothWindows()\r
+        {\r
+            _listForm.Owner = null;\r
+            Owner = _listForm;\r
+            BringToFront();\r
+            Owner = null;\r
+        }\r
+\r
+        public class TimeOutChecker\r
+        {\r
+            private DateTime _lastCheck;\r
+            private readonly TimeSpan _timeout = TimeSpan.FromMilliseconds(500);\r
+\r
+            public void Start()\r
+            {\r
+                _lastCheck = DateTime.Now;\r
+            }\r
+\r
+            public bool Check()\r
+            {\r
+                var now = DateTime.Now;\r
+                var last = _lastCheck;\r
+                _lastCheck = now;\r
+                return now - last < _timeout;\r
+            }\r
         }\r
 \r
         private void notifyIconMain_MouseDoubleClick(object sender, MouseEventArgs e)\r
@@ -375,8 +476,7 @@ namespace KancolleSniffer
         {\r
             ShowInTaskbar = true;\r
             WindowState = FormWindowState.Normal;\r
-            TopMost = _config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
-            Activate();\r
+            TopMost = _listForm.TopMost = Config.TopMost; // 最前面に表示されなくなることがあるのを回避する\r
         }\r
 \r
         private void ExitToolStripMenuItem_Click(object sender, EventArgs e)\r
@@ -388,7 +488,7 @@ namespace KancolleSniffer
         {\r
             if (_configDialog.ShowDialog(this) == DialogResult.OK)\r
             {\r
-                _config.Save();\r
+                Config.Save();\r
                 ApplyConfig();\r
                 StopRepeatingTimer(_configDialog.RepeatSettingsChanged);\r
             }\r
@@ -402,8 +502,12 @@ namespace KancolleSniffer
 \r
         private void PerformZoom()\r
         {\r
-            if (_config.Zoom == 100)\r
+            if (Config.Zoom == 100)\r
+            {\r
+                ShipLabel.Name.BaseFont = Font;\r
+                ShipLabel.Name.LatinFont = LatinFont();\r
                 return;\r
+            }\r
             var prev = CurrentAutoScaleDimensions;\r
             foreach (var control in new Control[]\r
             {\r
@@ -412,37 +516,53 @@ namespace KancolleSniffer
                 contextMenuStripMain, _errorDialog\r
             })\r
             {\r
-                control.Font = new Font(control.Font.FontFamily, control.Font.Size * _config.Zoom / 100);\r
+                control.Font = ZoomFont(control.Font);\r
             }\r
-            ShipLabel.LatinFont = new Font("Tahoma", 8f * _config.Zoom / 100);\r
+            foreach (var toolTip in new[] {_toolTip, _tooltipCopy})\r
+            {\r
+                toolTip.Font = ZoomFont(toolTip.Font);\r
+            }\r
+            ShipLabel.Name.BaseFont = Font;\r
+            ShipLabel.Name.LatinFont = LatinFont();\r
             var cur = CurrentAutoScaleDimensions;\r
-            ShipLabel.ScaleFactor = new SizeF(ShipLabel.ScaleFactor.Width * cur.Width / prev.Width,\r
-                ShipLabel.ScaleFactor.Height * cur.Height / prev.Height);\r
+            Scaler.Factor = Scaler.Scale(cur.Width / prev.Width, cur.Height / prev.Height);\r
+        }\r
+\r
+        private Font ZoomFont(Font font)\r
+        {\r
+            return new Font(font.FontFamily, font.Size * Config.Zoom / 100);\r
+        }\r
+\r
+        private Font LatinFont()\r
+        {\r
+            return new Font("Tahoma", 8f * Config.Zoom / 100);\r
         }\r
 \r
         private void RestoreLocation()\r
         {\r
-            if (_config.Location.X == int.MinValue)\r
+            if (Config.Location.X == int.MinValue)\r
                 return;\r
-            if (IsTitleBarOnAnyScreen(_config.Location))\r
-                Location = _config.Location;\r
+            if (IsTitleBarOnAnyScreen(Config.Location))\r
+                Location = Config.Location;\r
         }\r
 \r
         private void ApplyConfig()\r
         {\r
-            _listForm.TopMost = TopMost = _config.TopMost;\r
-            _sniffer.Item.MarginShips = _config.MarginShips;\r
-            UpdateNumOfShips();\r
-            _sniffer.Item.MarginEquips = _config.MarginEquips;\r
-            UpdateNumOfEquips();\r
-            _sniffer.Achievement.ResetHours = _config.ResetHours;\r
+            if (TopMost != Config.TopMost)\r
+                TopMost = _listForm.TopMost = Config.TopMost;\r
+            Sniffer.ShipCounter.Margin = Config.MarginShips;\r
+            _numberAndHistory.UpdateNumOfShips();\r
+            Sniffer.ItemCounter.Margin = Config.MarginEquips;\r
+            _numberAndHistory.UpdateNumOfEquips();\r
+            Sniffer.Achievement.ResetHours = Config.ResetHours;\r
             labelAkashiRepair.Visible = labelAkashiRepairTimer.Visible =\r
-                labelPresetAkashiTimer.Visible = _config.UsePresetAkashi;\r
+                labelPresetAkashiTimer.Visible = Config.UsePresetAkashi;\r
+            Sniffer.WarnBadDamageWithDameCon = Config.WarnBadDamageWithDameCon;\r
         }\r
 \r
         public void ApplyDebugLogSetting()\r
         {\r
-            _debugLogFile = _config.DebugLogging ? _config.DebugLogFile : null;\r
+            _debugLogFile = Config.DebugLogging ? Config.DebugLogFile : null;\r
         }\r
 \r
         public bool ApplyProxySetting()\r
@@ -452,11 +572,11 @@ namespace KancolleSniffer
 \r
         public void ApplyLogSetting()\r
         {\r
-            LogServer.OutputDir = _config.Log.OutputDir;\r
-            LogServer.MaterialHistory = _sniffer.Material.MaterialHistory;\r
-            _sniffer.EnableLog(_config.Log.On ? LogType.All : LogType.None);\r
-            _sniffer.MaterialLogInterval = _config.Log.MaterialLogInterval;\r
-            _sniffer.LogOutputDir = _config.Log.OutputDir;\r
+            LogServer.OutputDir = Config.Log.OutputDir;\r
+            LogServer.LogProcessor = new LogProcessor(Sniffer.Material.MaterialHistory, Sniffer.MapDictionary);\r
+            Sniffer.EnableLog(Config.Log.On ? LogType.All : LogType.None);\r
+            Sniffer.MaterialLogInterval = Config.Log.MaterialLogInterval;\r
+            Sniffer.LogOutputDir = Config.Log.OutputDir;\r
         }\r
 \r
         public static bool IsTitleBarOnAnyScreen(Point location)\r
@@ -521,109 +641,46 @@ namespace KancolleSniffer
             if (!_listForm.Visible)\r
                 return;\r
             var idx = (int)((Control)sender).Tag;\r
-            var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
-            if (statuses.Length <= idx)\r
-                return;\r
-            _listForm.ShowShip(statuses[idx].Id);\r
+            var ship = (_combinedFleet\r
+                ? Sniffer.Fleets[0].Ships.Concat(Sniffer.Fleets[1].Ships).ToArray()\r
+                : Sniffer.Fleets[_currentFleet].Ships)[idx];\r
+            if (!ship.Empty)\r
+                _listForm.ShowShip(ship.Id);\r
         }\r
 \r
+\r
         private void UpdateItemInfo()\r
         {\r
-            UpdateNumOfShips();\r
-            UpdateNumOfEquips();\r
-            _notificationManager.Flash();\r
-            labelNumOfBuckets.Text = _sniffer.Material.MaterialHistory[(int)Material.Bucket].Now.ToString("D");\r
-            UpdateBucketHistory();\r
-            var ac = _sniffer.Achievement.Value;\r
-            if (ac >= 10000)\r
-                ac = 9999;\r
-            labelAchievement.Text = ac >= 1000 ? ((int)ac).ToString("D") : ac.ToString("F1");\r
-            toolTipAchievement.SetToolTip(labelAchievement,\r
-                "今月 " + _sniffer.Achievement.ValueOfMonth.ToString("F1") + "\n" +\r
-                "EO " + _sniffer.ExMap.Achievement);\r
-            UpdateMaterialHistry();\r
+            _numberAndHistory.Update();\r
             if (_listForm.Visible)\r
                 _listForm.UpdateList();\r
         }\r
 \r
-        private void UpdateNumOfShips()\r
-        {\r
-            var item = _sniffer.Item;\r
-            labelNumOfShips.Text = $"{item.NowShips:D}/{item.MaxShips:D}";\r
-            labelNumOfShips.ForeColor = item.TooManyShips ? CUDColor.Red : Color.Black;\r
-            if (item.AlarmShips)\r
-            {\r
-                var message = $"残り{_sniffer.Item.MaxShips - _sniffer.Item.NowShips:D}隻";\r
-                _notificationManager.Enqueue("艦娘数超過", message);\r
-                item.AlarmShips = false;\r
-            }\r
-        }\r
-\r
-        private void UpdateNumOfEquips()\r
-        {\r
-            var item = _sniffer.Item;\r
-            labelNumOfEquips.Text = $"{item.NowEquips:D}/{item.MaxEquips:D}";\r
-            labelNumOfEquips.ForeColor = item.TooManyEquips ? CUDColor.Red : Color.Black;\r
-            if (item.AlarmEquips)\r
-            {\r
-                var message = $"残り{_sniffer.Item.MaxEquips - _sniffer.Item.NowEquips:D}個";\r
-                _notificationManager.Enqueue("装備数超過", message);\r
-                item.AlarmEquips = false;\r
-            }\r
-        }\r
-\r
-        private void UpdateBucketHistory()\r
-        {\r
-            var count = _sniffer.Material.MaterialHistory[(int)Material.Bucket];\r
-            var day = CutOverflow(count.Now - count.BegOfDay, 999);\r
-            var week = CutOverflow(count.Now - count.BegOfWeek, 999);\r
-            labelBucketHistory.Text = $"{day:+#;-#;±0} 今日\n{week:+#;-#;±0} 今週";\r
-        }\r
-\r
-        private void UpdateMaterialHistry()\r
-        {\r
-            var labels = new[] {labelFuelHistory, labelBulletHistory, labelSteelHistory, labelBouxiteHistory};\r
-            var text = new[] {"燃料", "弾薬", "鋼材", "ボーキ"};\r
-            for (var i = 0; i < labels.Length; i++)\r
-            {\r
-                var count = _sniffer.Material.MaterialHistory[i];\r
-                var port = CutOverflow(count.Now - _sniffer.Material.PrevPort[i], 99999);\r
-                var day = CutOverflow(count.Now - count.BegOfDay, 99999);\r
-                var week = CutOverflow(count.Now - count.BegOfWeek, 99999);\r
-                labels[i].Text = $"{text[i]}\n{port:+#;-#;±0}\n{day:+#;-#;±0}\n{week:+#;-#;±0}";\r
-            }\r
-        }\r
-\r
-        private int CutOverflow(int value, int limit)\r
-        {\r
-            if (value > limit)\r
-                return limit;\r
-            if (value < -limit)\r
-                return -limit;\r
-            return value;\r
-        }\r
-\r
         private void UpdateShipInfo()\r
         {\r
             SetCurrentFleet();\r
+            SetCombined();\r
             UpdatePanelShipInfo();\r
             NotifyDamagedShip();\r
             UpdateChargeInfo();\r
             UpdateRepairList();\r
+            UpdateMissionLabels();\r
             if (_listForm.Visible)\r
                 _listForm.UpdateList();\r
         }\r
 \r
+        private bool _inSortie;\r
+\r
         private void SetCurrentFleet()\r
         {\r
-            var inSortie = _sniffer.InSortie;\r
-            if (_inSortie || !inSortie.Any(x => x))\r
+            var inSortie = Sniffer.InSortie;\r
+            if (_inSortie || inSortie == -1)\r
             {\r
-                _inSortie = inSortie.Any(x => x);\r
+                _inSortie = inSortie != -1;\r
                 return;\r
             }\r
             _inSortie = true;\r
-            if (inSortie[0] && inSortie[1])\r
+            if (inSortie == 10)\r
             {\r
                 _combinedFleet = true;\r
                 _currentFleet = 0;\r
@@ -631,60 +688,108 @@ namespace KancolleSniffer
             else\r
             {\r
                 _combinedFleet = false;\r
-                _currentFleet = Array.FindIndex(inSortie, x => x);\r
+                _currentFleet = inSortie;\r
+            }\r
+        }\r
+\r
+        private bool _prevCombined;\r
+\r
+        private void SetCombined()\r
+        {\r
+            if (Sniffer.IsCombinedFleet && !_prevCombined)\r
+            {\r
+                _combinedFleet = true;\r
+                _currentFleet = 0;\r
             }\r
+            _prevCombined = Sniffer.IsCombinedFleet;\r
         }\r
 \r
         private void UpdatePanelShipInfo()\r
         {\r
-            var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
-            panel7Ships.Visible = statuses.Length == 7;\r
-            _mainLabels.SetShipLabels(statuses);\r
-            if (_sniffer.CombinedFleetType == 0)\r
-                _combinedFleet = false;\r
-            labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
-            panelCombinedFleet.Visible = _combinedFleet;\r
-            if (_combinedFleet)\r
-                _mainLabels.SetCombinedShipLabels(_sniffer.GetShipStatuses(0), _sniffer.GetShipStatuses(1));\r
-            for (var i = 0; i < _labelCheckFleets.Length; i++)\r
-                _labelCheckFleets[i].Visible = _currentFleet == i;\r
+            var ships = Sniffer.Fleets[_currentFleet].ActualShips;\r
+            panel7Ships.Visible = ships.Count == 7;\r
+            _mainLabels.SetShipLabels(ships);\r
+            ShowCombinedFleet();\r
+            ShowCurrentFleetNumber();\r
             UpdateAkashiTimer();\r
-            var battle = _sniffer.Battle;\r
-            UpdateFighterPower(_combinedFleet && (battle.BattleState == BattleState.None || battle.EnemyIsCombined));\r
+            UpdateFighterPower(IsCombinedFighterPower);\r
             UpdateLoS();\r
             UpdateCondTimers();\r
         }\r
 \r
+        private void ShowCombinedFleet()\r
+        {\r
+            if (!Sniffer.IsCombinedFleet)\r
+                _combinedFleet = false;\r
+            labelFleet1.Text = _combinedFleet ? CombinedName : "第一";\r
+            panelCombinedFleet.Visible = _combinedFleet;\r
+            if (_combinedFleet)\r
+                _mainLabels.SetCombinedShipLabels(Sniffer.Fleets[0].ActualShips, Sniffer.Fleets[1].ActualShips);\r
+        }\r
+\r
+        private void ShowCurrentFleetNumber()\r
+        {\r
+            var labels = new[] {labelCheckFleet1, labelCheckFleet2, labelCheckFleet3, labelCheckFleet4};\r
+            for (var i = 0; i < labels.Length; i++)\r
+                labels[i].Visible = _currentFleet == i;\r
+        }\r
+\r
+        private bool IsCombinedFighterPower => _combinedFleet &&\r
+                                               (Sniffer.Battle.BattleState == BattleState.None ||\r
+                                                Sniffer.Battle.EnemyIsCombined);\r
+\r
+        private string CombinedName\r
+        {\r
+            get\r
+            {\r
+                switch (Sniffer.Fleets[0].CombinedType)\r
+                {\r
+                    case CombinedType.Carrier:\r
+                        return "機動";\r
+                    case CombinedType.Surface:\r
+                        return "水上";\r
+                    case CombinedType.Transport:\r
+                        return "輸送";\r
+                    default:\r
+                        return "連合";\r
+                }\r
+            }\r
+        }\r
+\r
         private void NotifyDamagedShip()\r
         {\r
-            if (!_sniffer.BadlyDamagedShips.Any())\r
+            if (!Sniffer.BadlyDamagedShips.Any())\r
                 return;\r
-            _notificationManager.Enqueue("大破警告", string.Join(" ", _sniffer.BadlyDamagedShips));\r
+            _notificationManager.StopRepeat("大破警告");\r
+            SetNotification("大破警告", string.Join(" ", Sniffer.BadlyDamagedShips));\r
             _notificationManager.Flash();\r
         }\r
 \r
         public void UpdateFighterPower(bool combined)\r
         {\r
+            var fleets = Sniffer.Fleets;\r
             var fp = combined\r
-                ? _sniffer.GetFighterPower(0).Zip(_sniffer.GetFighterPower(1), (a, b) => a + b).ToArray()\r
-                : _sniffer.GetFighterPower(_currentFleet);\r
-            labelFighterPower.Text = fp[0].ToString("D");\r
+                ? fleets[0].FighterPower + fleets[1].FighterPower\r
+                : fleets[_currentFleet].FighterPower;\r
+            labelFighterPower.Text = fp.Min.ToString("D");\r
             var cr = combined\r
-                ? _sniffer.GetContactTriggerRate(0) + _sniffer.GetContactTriggerRate(1)\r
-                : _sniffer.GetContactTriggerRate(_currentFleet);\r
-            var text = "制空: " + (fp[0] == fp[1] ? $"{fp[0]}" : $"{fp[0]}~{fp[1]}") +\r
+                ? fleets[0].ContactTriggerRate + fleets[1].ContactTriggerRate\r
+                : fleets[_currentFleet].ContactTriggerRate;\r
+            var text = "制空: " + (fp.Diff ? fp.RangeString : fp.Min.ToString()) +\r
                        $" 触接: {cr * 100:f1}";\r
-            toolTipFighterPower.SetToolTip(labelFighterPower, text);\r
-            toolTipFighterPower.SetToolTip(labelFighterPowerCaption, text);\r
+            _toolTip.SetToolTip(labelFighterPower, text);\r
+            _toolTip.SetToolTip(labelFighterPowerCaption, text);\r
         }\r
 \r
         private void UpdateLoS()\r
         {\r
-            labelLoS.Text = RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 1)).ToString("F1");\r
-            var text = $"係数3: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 3)):F1}\r\n" +\r
-                       $"係数4: {RoundDown(_sniffer.GetFleetLineOfSights(_currentFleet, 4)):F1}";\r
-            toolTipLoS.SetToolTip(labelLoS, text);\r
-            toolTipLoS.SetToolTip(labelLoSCaption, text);\r
+            var fleet = Sniffer.Fleets[_currentFleet];\r
+            labelLoS.Text = RoundDown(fleet.GetLineOfSights(1)).ToString("F1");\r
+            var text = $"係数2: {RoundDown(fleet.GetLineOfSights(2)):F1}\r\n" +\r
+                       $"係数3: {RoundDown(fleet.GetLineOfSights(3)):F1}\r\n" +\r
+                       $"係数4: {RoundDown(fleet.GetLineOfSights(4)):F1}";\r
+            _toolTip.SetToolTip(labelLoS, text);\r
+            _toolTip.SetToolTip(labelLoSCaption, text);\r
         }\r
 \r
         private double RoundDown(double number)\r
@@ -696,16 +801,20 @@ namespace KancolleSniffer
         {\r
             ResetBattleInfo();\r
             _listForm.UpdateBattleResult();\r
-            if (_sniffer.Battle.BattleState == BattleState.None)\r
+            _listForm.UpdateAirBattleResult();\r
+            if (Sniffer.Battle.BattleState == BattleState.None)\r
                 return;\r
             panelBattleInfo.BringToFront();\r
-            var battle = _sniffer.Battle;\r
+            var battle = Sniffer.Battle;\r
             labelFormation.Text = new[] {"同航戦", "反航戦", "T字有利", "T字不利"}[battle.Formation[2] - 1];\r
             UpdateBattleFighterPower();\r
-            if ((_config.Spoilers & Spoiler.ResultRank) != 0)\r
+            if ((Config.Spoilers & Spoiler.ResultRank) != 0)\r
                 ShowResultRank();\r
-            if (_sniffer.Battle.BattleState == BattleState.Day)\r
-                _listForm.UpdateAirBattleResult();\r
+        }\r
+\r
+        private void UpdateCellInfo()\r
+        {\r
+            _listForm.UpdateCellInfo();\r
         }\r
 \r
         private void ResetBattleInfo()\r
@@ -714,30 +823,57 @@ namespace KancolleSniffer
             labelEnemyFighterPower.Text = "";\r
             labelFighterPower.ForeColor = DefaultForeColor;\r
             labelResultRank.Text = "判定";\r
-            panelBattleInfo.Visible = _sniffer.Battle.BattleState != BattleState.None;\r
+            panelBattleInfo.Visible = Sniffer.Battle.BattleState != BattleState.None;\r
         }\r
 \r
         private void UpdateBattleFighterPower()\r
         {\r
-            var battle = _sniffer.Battle;\r
-            var power = battle.EnemyFighterPower;\r
-            labelEnemyFighterPower.Text = power.AirCombat + power.UnknownMark;\r
-            if (power.AirCombat != power.Interception)\r
+            UpdateEnemyFighterPower();\r
+            var battle = Sniffer.Battle;\r
+            labelFighterPower.ForeColor = battle.BattleState == BattleState.Night\r
+                ? DefaultForeColor\r
+                : AirControlLevelColor(battle.AirControlLevel);\r
+            if (battle.BattleState == BattleState.AirRaid)\r
             {\r
-                var text = "防空: " + power.Interception + power.UnknownMark;\r
-                toolTipFighterPower.SetToolTip(labelEnemyFighterPower, text);\r
-                toolTipFighterPower.SetToolTip(labelEnemyFighterPowerCaption, text);\r
+                UpdateAirRaidFighterPower();\r
             }\r
-            UpdateFighterPower(_sniffer.CombinedFleetType > 0 && battle.EnemyIsCombined);\r
-            labelFighterPower.ForeColor = new[]\r
-                {DefaultForeColor, DefaultForeColor, CUDColor.Blue, CUDColor.Green, CUDColor.Orange, CUDColor.Red}[\r
-                battle.AirControlLevel + 1];\r
+            else\r
+            {\r
+                UpdateFighterPower(Sniffer.IsCombinedFleet && battle.EnemyIsCombined);\r
+            }\r
+        }\r
+\r
+        private void UpdateEnemyFighterPower()\r
+        {\r
+            var fp = Sniffer.Battle.EnemyFighterPower;\r
+            labelEnemyFighterPower.Text = fp.AirCombat + fp.UnknownMark;\r
+            var toolTip = fp.AirCombat == fp.Interception ? "" : "防空: " + fp.Interception + fp.UnknownMark;\r
+            _toolTip.SetToolTip(labelEnemyFighterPower, toolTip);\r
+            _toolTip.SetToolTip(labelEnemyFighterPowerCaption, toolTip);\r
+        }\r
+\r
+        private void UpdateAirRaidFighterPower()\r
+        {\r
+            var fp = Sniffer.Battle.FighterPower;\r
+            labelFighterPower.Text = fp.Min.ToString();\r
+            var toolTop = fp.Diff ? fp.RangeString : "";\r
+            _toolTip.SetToolTip(labelFighterPower, toolTop);\r
+            _toolTip.SetToolTip(labelFighterPowerCaption, toolTop);\r
+        }\r
+\r
+        private static Color AirControlLevelColor(int level)\r
+        {\r
+            var colors = new[]\r
+            {\r
+                DefaultForeColor, DefaultForeColor, CUDColors.Blue, CUDColors.Green, CUDColors.Orange, CUDColors.Red\r
+            };\r
+            return colors[level + 1];\r
         }\r
 \r
         private void ShowResultRank()\r
         {\r
             var result = new[] {"完全S", "勝利S", "勝利A", "勝利B", "敗北C", "敗北D", "敗北E"};\r
-            labelResultRank.Text = result[(int)_sniffer.Battle.ResultRank];\r
+            labelResultRank.Text = result[(int)Sniffer.Battle.ResultRank];\r
         }\r
 \r
         private void labelResultRank_Click(object sender, EventArgs e)\r
@@ -752,35 +888,59 @@ namespace KancolleSniffer
 \r
             for (var i = 0; i < fuelSq.Length; i++)\r
             {\r
-                var stat = _sniffer.ChargeStatuses[i];\r
+                var stat = Sniffer.Fleets[i].ChargeStatus;\r
                 fuelSq[i].ImageIndex = stat.Fuel;\r
                 bullSq[i].ImageIndex = stat.Bull;\r
+                var text = stat.Empty ? "" : $"燃{stat.FuelRatio * 100:f1}% 弾{stat.BullRatio * 100:f1}%";\r
+                _toolTip.SetToolTip(fuelSq[i], text);\r
+                _toolTip.SetToolTip(bullSq[i], text);\r
             }\r
         }\r
 \r
         private void UpdateNDocLabels()\r
         {\r
-            _mainLabels.SetNDockLabels(_sniffer.NDock);\r
+            _ndockLabels.SetName(Sniffer.NDock);\r
+            SetNDockLabel();\r
         }\r
 \r
+        private void SetNDockLabel()\r
+        {\r
+            labelNDock.Text = (Config.ShowEndTime & TimerKind.NDock) != 0 ? "入渠終了" : "入渠";\r
+        }\r
 \r
         private void labelNDock_Click(object sender, EventArgs e)\r
         {\r
-            _config.ShowEndTime ^= TimerKind.NDock;\r
+            Config.ShowEndTime ^= TimerKind.NDock;\r
+            SetNDockLabel();\r
             UpdateTimers();\r
         }\r
 \r
         private void UpdateMissionLabels()\r
         {\r
-            foreach (var entry in\r
-                new[] {labelMissionName1, labelMissionName2, labelMissionName3}.Zip(_sniffer.Missions,\r
-                    (label, mission) => new {label, mission.Name}))\r
-                entry.label.Text = entry.Name;\r
+            var nameLabels = new[] {labelMissionName1, labelMissionName2, labelMissionName3};\r
+            var paramsLabels = new[] {labelMissionParams1, labelMissionParams2, labelMissionParams3};\r
+            var names = Sniffer.Missions.Select(mission => mission.Name).ToArray();\r
+            for (var i = 0; i < ShipInfo.FleetCount - 1; i++)\r
+            {\r
+                var fleetParams = Sniffer.Fleets[i + 1].MissionParameter;\r
+                var inPort = string.IsNullOrEmpty(names[i]);\r
+                paramsLabels[i].Visible = inPort;\r
+                paramsLabels[i].Text = fleetParams;\r
+                nameLabels[i].Text = names[i];\r
+                _toolTip.SetToolTip(nameLabels[i], inPort ? "" : fleetParams);\r
+            }\r
+            SetMissionLabel();\r
+        }\r
+\r
+        private void SetMissionLabel()\r
+        {\r
+            labelMission.Text = (Config.ShowEndTime & TimerKind.Mission) != 0 ? "遠征終了" : "遠征";\r
         }\r
 \r
         private void labelMission_Click(object sender, EventArgs e)\r
         {\r
-            _config.ShowEndTime ^= TimerKind.Mission;\r
+            Config.ShowEndTime ^= TimerKind.Mission;\r
+            SetMissionLabel();\r
             UpdateTimers();\r
         }\r
 \r
@@ -789,19 +949,19 @@ namespace KancolleSniffer
             var mission = new[] {labelMission1, labelMission2, labelMission3};\r
             for (var i = 0; i < mission.Length; i++)\r
             {\r
-                var entry = _sniffer.Missions[i];\r
+                var entry = Sniffer.Missions[i];\r
                 SetTimerColor(mission[i], entry.Timer, _now);\r
-                mission[i].Text = entry.Timer.ToString(_now, (_config.ShowEndTime & TimerKind.Mission) != 0);\r
+                mission[i].Text = entry.Timer.ToString(_now, (Config.ShowEndTime & TimerKind.Mission) != 0);\r
             }\r
-            for (var i = 0; i < _sniffer.NDock.Length; i++)\r
+            for (var i = 0; i < Sniffer.NDock.Length; i++)\r
             {\r
-                var entry = _sniffer.NDock[i];\r
-                _mainLabels.SetNDockTimer(i, entry.Timer, _now, (_config.ShowEndTime & TimerKind.NDock) != 0);\r
+                var entry = Sniffer.NDock[i];\r
+                _ndockLabels.SetTimer(i, entry.Timer, _now, (Config.ShowEndTime & TimerKind.NDock) != 0);\r
             }\r
             var kdock = new[] {labelConstruct1, labelConstruct2, labelConstruct3, labelConstruct4};\r
             for (var i = 0; i < kdock.Length; i++)\r
             {\r
-                var timer = _sniffer.KDock[i];\r
+                var timer = Sniffer.KDock[i];\r
                 SetTimerColor(kdock[i], timer, _now);\r
                 kdock[i].Text = timer.ToString(_now);\r
             }\r
@@ -812,20 +972,22 @@ namespace KancolleSniffer
 \r
         private void NotifyTimers()\r
         {\r
-            for (var i = 0; i < _sniffer.Missions.Length; i++)\r
+            for (var i = 0; i < Sniffer.Missions.Length; i++)\r
             {\r
-                var entry = _sniffer.Missions[i];\r
+                var entry = Sniffer.Missions[i];\r
+                if (entry.Name == "前衛支援任務" || entry.Name == "艦隊決戦支援任務")\r
+                    continue;\r
                 CheckAlarm("遠征終了", entry.Timer, i + 1, entry.Name);\r
             }\r
-            for (var i = 0; i < _sniffer.NDock.Length; i++)\r
+            for (var i = 0; i < Sniffer.NDock.Length; i++)\r
             {\r
-                var entry = _sniffer.NDock[i];\r
+                var entry = Sniffer.NDock[i];\r
                 CheckAlarm("入渠終了", entry.Timer, i, entry.Name);\r
             }\r
-            for (var i = 0; i < _sniffer.KDock.Length; i++)\r
+            for (var i = 0; i < Sniffer.KDock.Length; i++)\r
             {\r
-                var timer = _sniffer.KDock[i];\r
-                CheckAlarm("建造完了", timer, 0, $"第{i + 1:D}ドック");\r
+                var timer = Sniffer.KDock[i];\r
+                CheckAlarm("建造完了", timer, i, "");\r
             }\r
             NotifyCondTimers();\r
             NotifyAkashiTimer();\r
@@ -839,7 +1001,7 @@ namespace KancolleSniffer
                 SetNotification(key, fleet, subject);\r
                 return;\r
             }\r
-            var pre = TimeSpan.FromSeconds(_config.Notifications[key].PreliminaryPeriod);\r
+            var pre = TimeSpan.FromSeconds(Config.Notifications[key].PreliminaryPeriod);\r
             if (pre == TimeSpan.Zero)\r
                 return;\r
             if (timer.CheckAlarm(_prev + pre, _now + pre))\r
@@ -848,7 +1010,7 @@ namespace KancolleSniffer
 \r
         private void SetTimerColor(Label label, AlarmTimer timer, DateTime now)\r
         {\r
-            label.ForeColor = timer.IsFinished(now) ? CUDColor.Red : Color.Black;\r
+            label.ForeColor = timer.IsFinished(now) ? CUDColors.Red : Color.Black;\r
         }\r
 \r
         private void UpdateCondTimers()\r
@@ -856,13 +1018,13 @@ namespace KancolleSniffer
             DateTime timer;\r
             if (_combinedFleet)\r
             {\r
-                var timer1 = _sniffer.GetConditionTimer(0);\r
-                var timer2 = _sniffer.GetConditionTimer(1);\r
+                var timer1 = Sniffer.GetConditionTimer(0);\r
+                var timer2 = Sniffer.GetConditionTimer(1);\r
                 timer = timer2 > timer1 ? timer2 : timer1;\r
             }\r
             else\r
             {\r
-                timer = _sniffer.GetConditionTimer(_currentFleet);\r
+                timer = Sniffer.GetConditionTimer(_currentFleet);\r
             }\r
             if (timer == DateTime.MinValue)\r
             {\r
@@ -871,7 +1033,7 @@ namespace KancolleSniffer
                 return;\r
             }\r
             var span = TimeSpan.FromSeconds(Ceiling((timer - _now).TotalSeconds));\r
-            if (span >= TimeSpan.FromMinutes(9))\r
+            if (span >= TimeSpan.FromMinutes(9) && Config.NotifyConditions.Contains(40))\r
             {\r
                 labelCondTimerTitle.Text = "cond40まで";\r
                 labelCondTimer.Text = (span - TimeSpan.FromMinutes(9)).ToString(@"mm\:ss");\r
@@ -881,24 +1043,24 @@ namespace KancolleSniffer
             {\r
                 labelCondTimerTitle.Text = "cond49まで";\r
                 labelCondTimer.Text = (span >= TimeSpan.Zero ? span : TimeSpan.Zero).ToString(@"mm\:ss");\r
-                labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColor.Red : DefaultForeColor;\r
+                labelCondTimer.ForeColor = span <= TimeSpan.Zero ? CUDColors.Red : DefaultForeColor;\r
             }\r
         }\r
 \r
         private void NotifyCondTimers()\r
         {\r
-            var notice = _sniffer.GetConditionNotice(_prev, _now);\r
-            var pre = TimeSpan.FromSeconds(_config.Notifications["疲労回復"].PreliminaryPeriod);\r
+            var notice = Sniffer.GetConditionNotice(_prev, _now);\r
+            var pre = TimeSpan.FromSeconds(Config.Notifications["疲労回復"].PreliminaryPeriod);\r
             var preNotice = pre == TimeSpan.Zero\r
                 ? new int[ShipInfo.FleetCount]\r
-                : _sniffer.GetConditionNotice(_prev + pre, _now + pre);\r
+                : Sniffer.GetConditionNotice(_prev + pre, _now + pre);\r
             for (var i = 0; i < ShipInfo.FleetCount; i++)\r
             {\r
-                if (_config.NotifyConditions.Contains(notice[i]))\r
+                if (Config.NotifyConditions.Contains(notice[i]))\r
                 {\r
                     SetNotification("疲労回復" + notice[i], i, "cond" + notice[i]);\r
                 }\r
-                else if (_config.NotifyConditions.Contains(preNotice[i]))\r
+                else if (Config.NotifyConditions.Contains(preNotice[i]))\r
                 {\r
                     SetPreNotification("疲労回復" + preNotice[i], i, "cond" + notice[i]);\r
                 }\r
@@ -907,22 +1069,21 @@ namespace KancolleSniffer
 \r
         private void UpdateAkashiTimer()\r
         {\r
-            if (_config.UsePresetAkashi)\r
+            if (Config.UsePresetAkashi)\r
                 UpdatePresetAkashiTimer();\r
-            var statuses = _sniffer.GetShipStatuses(_currentFleet);\r
-            _mainLabels.SetAkashiTimer(statuses,\r
-                _sniffer.AkashiTimer.GetTimers(_currentFleet));\r
+            _mainLabels.SetAkashiTimer(Sniffer.Fleets[_currentFleet].ActualShips,\r
+                Sniffer.AkashiTimer.GetTimers(_currentFleet, _now));\r
         }\r
 \r
         private void UpdatePresetAkashiTimer()\r
         {\r
-            var akashi = _sniffer.AkashiTimer;\r
-            var span = akashi.PresetDeckTimer;\r
-            var color = span == TimeSpan.Zero && akashi.CheckPresetRepairing() ? CUDColor.Red : DefaultForeColor;\r
+            var akashi = Sniffer.AkashiTimer;\r
+            var span = akashi.GetPresetDeckTimer(_now);\r
+            var color = span == TimeSpan.Zero && akashi.CheckPresetRepairing() ? CUDColors.Red : DefaultForeColor;\r
             var text = span == TimeSpan.MinValue ? "" : span.ToString(@"mm\:ss");\r
             labelAkashiRepairTimer.ForeColor = color;\r
             labelAkashiRepairTimer.Text = text;\r
-            if (akashi.CheckPresetRepairing() && !akashi.CheckRepairing(_currentFleet))\r
+            if (akashi.CheckPresetRepairing() && !akashi.CheckRepairing(_currentFleet, _now))\r
             {\r
                 labelPresetAkashiTimer.ForeColor = color;\r
                 labelPresetAkashiTimer.Text = text;\r
@@ -936,14 +1097,14 @@ namespace KancolleSniffer
 \r
         private void NotifyAkashiTimer()\r
         {\r
-            var akashi = _sniffer.AkashiTimer;\r
+            var akashi = Sniffer.AkashiTimer;\r
             var msgs = akashi.GetNotice(_prev, _now);\r
             if (msgs.Length == 0)\r
             {\r
                 _notificationManager.StopRepeat("泊地修理");\r
                 return;\r
             }\r
-            if (!akashi.CheckRepairing() && !(akashi.CheckPresetRepairing() && _config.UsePresetAkashi))\r
+            if (!akashi.CheckRepairing(_now) && !(akashi.CheckPresetRepairing() && Config.UsePresetAkashi))\r
             {\r
                 _notificationManager.StopRepeat("泊地修理");\r
                 return;\r
@@ -963,7 +1124,7 @@ namespace KancolleSniffer
                 if (msgs[i].Completed != "")\r
                     SetNotification("泊地修理完了", i, msgs[i].Completed);\r
             }\r
-            var pre = TimeSpan.FromSeconds(_config.Notifications["泊地修理20分経過"].PreliminaryPeriod);\r
+            var pre = TimeSpan.FromSeconds(Config.Notifications["泊地修理20分経過"].PreliminaryPeriod);\r
             if (skipPreliminary || pre == TimeSpan.Zero)\r
                 return;\r
             if ((msgs = akashi.GetNotice(_prev + pre, _now + pre))[0].Proceeded == "20分経過しました。")\r
@@ -977,91 +1138,63 @@ namespace KancolleSniffer
 \r
         private void SetNotification(string key, int fleet, string subject)\r
         {\r
-            var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
+            var spec = Config.Notifications[_notificationManager.KeyToName(key)];\r
             _notificationManager.Enqueue(key, fleet, subject,\r
-                (spec.Flags & _config.NotificationFlags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
+                (spec.Flags & Config.NotificationFlags & NotificationType.Repeat) == 0 ? 0 : spec.RepeatInterval);\r
         }\r
 \r
         private void SetPreNotification(string key, int fleet, string subject)\r
         {\r
-            var spec = _config.Notifications[_notificationManager.KeyToName(key)];\r
+            var spec = Config.Notifications[_notificationManager.KeyToName(key)];\r
             if ((spec.Flags & NotificationType.Preliminary) != 0)\r
                 _notificationManager.Enqueue(key, fleet, subject, 0, true);\r
         }\r
 \r
         private void UpdateRepairList()\r
         {\r
-            panelRepairList.SetRepairList(_sniffer.RepairList);\r
+            panelRepairList.SetRepairList(Sniffer.RepairList);\r
+            _toolTip.SetToolTip(label31, new RepairShipCount(Sniffer.RepairList).ToString());\r
         }\r
 \r
         private void UpdateQuestList()\r
         {\r
-            var category = new[]\r
-            {\r
-                labelQuestColor1, labelQuestColor2, labelQuestColor3, labelQuestColor4, labelQuestColor5,\r
-                labelQuestColor6\r
-            };\r
-            var name = new[] {labelQuest1, labelQuest2, labelQuest3, labelQuest4, labelQuest5, labelQuest6};\r
-            var count = new[]\r
-            {\r
-                labelQuestCount1, labelQuestCount2, labelQuestCount3, labelQuestCount4, labelQuestCount5,\r
-                labelQuestCount6\r
-            };\r
-            var progress = new[]\r
-                {labelProgress1, labelProgress2, labelProgress3, labelProgress4, labelProgress5, labelProgress6};\r
-            var quests = _sniffer.Quests;\r
-            for (var i = 0; i < name.Length; i++)\r
-            {\r
-                if (i < quests.Length)\r
-                {\r
-                    category[i].BackColor = quests[i].Color;\r
-                    name[i].Text = quests[i].Name;\r
-                    progress[i].Text = $"{quests[i].Progress:D}%";\r
-                    _toolTipQuest.SetToolTip(name[i], quests[i].ToToolTip());\r
-                    var c = quests[i].Count;\r
-                    if (c.Id == 0)\r
-                    {\r
-                        count[i].Text = "";\r
-                        count[i].ForeColor = Color.Black;\r
-                        _toolTipCount.SetToolTip(count[i], "");\r
-                        continue;\r
-                    }\r
-                    count[i].Text = " " + c;\r
-                    count[i].ForeColor = c.Cleared ? CUDColor.Green : Color.Black;\r
-                    _toolTipCount.SetToolTip(count[i], c.ToToolTip());\r
-                }\r
-                else\r
-                {\r
-                    category[i].BackColor = DefaultBackColor;\r
-                    name[i].Text = count[i].Text = progress[i].Text = "";\r
-                    _toolTipQuest.SetToolTip(name[i], "");\r
-                    _toolTipCount.SetToolTip(count[i], "");\r
-                }\r
-            }\r
+            questPanel.Update(Sniffer.Quests);\r
+            labelQuestCount.Text = Sniffer.Quests.Length.ToString();\r
+            SetQuestNotification();\r
+        }\r
+\r
+        private void SetQuestNotification()\r
+        {\r
+            Sniffer.GetQuestNotifications(out var notify, out var stop);\r
+            foreach (var questName in notify)\r
+                SetNotification("任務達成", 0, questName);\r
+            foreach (var questName in stop)\r
+                _notificationManager.StopRepeat("任務達成", questName);\r
+            _notificationManager.Flash();\r
         }\r
 \r
         private void Alarm(string balloonTitle, string balloonMessage, string name)\r
         {\r
-            var flags = _config.Notifications[name].Flags;\r
-            var effective = _config.NotificationFlags & _config.Notifications[name].Flags;\r
+            var flags = Config.Notifications[name].Flags;\r
+            var effective = Config.NotificationFlags & Config.Notifications[name].Flags;\r
             if ((effective & NotificationType.FlashWindow) != 0)\r
                 Win32API.FlashWindow(Handle);\r
             if ((effective & NotificationType.ShowBaloonTip) != 0)\r
                 notifyIconMain.ShowBalloonTip(20000, balloonTitle, balloonMessage, ToolTipIcon.Info);\r
             if ((effective & NotificationType.PlaySound) != 0)\r
-                PlaySound(_config.Sounds[name], _config.Sounds.Volume);\r
-            if (_config.Pushbullet.On && (flags & NotificationType.Push) != 0)\r
+                PlaySound(Config.Sounds[name], Config.Sounds.Volume);\r
+            if (Config.Pushbullet.On && (flags & NotificationType.Push) != 0)\r
             {\r
                 Task.Run(() =>\r
                 {\r
-                    PushNotification.PushToPushbullet(_config.Pushbullet.Token, balloonTitle, balloonMessage);\r
+                    PushNotification.PushToPushbullet(Config.Pushbullet.Token, balloonTitle, balloonMessage);\r
                 });\r
             }\r
-            if (_config.Pushover.On && (flags & NotificationType.Push) != 0)\r
+            if (Config.Pushover.On && (flags & NotificationType.Push) != 0)\r
             {\r
                 Task.Run(() =>\r
                 {\r
-                    PushNotification.PushToPushover(_config.Pushover.ApiKey, _config.Pushover.UserKey,\r
+                    PushNotification.PushToPushover(Config.Pushover.ApiKey, Config.Pushover.UserKey,\r
                         balloonTitle, balloonMessage);\r
                 });\r
             }\r
@@ -1069,9 +1202,10 @@ namespace KancolleSniffer
 \r
         [DllImport("winmm.dll")]\r
         private static extern int mciSendString(String command,\r
-            StringBuilder buffer, int bufferSize, IntPtr hwndCallback);\r
+            StringBuilder buffer, int bufferSize, IntPtr hWndCallback);\r
 \r
 // ReSharper disable InconsistentNaming\r
+        // ReSharper disable once IdentifierTypo\r
         private const int MM_MCINOTIFY = 0x3B9;\r
 \r
         private const int MCI_NOTIFY_SUCCESSFUL = 1;\r
@@ -1105,10 +1239,14 @@ namespace KancolleSniffer
             };\r
             foreach (var a in labels)\r
             {\r
-                for (var fleet = 0; fleet < labels[0].Length; fleet++)\r
+                a[0].Tag = 0;\r
+                a[0].Click += labelFleet1_Click;\r
+                a[0].DoubleClick += labelFleet1_DoubleClick;\r
+                for (var fleet = 1; fleet < labels[0].Length; fleet++)\r
                 {\r
                     a[fleet].Tag = fleet;\r
                     a[fleet].Click += labelFleet_Click;\r
+                    a[fleet].DoubleClick += labelFleet_DoubleClick;\r
                 }\r
             }\r
         }\r
@@ -1119,29 +1257,82 @@ namespace KancolleSniffer
                 return;\r
             var fleet = (int)((Label)sender).Tag;\r
             if (_currentFleet == fleet)\r
-            {\r
-                if (fleet > 0)\r
-                    return;\r
-                _combinedFleet = _sniffer.CombinedFleetType > 0 && !_combinedFleet;\r
-                UpdatePanelShipInfo();\r
                 return;\r
-            }\r
             _combinedFleet = false;\r
             _currentFleet = fleet;\r
             UpdatePanelShipInfo();\r
         }\r
 \r
+        private readonly SemaphoreSlim _clickSemaphore = new SemaphoreSlim(1);\r
+        private readonly SemaphoreSlim _doubleClickSemaphore = new SemaphoreSlim(0);\r
+\r
+        private async void labelFleet1_Click(object sender, EventArgs e)\r
+        {\r
+            if (!_started)\r
+                return;\r
+            if (_currentFleet != 0)\r
+            {\r
+                labelFleet_Click(sender, e);\r
+                return;\r
+            }\r
+            if (!_clickSemaphore.Wait(0))\r
+                return;\r
+            try\r
+            {\r
+                if (await _doubleClickSemaphore.WaitAsync(SystemInformation.DoubleClickTime))\r
+                    return;\r
+            }\r
+            finally\r
+            {\r
+                _clickSemaphore.Release();\r
+            }\r
+            _combinedFleet = Sniffer.IsCombinedFleet && !_combinedFleet;\r
+            UpdatePanelShipInfo();\r
+        }\r
+\r
         private void labelFleet1_MouseHover(object sender, EventArgs e)\r
         {\r
-            labelFleet1.Text = _currentFleet == 0 && _sniffer.CombinedFleetType > 0 && !_combinedFleet ? "連合" : "第一";\r
+            labelFleet1.Text = _currentFleet == 0 && Sniffer.IsCombinedFleet && !_combinedFleet ? "連合" : "第一";\r
         }\r
 \r
         private void labelFleet1_MouseLeave(object sender, EventArgs e)\r
         {\r
-            labelFleet1.Text = _combinedFleet ? "連合" : "第一";\r
+            labelFleet1.Text = _combinedFleet ? CombinedName : "第一";\r
+        }\r
+\r
+        private void labelFleet_DoubleClick(object sender, EventArgs e)\r
+        {\r
+            if (!_started)\r
+                return;\r
+            var fleet = (int)((Label)sender).Tag;\r
+            var text = TextGenerator.GenerateFleetData(Sniffer, fleet);\r
+            CopyFleetText(text, (Label)sender);\r
+        }\r
+\r
+        private void labelFleet1_DoubleClick(object sender, EventArgs e)\r
+        {\r
+            if (!_started)\r
+                return;\r
+            _doubleClickSemaphore.Release();\r
+            var text = TextGenerator.GenerateFleetData(Sniffer, 0);\r
+            if (_combinedFleet)\r
+                text += TextGenerator.GenerateFleetData(Sniffer, 1);\r
+            CopyFleetText(text, (Label)sender);\r
         }\r
 \r
-        private readonly Color _activeButtonColor = Color.FromArgb(152, 179, 208);\r
+        private void CopyFleetText(string text, Label fleetButton)\r
+        {\r
+            if (string.IsNullOrEmpty(text))\r
+                return;\r
+            Clipboard.SetText(text);\r
+            _tooltipCopy.Active = true;\r
+            _tooltipCopy.Show("コピーしました。", fleetButton);\r
+            Task.Run(async () =>\r
+            {\r
+                await Task.Delay(1000);\r
+                _tooltipCopy.Active = false;\r
+            });\r
+        }\r
 \r
         private void labelBucketHistoryButton_Click(object sender, EventArgs e)\r
         {\r
@@ -1154,7 +1345,7 @@ namespace KancolleSniffer
             {\r
                 labelBucketHistory.Visible = true;\r
                 labelBucketHistory.BringToFront();\r
-                labelBucketHistoryButton.BackColor = _activeButtonColor;\r
+                labelBucketHistoryButton.BackColor = CustomColors.ActiveButtonColor;\r
             }\r
         }\r
 \r
@@ -1175,7 +1366,7 @@ namespace KancolleSniffer
             {\r
                 panelMaterialHistory.Visible = true;\r
                 panelMaterialHistory.BringToFront();\r
-                labelMaterialHistoryButton.BackColor = _activeButtonColor;\r
+                labelMaterialHistoryButton.BackColor = CustomColors.ActiveButtonColor;\r
             }\r
         }\r
 \r
@@ -1185,9 +1376,9 @@ namespace KancolleSniffer
             labelMaterialHistoryButton.BackColor = DefaultBackColor;\r
         }\r
 \r
-        public void ResetAchievemnt()\r
+        public void ResetAchievement()\r
         {\r
-            _sniffer.Achievement.Reset();\r
+            Sniffer.Achievement.Reset();\r
             UpdateItemInfo();\r
         }\r
 \r
@@ -1202,7 +1393,7 @@ namespace KancolleSniffer
             {\r
                 panelRepairList.Visible = true;\r
                 panelRepairList.BringToFront();\r
-                labelRepairListButton.BackColor = _activeButtonColor;\r
+                labelRepairListButton.BackColor = CustomColors.ActiveButtonColor;\r
             }\r
         }\r
 \r
@@ -1223,18 +1414,18 @@ namespace KancolleSniffer
 \r
         private void LogToolStripMenuItem_Click(object sender, EventArgs e)\r
         {\r
-            Process.Start("http://localhost:" + _config.Proxy.Listen + "/");\r
+            Process.Start("http://localhost:" + Config.Proxy.Listen + "/");\r
         }\r
 \r
         private void labelClearQuest_Click(object sender, EventArgs e)\r
         {\r
-            _sniffer.ClearQuests();\r
+            Sniffer.ClearQuests();\r
             UpdateQuestList();\r
         }\r
 \r
         private void labelClearQuest_MouseDown(object sender, MouseEventArgs e)\r
         {\r
-            labelClearQuest.BackColor = _activeButtonColor;\r
+            labelClearQuest.BackColor = CustomColors.ActiveButtonColor;\r
         }\r
 \r
         private void labelClearQuest_MouseUp(object sender, MouseEventArgs e)\r
@@ -1245,6 +1436,8 @@ namespace KancolleSniffer
         private void labelQuest_DoubleClick(object sender, EventArgs e)\r
         {\r
             var label = (Label)sender;\r
+            if (string.IsNullOrEmpty(label.Text))\r
+                return;\r
             Clipboard.SetText(label.Text);\r
             _tooltipCopy.Active = true;\r
             _tooltipCopy.Show("コピーしました。", label);\r
@@ -1265,6 +1458,9 @@ namespace KancolleSniffer
             catch (FileNotFoundException)\r
             {\r
             }\r
+            catch (Win32Exception)\r
+            {\r
+            }\r
         }\r
     }\r
 }
\ No newline at end of file