OSDN Git Service

補給艦で「強行輸送艦隊、抜錨!」がカウントされないのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / QuestInfo.cs
index 7d25f99..7b3ed40 100644 (file)
-// Copyright (C) 2013, 2015 Kazuhiro Fujieda <fujieda@users.sourceforge.jp>\r
+// Copyright (C) 2013, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
 // \r
-// This program is part of KancolleSniffer.\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
 //\r
-// KancolleSniffer is free software: you can redistribute it and/or modify\r
-// it under the terms of the GNU General Public License as published by\r
-// the Free Software Foundation, either version 3 of the License, or\r
-// (at your option) any later version.\r
+//    http://www.apache.org/licenses/LICENSE-2.0\r
 //\r
-// This program is distributed in the hope that it will be useful,\r
-// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-// GNU General Public License for more details.\r
-//\r
-// You should have received a copy of the GNU General Public License\r
-// along with this program; if not, see <http://www.gnu.org/licenses/>.\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
 \r
 using System;\r
 using System.Collections.Generic;\r
+using System.Drawing;\r
 using System.Linq;\r
+using System.Windows.Forms;\r
+using System.Xml.Serialization;\r
+using static System.Math;\r
 \r
 namespace KancolleSniffer\r
 {\r
-    public class QuestInfo\r
+    public class QuestStatus\r
     {\r
-        private DateTime _lastCreared;\r
-        private readonly SortedDictionary<int, NameAndProgress> _quests = new SortedDictionary<int, NameAndProgress>();\r
+        public int Id { get; set; }\r
+        public int Category { get; set; }\r
+        public string Name { get; set; }\r
+        public string Detail { get; set; }\r
+        public int[] Material { get; set; }\r
+        public int Progress { get; set; }\r
+\r
+        [XmlIgnore]\r
+        public QuestCount Count { get; set; }\r
 \r
-        public int QuestCount { get; set; }\r
+        [XmlIgnore]\r
+        public Color Color { get; set; }\r
 \r
-        public void Inspect(dynamic json)\r
+        public string ToToolTip() =>\r
+            Detail +\r
+            (Material == null || Material.All(x => x == 0)\r
+                ? ""\r
+                : "\r\n" + string.Join(" ",\r
+                      new[] {"燃", "弾", "鋼", "ボ", "建造", "修復", "開発", "改修"}\r
+                          .Zip(Material, (m, num) => num == 0 ? "" : m + num)\r
+                          .Where(s => !string.IsNullOrEmpty(s))));\r
+    }\r
+\r
+    public enum QuestInterval\r
+    {\r
+        Other,\r
+        Daily,\r
+        Weekly,\r
+        Monthly,\r
+        Quarterly\r
+    }\r
+\r
+    public class QuestSpec\r
+    {\r
+        public QuestInterval Interval { get; set; }\r
+        public int Max { get; set; }\r
+        public int[] MaxArray { get; set; }\r
+        public bool AdjustCount { get; set; } = true;\r
+        public int Shift { get; set; }\r
+        public int[] Material { get; set; }\r
+    }\r
+\r
+    public class QuestSortie : QuestSpec\r
+    {\r
+        public string Rank { get; set; }\r
+        public int[] Maps { get; set; }\r
+        public int[] ShipTypes { get; set; }\r
+\r
+        public static int CompareRank(string a, string b)\r
+        {\r
+            const string ranks = "SABCDE";\r
+            return ranks.IndexOf(a, StringComparison.Ordinal) -\r
+                   ranks.IndexOf(b, StringComparison.Ordinal);\r
+        }\r
+\r
+        public bool Check(string rank, int map, bool boss)\r
         {\r
-            var resetTime = DateTime.Today.AddHours(5);\r
-            if (DateTime.Now >= resetTime && _lastCreared < resetTime)\r
+            return (Rank == null || CompareRank(rank, Rank) <= 0) &&\r
+                   (Maps == null || Maps.Contains(map) && boss);\r
+        }\r
+    }\r
+\r
+    public class QuestEnemyType : QuestSpec\r
+    {\r
+        public int[] EnemyType { get; set; } = new int[0];\r
+\r
+        public int CountResult(IEnumerable<ShipStatus> enemyResult) =>\r
+            enemyResult.Count(ship => ship.NowHp == 0 && EnemyType.Contains(ship.Spec.ShipType));\r
+    }\r
+\r
+    public class QuestPractice : QuestSpec\r
+    {\r
+        public bool Win { get; set; }\r
+        public bool Check(string rank) => !Win || QuestSortie.CompareRank(rank, "B") <= 0;\r
+    }\r
+\r
+    public class QuestMission : QuestSpec\r
+    {\r
+        public int[] Ids { get; set; }\r
+        public bool Check(int id) => Ids == null || Ids.Contains(id);\r
+    }\r
+\r
+    public class QuestDestroyItem : QuestSpec\r
+    {\r
+        public int[] Items { get; set; }\r
+        public bool Check(int id) => Items == null || Items.Contains(id);\r
+    }\r
+\r
+    public class QuestPowerup : QuestSpec\r
+    {\r
+    }\r
+\r
+    public class QuestCount\r
+    {\r
+        public int Id { get; set; }\r
+        public int Now { get; set; }\r
+        public int[] NowArray { get; set; }\r
+\r
+        [XmlIgnore]\r
+        public QuestSpec Spec { get; set; }\r
+\r
+        public bool AdjustCount(int progress)\r
+        {\r
+            if (!Spec.AdjustCount)\r
+                return false;\r
+            if (NowArray != null)\r
             {\r
-                _quests.Clear(); // 前日に未消化のデイリーを消す。\r
-                _lastCreared = DateTime.Now;\r
+                if (progress != 100)\r
+                    return false;\r
+                NowArray = NowArray.Zip(Spec.MaxArray, Max).ToArray();\r
+                return true;\r
             }\r
+            var next = 0;\r
+            switch (progress)\r
+            {\r
+                case 0:\r
+                    next = 50;\r
+                    break;\r
+                case 50:\r
+                    next = 80;\r
+                    break;\r
+                case 80:\r
+                    next = 100;\r
+                    break;\r
+                case 100:\r
+                    next = 100000;\r
+                    break;\r
+            }\r
+            var now = Now + Spec.Shift;\r
+            var max = Spec.Max + Spec.Shift;\r
+            var low = (int)Ceiling(max * progress / 100.0);\r
+            if (low >= max && progress != 100)\r
+                low = max - 1;\r
+            var high = (int)Ceiling(max * next / 100.0);\r
+            if (now < low)\r
+            {\r
+                Now = low - Spec.Shift;\r
+                return true;\r
+            }\r
+            if (now >= high)\r
+            {\r
+                Now = high - 1 - Spec.Shift;\r
+                return true;\r
+            }\r
+            return false;\r
+        }\r
+\r
+        public override string ToString()\r
+        {\r
+            if (Id == 426 || Id == 854 || Id == 873)\r
+                return $"{NowArray.Count(n => n >= 1)}/{Spec.MaxArray.Length}";\r
+            return NowArray != null\r
+                ? string.Join(" ", NowArray.Zip(Spec.MaxArray, (n, m) => $"{n}/{m}"))\r
+                : $"{Now}/{Spec.Max}";\r
+        }\r
+\r
+        public string ToToolTip()\r
+        {\r
+            switch (Id)\r
+            {\r
+                case 426:\r
+                    return string.Join(" ",\r
+                        new[] {"警備任務", "対潜警戒任務", "海上護衛任務", "強硬偵察任務"}\r
+                            .Zip(NowArray, (mission, n) => n >= 1 ? mission : "")\r
+                            .Where(s => !string.IsNullOrEmpty(s)));\r
+                case 428:\r
+                    return string.Join(" ",\r
+                        new[] {"対潜警戒任務", "海峡警備行動", "長時間対潜警戒"}.Zip(NowArray, (mission, n) => n >= 1 ? mission + n : "")\r
+                            .Where(s => !string.IsNullOrEmpty(s)));\r
+                case 854:\r
+                    return string.Join(" ",\r
+                        new[] {"2-4", "6-1", "6-3", "6-4"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
+                            .Where(s => !string.IsNullOrEmpty(s)));\r
+                case 873:\r
+                    return string.Join(" ",\r
+                        new[] {"3-1", "3-2", "3-3"}.Zip(NowArray, (map, n) => n >= 1 ? map : "")\r
+                            .Where(s => !string.IsNullOrEmpty(s)));\r
+            }\r
+            return "";\r
+        }\r
+\r
+        public bool Cleared => NowArray?.Zip(Spec.MaxArray, (n, m) => n >= m).All(x => x) ?? Now >= Spec.Max;\r
+    }\r
+\r
+    // @formatter:off\r
+    public class QuestCountList\r
+    {\r
+        private const QuestInterval Daily = QuestInterval.Daily;\r
+        private const QuestInterval Weekly = QuestInterval.Weekly;\r
+        private const QuestInterval Monthly = QuestInterval.Monthly;\r
+        private const QuestInterval Quarterly = QuestInterval.Quarterly;\r
+\r
+        /// <summary>\r
+        /// このテーブルは七四式電子観測儀を参考に作成した。\r
+        /// https://github.com/andanteyk/ElectronicObserver/blob/develop/ElectronicObserver/Data/Quest/QuestProgressManager.cs\r
+        /// </summary>\r
+        private static readonly Dictionary<int, QuestSpec> QuestSpecs = new Dictionary<int, QuestSpec>\r
+        {\r
+            {201, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 0, 1, 0}}}, // 201: 敵艦隊を撃滅せよ!\r
+            {216, new QuestSortie {Interval = Daily, Max = 1, Rank = "B", Material = new[] {0, 1, 1, 0}}}, // 216: 敵艦隊主力を撃滅せよ!\r
+            {210, new QuestSortie {Interval = Daily, Max = 10, Material = new[] {0, 0, 1, 0}}}, // 210: 敵艦隊を10回邀撃せよ!\r
+            {211, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 211: 敵空母を3隻撃沈せよ!\r
+            {212, new QuestEnemyType {Interval = Daily, Max = 5, EnemyType = new[] {15}, Material = new[] {0, 0, 2, 0}}}, // 212: 敵輸送船団を叩け!\r
+            {218, new QuestEnemyType {Interval = Daily, Max = 3, EnemyType = new[] {15}, Material = new[] {0, 1, 1, 0}}}, // 218: 敵補給艦を3隻撃沈せよ!\r
+            {226, new QuestSortie {Interval = Daily, Max = 5, Rank = "B", Maps = new[] {21, 22, 23, 24, 25}, Material = new[] {1, 1, 0, 0}}}, // 226: 南西諸島海域の制海権を握れ!\r
+            {230, new QuestEnemyType {Interval = Daily, Max = 6, EnemyType = new[] {13}, Material = new[] {0, 1, 0, 0}}}, // 230: 敵潜水艦を制圧せよ!\r
+\r
+            {213, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {15}, Material = new[] {0, 0, 3, 0}}}, // 213: 海上通商破壊作戦\r
+            {214, new QuestSpec {Interval = Weekly, MaxArray = new[] {36, 6, 24, 12}, Material = new[] {2, 0, 2, 0}}}, // 214: あ号作戦\r
+            {220, new QuestEnemyType {Interval = Weekly, Max = 20, EnemyType = new[] {7, 11}, Material = new[] {0, 2, 0, 0}}}, // 220: い号作戦\r
+            {221, new QuestEnemyType {Interval = Weekly, Max = 50, EnemyType = new[] {15}, Material = new[] {0, 3, 0, 0}}}, // 221: ろ号作戦\r
+            {228, new QuestEnemyType {Interval = Weekly, Max = 15, EnemyType = new[] {13}, Material = new[] {0, 2, 0, 1}}}, // 228: 海上護衛戦\r
+            {229, new QuestSortie {Interval = Weekly, Max = 12, Rank = "B", Maps = new[] {41, 42, 43, 44, 45}, Material = new[] {0, 0, 2, 0}}}, // 229: 敵東方艦隊を撃滅せよ!\r
+            {241, new QuestSortie {Interval = Weekly, Max = 5, Rank = "B", Maps = new[] {33, 34, 35}, Material = new[] {0, 0, 3, 3}}}, // 241: 敵北方艦隊主力を撃滅せよ!\r
+            {242, new QuestSortie {Interval = Weekly, Max = 1, Rank = "B", Maps = new[] {44}, Material = new[] {0, 1, 1, 0}}}, // 242: 敵東方中枢艦隊を撃破せよ!\r
+            {243, new QuestSortie {Interval = Weekly, Max = 2, Rank = "S", Maps = new[] {52}, Material = new[] {0, 0, 2, 2}}}, // 243: 南方海域珊瑚諸島沖の制空権を握れ!\r
+            {249, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 5, 0}}}, // 249: 「第五戦隊」出撃せよ!\r
+            {256, new QuestSortie {Interval = Monthly, Max = 3, Rank = "S", Maps = new[] {61}, Material = new[] {0, 0, 0, 0}}}, // 256: 「潜水艦隊」出撃せよ!\r
+            {257, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 0, 3}}}, // 257: 「水雷戦隊」南西へ!\r
+            {259, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 3, 0, 4}}}, // 259: 「水上打撃部隊」南方へ!\r
+            {261, new QuestSortie {Interval = Weekly, Max = 3, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 0, 3}}}, // 261: 海上輸送路の安全確保に努めよ!\r
+            {265, new QuestSortie {Interval = Monthly, Max = 10, Rank = "A", Maps = new[] {15}, Material = new[] {0, 0, 5, 3}}}, // 265: 海上護衛強化月間\r
+            {266, new QuestSpec {Interval = Monthly, Max = 1, Material = new[] {0, 0, 4, 2}}}, // 266: 「水上反撃部隊」突入せよ!\r
+\r
+            {822, new QuestSortie {Interval = Quarterly, Max = 2, Rank = "S", Maps = new[] {24}, Material = new[] {0, 0, 0, 5}}}, // 822: 沖ノ島海域迎撃戦\r
+            {854, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 0, 4}}}, // 854: 戦果拡張任務!「Z作戦」前段作戦\r
+            {861, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 4, 0, 0}}}, // 861: 強行輸送艦隊、抜錨!\r
+            {862, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 8, 4}}}, // 862: 前線の航空偵察を実施せよ!\r
+            {873, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1}, Material = new[] {0, 0, 0, 0}}}, // 873: 北方海域警備を実施せよ!\r
+            {875, new QuestSpec {Interval = Quarterly, Max = 2, Material = new[] {0, 0, 0, 0}}}, // 875: 精鋭「三一駆」、鉄底海域に突入せよ!\r
+\r
+            {303, new QuestPractice {Interval = Daily, Max = 3, Win = false, Material = new[] {1, 0, 0, 0}}}, // 303: 「演習」で練度向上!\r
+            {304, new QuestPractice {Interval = Daily, Max = 5, Win = true, Material = new[] {0, 0, 1, 0}}}, // 304: 「演習」で他提督を圧倒せよ!\r
+            {302, new QuestPractice {Interval = Weekly, Max = 20, Win = true, Material = new[] {0, 0, 2, 1}}}, // 302: 大規模演習\r
+            {311, new QuestPractice {Interval = Daily, Max = 7, Win = true, Material = new[] {0, 2, 0, 0}}}, // 311: 精鋭艦隊演習\r
+\r
+            {402, new QuestMission {Interval = Daily, Max = 3, Material = new[] {0, 0, 1, 0}}}, // 402: 「遠征」を3回成功させよう!\r
+            {403, new QuestMission {Interval = Daily, Max = 10, Material = new[] {0, 0, 0, 0}}}, // 403: 「遠征」を10回成功させよう!\r
+            {404, new QuestMission {Interval = Weekly, Max = 30, Material = new[] {0, 0, 3, 0}}}, // 404: 大規模遠征作戦、発令!\r
+            {410, new QuestMission {Interval = Weekly, Max = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 0, 0}}}, // 410: 南方への輸送作戦を成功させよ!\r
+            {411, new QuestMission {Interval = Weekly, Max = 6, Shift = 1, Ids = new[] {37, 38}, Material = new[] {0, 0, 2, 1}}}, // 411: 南方への鼠輸送を継続実施せよ!\r
+            {424, new QuestMission {Interval = Monthly, Max = 4, Shift = 1, Ids = new[] {5}, Material = new[] {0, 0, 0, 0}}}, // 424: 輸送船団護衛を強化せよ!\r
+            {426, new QuestSpec {Interval = Quarterly, MaxArray = new[] {1, 1, 1, 1}, Material = new[] {0, 0, 4, 0}}}, // 426: 海上通商航路の警戒を厳とせよ!\r
+            {428, new QuestSpec {Interval = Quarterly, MaxArray = new[] {2, 2, 2}, Material = new[] {0, 0, 0, 3}}}, // 428: 近海に侵入する敵潜を制圧せよ!\r
+\r
+            {503, new QuestSpec {Interval = Daily, Max = 5, Material = new[] {0, 2, 0, 0}}}, // 503: 艦隊大整備!\r
+            {504, new QuestSpec {Interval = Daily, Max = 15, Material = new[] {1, 0, 1, 0}}}, // 504: 艦隊酒保祭り!\r
+\r
+            {605, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {1, 0, 1, 0}}}, // 605: 新装備「開発」指令\r
+            {606, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 1, 1, 0}}}, // 606: 新造艦「建造」指令\r
+            {607, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {0, 0, 2, 0}}}, // 607: 装備「開発」集中強化!\r
+            {608, new QuestSpec {Interval = Daily, Max = 3, Shift = 1, Material = new[] {1, 0, 2, 0}}}, // 608: 艦娘「建造」艦隊強化!\r
+            {609, new QuestSpec {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 609: 軍縮条約対応!\r
+            {619, new QuestSpec {Interval = Daily, Max = 1, Material = new[] {0, 0, 0, 1}}}, // 619: 装備の改修強化\r
+\r
+            {613, new QuestSpec {Interval = Weekly, Max = 24, Material = new[] {0, 0, 0, 0}}}, // 613: 資源の再利用\r
+            {638, new QuestDestroyItem {Interval = Weekly, Max = 6, Items = new[] {21}, Material = new[] {0, 0, 2, 1}}}, // 638: 対空機銃量産\r
+            {663, new QuestDestroyItem {Interval = Quarterly, Max = 10, Items = new[] {3}, Material = new[] {0, 0, 3, 0}}}, // 663: 新型艤装の継続研究\r
+            {673, new QuestDestroyItem {Interval = Daily, Max = 4, Items = new[] {1}, Shift = 1, Material = new[] {0, 0, 1, 0}}}, // 673: 装備開発力の整備\r
+            {674, new QuestDestroyItem {Interval = Daily, Max = 3, Items = new[] {21}, Shift = 2, Material = new[] {0, 1, 1, 0}}}, // 674: 工廠環境の整備\r
+            {675, new QuestSpec {Interval = Quarterly, MaxArray = new[] {6, 4}, Material = new[] {0, 0, 0, 0}}}, // 675: 運用装備の統合整備\r
+            {676, new QuestSpec {Interval = Weekly, MaxArray = new[] {3, 3, 1}, Material = new[] {0, 1, 7, 0}}}, // 676: 装備開発力の集中整備\r
+            {677, new QuestSpec {Interval = Weekly, MaxArray = new[] {4, 2, 3}, Material = new[] {0, 5, 0, 0}}}, // 677: 継戦支援能力の整備\r
+\r
+            {702, new QuestPowerup {Interval = Daily, Max = 2, Material = new[] {0, 1, 0, 0}}}, // 702: 艦の「近代化改修」を実施せよ!\r
+            {703, new QuestPowerup {Interval = Weekly, Max = 15, Material = new[] {1, 0, 2, 0}}} // 703: 「近代化改修」を進め、戦備を整えよ!\r
+        };\r
+        // @formatter:on\r
+\r
+        private readonly Dictionary<int, QuestCount> _countDict = new Dictionary<int, QuestCount>();\r
+\r
+        public QuestCount GetCount(int id)\r
+        {\r
+            if (_countDict.TryGetValue(id, out var value))\r
+                return value;\r
+            if (QuestSpecs.TryGetValue(id, out var spec))\r
+            {\r
+                var nowArray = spec.MaxArray?.Select(x => 0).ToArray();\r
+                return _countDict[id] = new QuestCount\r
+                {\r
+                    Id = id,\r
+                    Now = 0,\r
+                    NowArray = nowArray,\r
+                    Spec = spec\r
+                };\r
+            }\r
+            return new QuestCount {Spec = new QuestSpec {Material = new int[0], AdjustCount = false}};\r
+        }\r
+\r
+        public void Remove(int id)\r
+        {\r
+            _countDict.Remove(id);\r
+        }\r
+\r
+        public void Remove(QuestInterval interval)\r
+        {\r
+            foreach (var id in\r
+                _countDict.Where(pair => pair.Value.Spec.Interval == interval).Select(pair => pair.Key).ToArray())\r
+            {\r
+                _countDict.Remove(id);\r
+            }\r
+        }\r
+\r
+        public IEnumerable<QuestCount> CountList\r
+        {\r
+            get => _countDict.Values.Where(c => c.Now > 0 || (c.NowArray?.Any(n => n > 0) ?? false));\r
+            set\r
+            {\r
+                if (value == null)\r
+                    return;\r
+                foreach (var count in value)\r
+                {\r
+                    count.Spec = QuestSpecs[count.Id];\r
+                    _countDict[count.Id] = count;\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    public class QuestInfo : IHaveState\r
+    {\r
+        private readonly SortedDictionary<int, QuestStatus> _quests = new SortedDictionary<int, QuestStatus>();\r
+        private readonly QuestCountList _countList = new QuestCountList();\r
+        private readonly ItemInfo _itemInfo;\r
+        private readonly BattleInfo _battleInfo;\r
+        private readonly Func<DateTime> _nowFunc = () => DateTime.Now;\r
+        private DateTime _lastReset;\r
+\r
+        private readonly Color[] _color =\r
+        {\r
+            Color.FromArgb(60, 141, 76), Color.FromArgb(232, 57, 41), Color.FromArgb(136, 204, 120),\r
+            Color.FromArgb(52, 147, 185), Color.FromArgb(220, 198, 126), Color.FromArgb(168, 111, 76),\r
+            Color.FromArgb(200, 148, 231), Color.FromArgb(232, 57, 41)\r
+        };\r
+\r
+        public int AcceptMax { get; set; } = 5;\r
+\r
+        public QuestStatus[] Quests => _quests.Values.ToArray();\r
+\r
+        public QuestInfo(ItemInfo itemInfo, BattleInfo battleInfo, Func<DateTime> nowFunc = null)\r
+        {\r
+            _itemInfo = itemInfo;\r
+            _battleInfo = battleInfo;\r
+            if (nowFunc != null)\r
+                _nowFunc = nowFunc;\r
+        }\r
+\r
+        public void InspectQuestList(dynamic json)\r
+        {\r
+            ResetQuests();\r
             if (json.api_list == null)\r
                 return;\r
             for (var i = 0; i < 2; i++)\r
@@ -48,7 +386,10 @@ namespace KancolleSniffer
                     var id = (int)entry.api_no;\r
                     var state = (int)entry.api_state;\r
                     var progress = (int)entry.api_progress_flag;\r
+                    var cat = (int)entry.api_category;\r
                     var name = (string)entry.api_title;\r
+                    var detail = ((string)entry.api_detail).Replace("<br>", "\r\n");\r
+                    var material = (int[])entry.api_get_material;\r
 \r
                     switch (progress)\r
                     {\r
@@ -63,16 +404,19 @@ namespace KancolleSniffer
                     }\r
                     switch (state)\r
                     {\r
-                        case 2:\r
-                            _quests[id] = new NameAndProgress {Name = name, Progress = progress};\r
-                            break;\r
                         case 1:\r
+                            if (_quests.Remove(id))\r
+                                NeedSave = true;\r
+                            break;\r
                         case 3:\r
-                            _quests.Remove(id);\r
-                            continue;\r
+                            progress = 100;\r
+                            goto case 2;\r
+                        case 2:\r
+                            AddQuest(id, cat, name, detail, material, progress, true);\r
+                            break;\r
                     }\r
                 }\r
-                if (_quests.Count <= QuestCount)\r
+                if (_quests.Count <= AcceptMax)\r
                     break;\r
                 /*\r
                  * ほかのPCで任務を達成した場合、任務が消えずに受領した任務の数が_questCountを超えることがある。\r
@@ -82,15 +426,446 @@ namespace KancolleSniffer
             }\r
         }\r
 \r
-        public NameAndProgress[] Quests\r
+        private void AddQuest(int id, int category, string name, string detail, int[] material, int progress,\r
+            bool adjustCount)\r
+        {\r
+            var count = _countList.GetCount(id);\r
+            if (adjustCount)\r
+            {\r
+                if (count.AdjustCount(progress))\r
+                    NeedSave = true;\r
+            }\r
+            _quests[id] = new QuestStatus\r
+            {\r
+                Id = id,\r
+                Category = category,\r
+                Name = name,\r
+                Detail = detail,\r
+                Material = adjustCount ? material?.Concat(count.Spec.Material).ToArray() : material,\r
+                Count = count,\r
+                Progress = progress,\r
+                Color = category <= _color.Length ? _color[category - 1] : Control.DefaultBackColor\r
+            };\r
+        }\r
+\r
+        public void ClearQuests()\r
         {\r
-            get { return _quests.Values.ToArray(); }\r
+            _quests.Clear();\r
+        }\r
+\r
+        private void ResetQuests()\r
+        {\r
+            var now = _nowFunc();\r
+            var daily = now.Date.AddHours(5);\r
+            if (!(_lastReset < daily && daily <= now))\r
+                return;\r
+            _quests.Clear(); // 前日に未消化のデイリーを消す。\r
+            _countList.Remove(QuestInterval.Daily);\r
+            var weekly = now.Date.AddDays(-((6 + (int)now.DayOfWeek) % 7)).AddHours(5);\r
+            if (_lastReset < weekly && weekly <= now)\r
+                _countList.Remove(QuestInterval.Weekly);\r
+            var monthly = new DateTime(now.Year, now.Month, 1, 5, 0, 0);\r
+            if (_lastReset < monthly && monthly <= now)\r
+                _countList.Remove(QuestInterval.Monthly);\r
+            var season = now.Month / 3;\r
+            var quarterly = new DateTime(now.Year - (season == 0 ? 1 : 0), (season == 0 ? 12 : season * 3), 1, 5, 0, 0);\r
+            if (_lastReset < quarterly && quarterly <= now)\r
+                _countList.Remove(QuestInterval.Quarterly);\r
+            _lastReset = now;\r
+            NeedSave = true;\r
+        }\r
+\r
+\r
+        private int _map;\r
+        private bool _boss;\r
+\r
+        public void InspectMapStart(dynamic json)\r
+        {\r
+            if (_quests.TryGetValue(214, out var ago)) // あ号\r
+                ago.Count.NowArray[0]++;\r
+            InspectMapNext(json);\r
+        }\r
+\r
+        public void InspectMapNext(dynamic json)\r
+        {\r
+            _map = (int)json.api_maparea_id * 10 + (int)json.api_mapinfo_no;\r
+            _boss = (int)json.api_event_id == 5;\r
+\r
+            if (_quests.TryGetValue(861, out var q861))\r
+            {\r
+                if (_map == 16 && (int)json.api_event_id == 8)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
+                        .ToArray();\r
+                    if (fleet.Count(s => s == 10 || s == 22) == 2)\r
+                        IncrementCount(q861.Count);\r
+                }\r
+            }\r
         }\r
 \r
-        public struct NameAndProgress\r
+        public void InspectBattleResult(dynamic json)\r
         {\r
-            public string Name { get; set; }\r
-            public int Progress { get; set; }\r
+            var rank = json.api_win_rank;\r
+            foreach (var quest in _quests.Values)\r
+            {\r
+                var count = quest.Count;\r
+                switch (count.Spec)\r
+                {\r
+                    case QuestSortie sortie:\r
+                        if (count.Id == 216 && !_boss || sortie.Check(rank, _map, _boss))\r
+                            IncrementCount(count);\r
+                        break;\r
+                    case QuestEnemyType enemyType:\r
+                        var num = enemyType.CountResult(\r
+                            _battleInfo.Result.Enemy.Main.Concat(_battleInfo.Result.Enemy.Guard));\r
+                        if (num > 0)\r
+                            AddCount(count, num);\r
+                        break;\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(214, out var ago))\r
+            {\r
+                var array = ago.Count.NowArray;\r
+                if (_boss)\r
+                {\r
+                    array[2]++;\r
+                    if (QuestSortie.CompareRank(rank, "B") <= 0)\r
+                        array[3]++;\r
+                    NeedSave = true;\r
+                }\r
+                if (rank == "S")\r
+                {\r
+                    array[1]++;\r
+                    NeedSave = true;\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(249, out var q249))\r
+            {\r
+                if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id)\r
+                        .ToArray();\r
+                    if (fleet.Intersect(new[] {62, 63, 64, 265, 266, 268, 319, 192, 194}).Count() == 3)\r
+                        IncrementCount(q249.Count);\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(257, out var q257))\r
+            {\r
+                if (_map == 14 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
+                        .ToArray();\r
+                    if (fleet[0] == 3 && fleet.Count(s => s == 3) <= 3 && fleet.All(s => s == 2 || s == 3))\r
+                        IncrementCount(q257.Count);\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(259, out var q259))\r
+            {\r
+                if (_map == 51 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec).ToArray();\r
+                    var senkan = new[]\r
+                    {\r
+                        131, 136, // 大和\r
+                        143, 148, 546, // 武蔵\r
+                        80, 275, 541, // 長門\r
+                        81, 276, // 陸奥\r
+                        26, 286, 411, // 扶桑\r
+                        27, 287, 412, // 山城\r
+                        77, 82, // 伊勢\r
+                        87, 88 // 日向\r
+                    };\r
+                    if (fleet.Select(s => s.Id).Intersect(senkan).Count() == 3 &&\r
+                        fleet.Count(s => s.ShipType == 3) > 0)\r
+                    {\r
+                        IncrementCount(q259.Count);\r
+                    }\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(266, out var q266))\r
+            {\r
+                if (_map == 25 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType)\r
+                        .ToArray();\r
+                    if (fleet[0] == 2 && fleet.OrderBy(x => x).SequenceEqual(new[] {2, 2, 2, 2, 3, 5}))\r
+                        IncrementCount(q266.Count);\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(854, out var opz) && _boss)\r
+            {\r
+                var array = opz.Count.NowArray;\r
+                switch (_map)\r
+                {\r
+                    case 24 when QuestSortie.CompareRank(rank, "A") <= 0:\r
+                        array[0]++;\r
+                        NeedSave = true;\r
+                        break;\r
+                    case 61 when QuestSortie.CompareRank(rank, "A") <= 0:\r
+                        array[1]++;\r
+                        NeedSave = true;\r
+                        break;\r
+                    case 63 when QuestSortie.CompareRank(rank, "A") <= 0:\r
+                        array[2]++;\r
+                        NeedSave = true;\r
+                        break;\r
+                    case 64 when QuestSortie.CompareRank(rank, "S") <= 0:\r
+                        array[3]++;\r
+                        NeedSave = true;\r
+                        break;\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(862, out var q862))\r
+            {\r
+                if (_map == 63 && _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.ShipType).ToArray();\r
+                    if (fleet.Count(s => s == 3) == 2 && fleet.Count(s => s == 16) == 1)\r
+                        IncrementCount(q862.Count);\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(873, out var q873))\r
+            {\r
+                if (_battleInfo.Result.Friend.Main.Count(s => s.NowHp > 0 && s.Spec.ShipType == 3) >= 1 &&\r
+                    _boss && QuestSortie.CompareRank(rank, "A") <= 0)\r
+                {\r
+                    var array = q873.Count.NowArray;\r
+                    switch (_map)\r
+                    {\r
+                        case 31:\r
+                            array[0]++;\r
+                            NeedSave = true;\r
+                            break;\r
+                        case 32:\r
+                            array[1]++;\r
+                            NeedSave = true;\r
+                            break;\r
+                        case 33:\r
+                            array[2]++;\r
+                            NeedSave = true;\r
+                            break;\r
+                    }\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(875, out var q875))\r
+            {\r
+                if (_map == 54 && _boss && QuestSortie.CompareRank(rank, "S") == 0)\r
+                {\r
+                    var fleet = _battleInfo.Result.Friend.Main.Where(s => s.NowHp > 0).Select(s => s.Spec.Id).ToArray();\r
+                    if (fleet.Contains(543) && fleet.Intersect(new []{344, 345, 359}).Any())\r
+                        IncrementCount(q875.Count);\r
+                }\r
+            }\r
+        }\r
+\r
+        public void InspectPracticeResult(dynamic json)\r
+        {\r
+            foreach (var quest in _quests.Values)\r
+            {\r
+                var count = quest.Count;\r
+                if (!(count.Spec is QuestPractice practice))\r
+                    continue;\r
+                if (practice.Check(json.api_win_rank))\r
+                    IncrementCount(count);\r
+            }\r
+        }\r
+\r
+        private readonly int[] _missionId = new int[ShipInfo.FleetCount];\r
+\r
+        public void InspectDeck(dynamic json)\r
+        {\r
+            foreach (var entry in json)\r
+                _missionId[(int)entry.api_id - 1] = (int)entry.api_mission[1];\r
+        }\r
+\r
+        public void InspectMissionResult(string request, dynamic json)\r
+        {\r
+            var values = HttpUtility.ParseQueryString(request);\r
+            var deck = int.Parse(values["api_deck_id"]);\r
+            if ((int)json.api_clear_result == 0)\r
+                return;\r
+            var mid = _missionId[deck - 1];\r
+            foreach (var quest in _quests.Values)\r
+            {\r
+                var count = quest.Count;\r
+                if (!(count.Spec is QuestMission mission))\r
+                    continue;\r
+                if (mission.Check(mid))\r
+                    IncrementCount(count);\r
+            }\r
+            if (_quests.TryGetValue(426, out var q426))\r
+            {\r
+                var count = q426.Count;\r
+                switch (mid)\r
+                {\r
+                    case 3:\r
+                        count.NowArray[0]++;\r
+                        break;\r
+                    case 4:\r
+                        count.NowArray[1]++;\r
+                        break;\r
+                    case 5:\r
+                        count.NowArray[2]++;\r
+                        break;\r
+                    case 10:\r
+                        count.NowArray[3]++;\r
+                        break;\r
+                }\r
+            }\r
+            if (_quests.TryGetValue(428, out var q428))\r
+            {\r
+                var count = q428.Count;\r
+                switch (mid)\r
+                {\r
+                    case 4:\r
+                        count.NowArray[0]++;\r
+                        break;\r
+                    case 101:\r
+                        count.NowArray[1]++;\r
+                        break;\r
+                    case 102:\r
+                        count.NowArray[2]++;\r
+                        break;\r
+                }\r
+            }\r
+        }\r
+\r
+        private void IncrementCount(QuestCount count)\r
+        {\r
+            count.Now++;\r
+            NeedSave = true;\r
+        }\r
+\r
+        private void AddCount(QuestCount count, int value)\r
+        {\r
+            count.Now += value;\r
+            NeedSave = true;\r
+        }\r
+\r
+        private void IncrementCount(int id)\r
+        {\r
+            AddCount(id, 1);\r
+        }\r
+\r
+        private void AddCount(int id, int value)\r
+        {\r
+            if (_quests.TryGetValue(id, out var quest))\r
+            {\r
+                quest.Count.Now += value;\r
+                NeedSave = true;\r
+            }\r
+        }\r
+\r
+        public void CountNyukyo() => IncrementCount(503);\r
+\r
+        public void CountCharge() => IncrementCount(504);\r
+\r
+        public void CountCreateItem()\r
+        {\r
+            IncrementCount(605);\r
+            IncrementCount(607);\r
+        }\r
+\r
+        public void CountCreateShip()\r
+        {\r
+            IncrementCount(606);\r
+            IncrementCount(608);\r
+        }\r
+\r
+        public void InspectDestroyShip(string request)\r
+        {\r
+            AddCount(609, HttpUtility.ParseQueryString(request)["api_ship_id"].Split(',').Length);\r
+        }\r
+\r
+        public void CountRemodelSlot() => IncrementCount(619);\r
+\r
+        public void InspectDestroyItem(string request, dynamic json)\r
+        {\r
+            var values = HttpUtility.ParseQueryString(request);\r
+            var items = values["api_slotitem_ids"].Split(',')\r
+                .Select(id => _itemInfo.GetStatus(int.Parse(id)).Spec.Type).ToArray();\r
+            IncrementCount(613); // 613: 資源の再利用\r
+            foreach (var quest in _quests.Values)\r
+            {\r
+                var count = quest.Count;\r
+                if (!(count.Spec is QuestDestroyItem destroy))\r
+                    continue;\r
+                AddCount(count, items.Count(destroy.Check));\r
+            }\r
+            if (_quests.TryGetValue(675, out var q675))\r
+            {\r
+                q675.Count.NowArray[0] += items.Count(id => id == 6);\r
+                q675.Count.NowArray[1] += items.Count(id => id == 21);\r
+                NeedSave = true;\r
+            }\r
+            if (_quests.TryGetValue(676, out var q676))\r
+            {\r
+                q676.Count.NowArray[0] += items.Count(id => id == 2);\r
+                q676.Count.NowArray[1] += items.Count(id => id == 4);\r
+                q676.Count.NowArray[2] += items.Count(id => id == 30);\r
+                NeedSave = true;\r
+            }\r
+            if (_quests.TryGetValue(677, out var q677))\r
+            {\r
+                q677.Count.NowArray[0] += items.Count(id => id == 3);\r
+                q677.Count.NowArray[1] += items.Count(id => id == 10);\r
+                q677.Count.NowArray[2] += items.Count(id => id == 5);\r
+                NeedSave = true;\r
+            }\r
+        }\r
+\r
+        public void InspectPowerup(dynamic json)\r
+        {\r
+            if ((int)json.api_powerup_flag == 0)\r
+                return;\r
+            foreach (var quest in _quests.Values)\r
+            {\r
+                var count = quest.Count;\r
+                if (!(count.Spec is QuestPowerup))\r
+                    continue;\r
+                IncrementCount(count);\r
+            }\r
+        }\r
+\r
+        public void InspectStop(string request)\r
+        {\r
+            var values = HttpUtility.ParseQueryString(request);\r
+            _quests.Remove(int.Parse(values["api_quest_id"]));\r
+            NeedSave = true;\r
+        }\r
+\r
+        public void InspectClearItemGet(string request)\r
+        {\r
+            var values = HttpUtility.ParseQueryString(request);\r
+            var id = int.Parse(values["api_quest_id"]);\r
+            _countList.Remove(id);\r
+            _quests.Remove(id);\r
+            NeedSave = true;\r
+        }\r
+\r
+        public bool NeedSave { get; private set; }\r
+\r
+        public void SaveState(Status status)\r
+        {\r
+            NeedSave = false;\r
+            status.QuestLastReset = _lastReset;\r
+            if (_quests != null)\r
+                status.QuestList = _quests.Values.ToArray();\r
+            if (_countList != null)\r
+                status.QuestCountList = _countList.CountList.ToArray();\r
+        }\r
+\r
+        public void LoadState(Status status)\r
+        {\r
+            _lastReset = status.QuestLastReset;\r
+            if (status.QuestCountList != null)\r
+                _countList.CountList = status.QuestCountList;\r
+            if (status.QuestList != null)\r
+            {\r
+                _quests.Clear();\r
+                foreach (var q in status.QuestList)\r
+                    AddQuest(q.Id, q.Category, q.Name, q.Detail, q.Material, q.Progress, false);\r
+            }\r
         }\r
     }\r
 }
\ No newline at end of file